diff --git a/CHANGES.md b/CHANGES.md index ed0fc52d9..b424a3872 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,10 @@ * Type-safe path/query parameter handling, using serde #70 +* Use more ergonomic `actix_web::Error` instead of `http::Error` for `HttpResponseBuilder::body()` + +* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` + * Router cannot parse Non-ASCII characters in URL #137 * Fix long client urls #129 @@ -12,10 +16,6 @@ * Fix client connection pooling -* Use more ergonomic `actix_web::Error` instead of `http::Error` for `HttpResponseBuilder::body()` - -* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` - ## 0.4.10 (2018-03-20) diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index e2c4492ea..8a20da46f 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -437,6 +437,7 @@ This handler designed to be use as a handler for application's *default resource # extern crate actix_web; # #[macro_use] extern crate serde_derive; # use actix_web::*; +use actix_web::helpers::NormalizePath; # # fn index(req: HttpRequest) -> httpcodes::StaticResponse { # httpcodes::HttpOk @@ -462,6 +463,7 @@ It is possible to register path normalization only for *GET* requests only: # extern crate actix_web; # #[macro_use] extern crate serde_derive; # use actix_web::*; +use actix_web::helpers::NormalizePath; # # fn index(req: HttpRequest) -> httpcodes::StaticResponse { # httpcodes::HttpOk diff --git a/src/handler.rs b/src/handler.rs index 7b8f2d480..2475ed9f8 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,10 +1,6 @@ use std::marker::PhantomData; - -use regex::Regex; use futures::future::{Future, ok, err}; -use http::{header, StatusCode}; -use body::Body; use error::Error; use httprequest::HttpRequest; use httpresponse::HttpResponse; @@ -341,316 +337,3 @@ impl RouteHandler for AsyncHandler Reply::async(fut) } } - -/// Path normalization helper -/// -/// By normalizing it means: -/// -/// - Add a trailing slash to the path. -/// - Remove a trailing slash from the path. -/// - Double slashes are replaced by one. -/// -/// The handler returns as soon as it finds a path that resolves -/// correctly. The order if all enable is 1) merge, 3) both merge and append -/// and 3) append. If the path resolves with -/// at least one of those conditions, it will redirect to the new path. -/// -/// If *append* is *true* append slash when needed. If a resource is -/// defined with trailing slash and the request comes without it, it will -/// append it automatically. -/// -/// If *merge* is *true*, merge multiple consecutive slashes in the path into one. -/// -/// This handler designed to be use as a handler for application's *default resource*. -/// -/// ```rust -/// # extern crate actix_web; -/// # #[macro_use] extern crate serde_derive; -/// # use actix_web::*; -/// # -/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { -/// # httpcodes::HttpOk -/// # } -/// fn main() { -/// let app = Application::new() -/// .resource("/test/", |r| r.f(index)) -/// .default_resource(|r| r.h(NormalizePath::default())) -/// .finish(); -/// } -/// ``` -/// In this example `/test`, `/test///` will be redirected to `/test/` url. -pub struct NormalizePath { - append: bool, - merge: bool, - re_merge: Regex, - redirect: StatusCode, - not_found: StatusCode, -} - -impl Default for NormalizePath { - /// Create default `NormalizePath` instance, *append* is set to *true*, - /// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY` - fn default() -> NormalizePath { - NormalizePath { - append: true, - merge: true, - re_merge: Regex::new("//+").unwrap(), - redirect: StatusCode::MOVED_PERMANENTLY, - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl NormalizePath { - /// Create new `NormalizePath` instance - pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { - NormalizePath { - append, - merge, - redirect, - re_merge: Regex::new("//+").unwrap(), - not_found: StatusCode::NOT_FOUND, - } - } -} - -impl Handler for NormalizePath { - type Result = Result; - - fn handle(&mut self, req: HttpRequest) -> Self::Result { - if let Some(router) = req.router() { - let query = req.query_string(); - if self.merge { - // merge slashes - let p = self.re_merge.replace_all(req.path(), "/"); - if p.len() != req.path().len() { - if router.has_route(p.as_ref()) { - let p = if !query.is_empty() { p + "?" + query } else { p }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_ref()) - .body(Body::Empty); - } - // merge slashes and append trailing slash - if self.append && !p.ends_with('/') { - let p = p.as_ref().to_owned() + "/"; - if router.has_route(&p) { - let p = if !query.is_empty() { p + "?" + query } else { p }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .body(Body::Empty); - } - } - - // try to remove trailing slash - if p.ends_with('/') { - let p = p.as_ref().trim_right_matches('/'); - if router.has_route(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str()) - } else { - req.header(header::LOCATION, p) - } - .body(Body::Empty); - } - } - } else if p.ends_with('/') { - // try to remove trailing slash - let p = p.as_ref().trim_right_matches('/'); - if router.has_route(p) { - let mut req = HttpResponse::build(self.redirect); - return if !query.is_empty() { - req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str()) - } else { - req.header(header::LOCATION, p) - } - .body(Body::Empty); - } - } - } - // append trailing slash - if self.append && !req.path().ends_with('/') { - let p = req.path().to_owned() + "/"; - if router.has_route(&p) { - let p = if !query.is_empty() { p + "?" + query } else { p }; - return HttpResponse::build(self.redirect) - .header(header::LOCATION, p.as_str()) - .body(Body::Empty); - } - } - } - Ok(HttpResponse::new(self.not_found, Body::Empty)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use http::{header, Method}; - use test::TestRequest; - use application::Application; - - fn index(_req: HttpRequest) -> HttpResponse { - HttpResponse::new(StatusCode::OK, Body::Empty) - } - - #[test] - fn test_normalize_path_trailing_slashes() { - let mut app = Application::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = - vec![("/resource1", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/", "", StatusCode::OK), - ("/resource1?p1=1&p2=2", "", StatusCode::OK), - ("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2", - StatusCode::MOVED_PERMANENTLY), - ("/resource2/?p1=1&p2=2", "", StatusCode::OK) - ]; - for (path, target, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); - let resp = app.run(req); - let r = resp.as_response().unwrap(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); - } - } - } - - #[test] - fn test_normalize_path_trailing_slashes_disabled() { - let mut app = Application::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h( - NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY))) - .finish(); - - // trailing slashes - let params = vec![("/resource1", StatusCode::OK), - ("/resource1/", StatusCode::MOVED_PERMANENTLY), - ("/resource2", StatusCode::NOT_FOUND), - ("/resource2/", StatusCode::OK), - ("/resource1?p1=1&p2=2", StatusCode::OK), - ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), - ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), - ("/resource2/?p1=1&p2=2", StatusCode::OK) - ]; - for (path, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); - let resp = app.run(req); - let r = resp.as_response().unwrap(); - assert_eq!(r.status(), code); - } - } - - #[test] - fn test_normalize_path_merge_slashes() { - let mut app = Application::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/resource1/a/b?p=1", "", StatusCode::OK), - ("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ]; - for (path, target, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); - let resp = app.run(req); - let r = resp.as_response().unwrap(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, - r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); - } - } - } - - #[test] - fn test_normalize_path_merge_and_append_slashes() { - let mut app = Application::new() - .resource("/resource1", |r| r.method(Method::GET).f(index)) - .resource("/resource2/", |r| r.method(Method::GET).f(index)) - .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) - .resource("/resource2/a/b/", |r| r.method(Method::GET).f(index)) - .default_resource(|r| r.h(NormalizePath::default())) - .finish(); - - // trailing slashes - let params = vec![ - ("/resource1/a/b", "", StatusCode::OK), - ("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), - ("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("/resource2/a/b/", "", StatusCode::OK), - ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), - ("/resource1/a/b?p=1", "", StatusCode::OK), - ("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), - ("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), - ]; - for (path, target, code) in params { - let req = app.prepare_request(TestRequest::with_uri(path).finish()); - let resp = app.run(req); - let r = resp.as_response().unwrap(); - assert_eq!(r.status(), code); - if !target.is_empty() { - assert_eq!( - target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); - } - } - } - - -} diff --git a/src/helpers.rs b/src/helpers.rs new file mode 100644 index 000000000..9895a5111 --- /dev/null +++ b/src/helpers.rs @@ -0,0 +1,322 @@ +//! Various helpers + +use regex::Regex; +use http::{header, StatusCode}; + +use body::Body; +use error::Error; +use handler::Handler; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; + +/// Path normalization helper +/// +/// By normalizing it means: +/// +/// - Add a trailing slash to the path. +/// - Remove a trailing slash from the path. +/// - Double slashes are replaced by one. +/// +/// The handler returns as soon as it finds a path that resolves +/// correctly. The order if all enable is 1) merge, 3) both merge and append +/// and 3) append. If the path resolves with +/// at least one of those conditions, it will redirect to the new path. +/// +/// If *append* is *true* append slash when needed. If a resource is +/// defined with trailing slash and the request comes without it, it will +/// append it automatically. +/// +/// If *merge* is *true*, merge multiple consecutive slashes in the path into one. +/// +/// This handler designed to be use as a handler for application's *default resource*. +/// +/// ```rust +/// # extern crate actix_web; +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// use actix_web::helpers::NormalizePath; +/// # +/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse { +/// # httpcodes::HttpOk +/// # } +/// fn main() { +/// let app = Application::new() +/// .resource("/test/", |r| r.f(index)) +/// .default_resource(|r| r.h(NormalizePath::default())) +/// .finish(); +/// } +/// ``` +/// In this example `/test`, `/test///` will be redirected to `/test/` url. +pub struct NormalizePath { + append: bool, + merge: bool, + re_merge: Regex, + redirect: StatusCode, + not_found: StatusCode, +} + +impl Default for NormalizePath { + /// Create default `NormalizePath` instance, *append* is set to *true*, + /// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY` + fn default() -> NormalizePath { + NormalizePath { + append: true, + merge: true, + re_merge: Regex::new("//+").unwrap(), + redirect: StatusCode::MOVED_PERMANENTLY, + not_found: StatusCode::NOT_FOUND, + } + } +} + +impl NormalizePath { + /// Create new `NormalizePath` instance + pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath { + NormalizePath { + append, + merge, + redirect, + re_merge: Regex::new("//+").unwrap(), + not_found: StatusCode::NOT_FOUND, + } + } +} + +impl Handler for NormalizePath { + type Result = Result; + + fn handle(&mut self, req: HttpRequest) -> Self::Result { + if let Some(router) = req.router() { + let query = req.query_string(); + if self.merge { + // merge slashes + let p = self.re_merge.replace_all(req.path(), "/"); + if p.len() != req.path().len() { + if router.has_route(p.as_ref()) { + let p = if !query.is_empty() { p + "?" + query } else { p }; + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_ref()) + .body(Body::Empty); + } + // merge slashes and append trailing slash + if self.append && !p.ends_with('/') { + let p = p.as_ref().to_owned() + "/"; + if router.has_route(&p) { + let p = if !query.is_empty() { p + "?" + query } else { p }; + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_str()) + .body(Body::Empty); + } + } + + // try to remove trailing slash + if p.ends_with('/') { + let p = p.as_ref().trim_right_matches('/'); + if router.has_route(p) { + let mut req = HttpResponse::build(self.redirect); + return if !query.is_empty() { + req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str()) + } else { + req.header(header::LOCATION, p) + } + .body(Body::Empty); + } + } + } else if p.ends_with('/') { + // try to remove trailing slash + let p = p.as_ref().trim_right_matches('/'); + if router.has_route(p) { + let mut req = HttpResponse::build(self.redirect); + return if !query.is_empty() { + req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str()) + } else { + req.header(header::LOCATION, p) + } + .body(Body::Empty); + } + } + } + // append trailing slash + if self.append && !req.path().ends_with('/') { + let p = req.path().to_owned() + "/"; + if router.has_route(&p) { + let p = if !query.is_empty() { p + "?" + query } else { p }; + return HttpResponse::build(self.redirect) + .header(header::LOCATION, p.as_str()) + .body(Body::Empty); + } + } + } + Ok(HttpResponse::new(self.not_found, Body::Empty)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::{header, Method}; + use test::TestRequest; + use application::Application; + + fn index(_req: HttpRequest) -> HttpResponse { + HttpResponse::new(StatusCode::OK, Body::Empty) + } + + #[test] + fn test_normalize_path_trailing_slashes() { + let mut app = Application::new() + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource2/", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); + + // trailing slashes + let params = + vec![("/resource1", "", StatusCode::OK), + ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY), + ("/resource2/", "", StatusCode::OK), + ("/resource1?p1=1&p2=2", "", StatusCode::OK), + ("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), + ("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2", + StatusCode::MOVED_PERMANENTLY), + ("/resource2/?p1=1&p2=2", "", StatusCode::OK) + ]; + for (path, target, code) in params { + let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + if !target.is_empty() { + assert_eq!( + target, + r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + } + } + } + + #[test] + fn test_normalize_path_trailing_slashes_disabled() { + let mut app = Application::new() + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource2/", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h( + NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY))) + .finish(); + + // trailing slashes + let params = vec![("/resource1", StatusCode::OK), + ("/resource1/", StatusCode::MOVED_PERMANENTLY), + ("/resource2", StatusCode::NOT_FOUND), + ("/resource2/", StatusCode::OK), + ("/resource1?p1=1&p2=2", StatusCode::OK), + ("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY), + ("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND), + ("/resource2/?p1=1&p2=2", StatusCode::OK) + ]; + for (path, code) in params { + let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + } + } + + #[test] + fn test_normalize_path_merge_slashes() { + let mut app = Application::new() + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); + + // trailing slashes + let params = vec![ + ("/resource1/a/b", "", StatusCode::OK), + ("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/resource1/a/b?p=1", "", StatusCode::OK), + ("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ]; + for (path, target, code) in params { + let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + if !target.is_empty() { + assert_eq!( + target, + r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + } + } + } + + #[test] + fn test_normalize_path_merge_and_append_slashes() { + let mut app = Application::new() + .resource("/resource1", |r| r.method(Method::GET).f(index)) + .resource("/resource2/", |r| r.method(Method::GET).f(index)) + .resource("/resource1/a/b", |r| r.method(Method::GET).f(index)) + .resource("/resource2/a/b/", |r| r.method(Method::GET).f(index)) + .default_resource(|r| r.h(NormalizePath::default())) + .finish(); + + // trailing slashes + let params = vec![ + ("/resource1/a/b", "", StatusCode::OK), + ("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY), + ("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/resource2/a/b/", "", StatusCode::OK), + ("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY), + ("/resource1/a/b?p=1", "", StatusCode::OK), + ("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY), + ("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY), + ]; + for (path, target, code) in params { + let req = app.prepare_request(TestRequest::with_uri(path).finish()); + let resp = app.run(req); + let r = resp.as_response().unwrap(); + assert_eq!(r.status(), code); + if !target.is_empty() { + assert_eq!( + target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap()); + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 367b9b656..88a690b80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,7 @@ pub mod fs; pub mod ws; pub mod error; pub mod header; +pub mod helpers; pub mod httpcodes; pub mod multipart; pub mod middleware; @@ -157,12 +158,6 @@ pub(crate) const HAS_TLS: bool = true; #[cfg(not(feature="tls"))] pub(crate) const HAS_TLS: bool = false; -pub mod helpers { - //! Various helpers - - pub use handler::{NormalizePath}; -} - pub mod dev { //! The `actix-web` prelude for library developers //!