1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-02 13:29:24 +00:00
actix-web/src/middleware/normalize.rs

199 lines
6 KiB
Rust
Raw Normal View History

//! `Middleware` to normalize request's URI
2019-11-21 08:52:33 +00:00
use std::task::{Context, Poll};
2019-12-05 17:35:43 +00:00
use actix_http::http::{PathAndQuery, Uri};
use actix_service::{Service, Transform};
2019-05-01 19:40:56 +00:00
use bytes::Bytes;
2020-05-18 02:47:20 +00:00
use futures_util::future::{ok, Ready};
2019-04-20 00:23:17 +00:00
use regex::Regex;
use crate::service::{ServiceRequest, ServiceResponse};
2019-04-25 18:14:32 +00:00
use crate::Error;
#[derive(Default, Clone, Copy)]
/// `Middleware` to normalize request's URI in place
///
/// Performs following:
///
/// - Merges multiple slashes into one.
///
/// ```rust
/// use actix_web::{web, http, middleware, App, HttpResponse};
///
2019-11-21 08:52:33 +00:00
/// # fn main() {
/// let app = App::new()
/// .wrap(middleware::NormalizePath)
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
/// );
/// # }
/// ```
pub struct NormalizePath;
impl<S, B> Transform<S> for NormalizePath
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
2019-04-25 18:14:32 +00:00
type Error = Error;
type InitError = ();
type Transform = NormalizePathNormalization<S>;
2019-11-21 08:52:33 +00:00
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
2019-11-21 08:52:33 +00:00
ok(NormalizePathNormalization {
service,
2019-04-20 00:23:17 +00:00
merge_slash: Regex::new("//+").unwrap(),
})
}
}
pub struct NormalizePathNormalization<S> {
service: S,
merge_slash: Regex,
}
impl<S, B> Service for NormalizePathNormalization<S>
where
S: Service<Request = ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
S::Future: 'static,
{
type Request = ServiceRequest;
type Response = ServiceResponse<B>;
2019-04-25 18:14:32 +00:00
type Error = Error;
type Future = S::Future;
2019-12-07 18:46:51 +00:00
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
2019-11-21 08:52:33 +00:00
self.service.poll_ready(cx)
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let head = req.head_mut();
// always add trailing slash, might be an extra one
let path = head.uri.path().to_string() + "/";
let original_len = path.len();
// normalize multiple /'s to one /
let path = self.merge_slash.replace_all(&path, "/");
if original_len != path.len() {
2019-05-01 19:40:56 +00:00
let mut parts = head.uri.clone().into_parts();
let pq = parts.path_and_query.as_ref().unwrap();
let path = if let Some(q) = pq.query() {
Bytes::from(format!("{}?{}", path, q))
} else {
2019-12-05 17:35:43 +00:00
Bytes::copy_from_slice(path.as_bytes())
2019-05-01 19:40:56 +00:00
};
2019-12-05 17:35:43 +00:00
parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap());
2019-05-01 19:40:56 +00:00
let uri = Uri::from_parts(parts).unwrap();
req.match_info_mut().get_mut().update(&uri);
req.head_mut().uri = uri;
}
self.service.call(req)
}
}
#[cfg(test)]
mod tests {
2019-05-12 15:34:51 +00:00
use actix_service::IntoService;
use super::*;
use crate::dev::ServiceRequest;
2019-11-26 05:25:50 +00:00
use crate::test::{call_service, init_service, TestRequest};
2019-05-01 19:40:56 +00:00
use crate::{web, App, HttpResponse};
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_wrap() {
let mut app = init_service(
App::new()
.wrap(NormalizePath::default())
.service(web::resource("/v1/something/").to(|| HttpResponse::Ok())),
)
.await;
let req = TestRequest::with_uri("/v1//something////").to_request();
let res = call_service(&mut app, req).await;
assert!(res.status().is_success());
let req2 = TestRequest::with_uri("//v1/something").to_request();
let res2 = call_service(&mut app, req2).await;
assert!(res2.status().is_success());
let req3 = TestRequest::with_uri("//v1//////something").to_request();
let res3 = call_service(&mut app, req3).await;
assert!(res3.status().is_success());
2019-05-01 19:40:56 +00:00
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn test_in_place_normalization() {
let srv = |req: ServiceRequest| {
assert_eq!("/v1/something/", req.path());
ok(req.into_response(HttpResponse::Ok().finish()))
};
let mut normalize = NormalizePath
.new_transform(srv.into_service())
.await
.unwrap();
let req = TestRequest::with_uri("/v1//something////").to_srv_request();
let res = normalize.call(req).await.unwrap();
assert!(res.status().is_success());
let req2 = TestRequest::with_uri("///v1/something").to_srv_request();
let res2 = normalize.call(req2).await.unwrap();
assert!(res2.status().is_success());
let req3 = TestRequest::with_uri("//v1///something").to_srv_request();
let res3 = normalize.call(req3).await.unwrap();
assert!(res3.status().is_success());
}
2019-11-26 05:25:50 +00:00
#[actix_rt::test]
async fn should_normalize_nothing() {
const URI: &str = "/v1/something/";
2019-11-26 05:25:50 +00:00
let srv = |req: ServiceRequest| {
assert_eq!(URI, req.path());
ok(req.into_response(HttpResponse::Ok().finish()))
};
let mut normalize = NormalizePath
.new_transform(srv.into_service())
.await
.unwrap();
let req = TestRequest::with_uri(URI).to_srv_request();
let res = normalize.call(req).await.unwrap();
assert!(res.status().is_success());
}
#[actix_rt::test]
async fn should_normalize_nothing_notrail() {
const URI: &str = "/v1/something";
let srv = |req: ServiceRequest| {
assert_eq!(URI, req.path());
ok(req.into_response(HttpResponse::Ok().finish()))
};
2019-11-26 05:25:50 +00:00
let mut normalize = NormalizePath
.new_transform(srv.into_service())
.await
.unwrap();
2019-11-26 05:25:50 +00:00
let req = TestRequest::with_uri(URI).to_srv_request();
let res = normalize.call(req).await.unwrap();
assert!(res.status().is_success());
}
}