2020-12-23 00:19:20 +00:00
|
|
|
//! For middleware documentation, see [`NormalizePath`].
|
|
|
|
|
2021-12-05 14:37:20 +00:00
|
|
|
use actix_http::uri::{PathAndQuery, Uri};
|
2019-04-19 20:53:49 +00:00
|
|
|
use actix_service::{Service, Transform};
|
2021-04-01 14:26:13 +00:00
|
|
|
use actix_utils::future::{ready, Ready};
|
2019-05-01 19:40:56 +00:00
|
|
|
use bytes::Bytes;
|
2019-04-20 00:23:17 +00:00
|
|
|
use regex::Regex;
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2021-01-05 09:51:58 +00:00
|
|
|
use crate::{
|
|
|
|
service::{ServiceRequest, ServiceResponse},
|
|
|
|
Error,
|
|
|
|
};
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2021-01-05 09:51:58 +00:00
|
|
|
/// Determines the behavior of the [`NormalizePath`] middleware.
|
|
|
|
///
|
|
|
|
/// The default is `TrailingSlash::Trim`.
|
2020-08-19 11:21:52 +00:00
|
|
|
#[non_exhaustive]
|
2021-01-05 09:51:58 +00:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
2020-08-19 11:21:52 +00:00
|
|
|
pub enum TrailingSlash {
|
2021-01-05 09:51:58 +00:00
|
|
|
/// Trim trailing slashes from the end of the path.
|
|
|
|
///
|
|
|
|
/// Using this will require all routes to omit trailing slashes for them to be accessible.
|
|
|
|
Trim,
|
2020-12-23 00:19:20 +00:00
|
|
|
|
2020-09-25 11:50:59 +00:00
|
|
|
/// Only merge any present multiple trailing slashes.
|
|
|
|
///
|
2021-01-05 09:51:58 +00:00
|
|
|
/// This option provides the best compatibility with behavior in actix-web v2.0.
|
2020-09-25 11:50:59 +00:00
|
|
|
MergeOnly,
|
2020-12-23 00:19:20 +00:00
|
|
|
|
2021-01-05 09:51:58 +00:00
|
|
|
/// Always add a trailing slash to the end of the path.
|
|
|
|
///
|
|
|
|
/// Using this will require all routes have a trailing slash for them to be accessible.
|
|
|
|
Always,
|
2020-08-19 11:21:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for TrailingSlash {
|
|
|
|
fn default() -> Self {
|
2021-01-05 09:51:58 +00:00
|
|
|
TrailingSlash::Trim
|
2020-08-19 11:21:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-05 09:51:58 +00:00
|
|
|
/// Middleware for normalizing a request's path so that routes can be matched more flexibly.
|
2019-04-19 20:53:49 +00:00
|
|
|
///
|
2020-12-23 00:19:20 +00:00
|
|
|
/// # Normalization Steps
|
2021-01-05 09:51:58 +00:00
|
|
|
/// - Merges consecutive slashes into one. (For example, `/path//one` always becomes `/path/one`.)
|
2020-09-25 11:50:59 +00:00
|
|
|
/// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing
|
2020-12-23 00:19:20 +00:00
|
|
|
/// slashes as-is, depending on which [`TrailingSlash`] variant is supplied
|
|
|
|
/// to [`new`](NormalizePath::new()).
|
|
|
|
///
|
|
|
|
/// # Default Behavior
|
2021-01-05 09:51:58 +00:00
|
|
|
/// The default constructor chooses to strip trailing slashes from the end of paths with them
|
|
|
|
/// ([`TrailingSlash::Trim`]). The implication is that route definitions should be defined without
|
|
|
|
/// trailing slashes or else they will be inaccessible (or vice versa when using the
|
|
|
|
/// `TrailingSlash::Always` behavior), as shown in the example tests below.
|
2019-05-01 18:47:51 +00:00
|
|
|
///
|
2021-02-10 12:10:03 +00:00
|
|
|
/// # Examples
|
2021-03-25 08:45:52 +00:00
|
|
|
/// ```
|
2020-12-23 00:19:20 +00:00
|
|
|
/// use actix_web::{web, middleware, App};
|
2019-05-01 18:47:51 +00:00
|
|
|
///
|
2021-02-07 01:00:40 +00:00
|
|
|
/// # actix_web::rt::System::new().block_on(async {
|
2019-11-21 08:52:33 +00:00
|
|
|
/// let app = App::new()
|
2021-08-31 03:07:53 +00:00
|
|
|
/// .wrap(middleware::NormalizePath::trim())
|
2020-12-23 00:19:20 +00:00
|
|
|
/// .route("/test", web::get().to(|| async { "test" }))
|
|
|
|
/// .route("/unmatchable/", web::get().to(|| async { "unmatchable" }));
|
|
|
|
///
|
|
|
|
/// use actix_web::http::StatusCode;
|
|
|
|
/// use actix_web::test::{call_service, init_service, TestRequest};
|
|
|
|
///
|
2021-02-07 01:00:40 +00:00
|
|
|
/// let app = init_service(app).await;
|
2020-12-23 00:19:20 +00:00
|
|
|
///
|
|
|
|
/// let req = TestRequest::with_uri("/test").to_request();
|
2021-02-07 01:00:40 +00:00
|
|
|
/// let res = call_service(&app, req).await;
|
2020-12-23 00:19:20 +00:00
|
|
|
/// assert_eq!(res.status(), StatusCode::OK);
|
|
|
|
///
|
|
|
|
/// let req = TestRequest::with_uri("/test/").to_request();
|
2021-02-07 01:00:40 +00:00
|
|
|
/// let res = call_service(&app, req).await;
|
2020-12-23 00:19:20 +00:00
|
|
|
/// assert_eq!(res.status(), StatusCode::OK);
|
|
|
|
///
|
|
|
|
/// let req = TestRequest::with_uri("/unmatchable").to_request();
|
2021-02-07 01:00:40 +00:00
|
|
|
/// let res = call_service(&app, req).await;
|
2020-12-23 00:19:20 +00:00
|
|
|
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
|
|
|
///
|
|
|
|
/// let req = TestRequest::with_uri("/unmatchable/").to_request();
|
2021-02-07 01:00:40 +00:00
|
|
|
/// let res = call_service(&app, req).await;
|
2020-12-23 00:19:20 +00:00
|
|
|
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
2021-01-05 09:51:58 +00:00
|
|
|
/// # })
|
2019-05-01 18:47:51 +00:00
|
|
|
/// ```
|
2021-08-31 03:07:53 +00:00
|
|
|
#[derive(Debug, Clone, Copy)]
|
2020-08-19 11:21:52 +00:00
|
|
|
pub struct NormalizePath(TrailingSlash);
|
|
|
|
|
2021-08-31 03:07:53 +00:00
|
|
|
impl Default for NormalizePath {
|
|
|
|
fn default() -> Self {
|
|
|
|
log::warn!(
|
|
|
|
"`NormalizePath::default()` is deprecated. The default trailing slash behavior changed \
|
|
|
|
in v4 from `Always` to `Trim`. Update your call to `NormalizePath::new(...)`."
|
|
|
|
);
|
|
|
|
|
|
|
|
Self(TrailingSlash::Trim)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-19 11:21:52 +00:00
|
|
|
impl NormalizePath {
|
|
|
|
/// Create new `NormalizePath` middleware with the specified trailing slash style.
|
|
|
|
pub fn new(trailing_slash_style: TrailingSlash) -> Self {
|
2021-08-31 03:07:53 +00:00
|
|
|
Self(trailing_slash_style)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Constructs a new `NormalizePath` middleware with [trim](TrailingSlash::Trim) semantics.
|
|
|
|
///
|
|
|
|
/// Use this instead of `NormalizePath::default()` to avoid deprecation warning.
|
|
|
|
pub fn trim() -> Self {
|
|
|
|
Self::new(TrailingSlash::Trim)
|
2020-08-19 11:21:52 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2021-01-03 23:47:04 +00:00
|
|
|
impl<S, B> Transform<S, ServiceRequest> for NormalizePath
|
2019-04-19 20:53:49 +00:00
|
|
|
where
|
2021-01-03 23:47:04 +00:00
|
|
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
2019-05-01 18:47:51 +00:00
|
|
|
S::Future: 'static,
|
2019-04-19 20:53:49 +00:00
|
|
|
{
|
2019-05-01 18:47:51 +00:00
|
|
|
type Response = ServiceResponse<B>;
|
2019-04-25 18:14:32 +00:00
|
|
|
type Error = Error;
|
2019-04-19 20:53:49 +00:00
|
|
|
type Transform = NormalizePathNormalization<S>;
|
2021-01-03 23:47:04 +00:00
|
|
|
type InitError = ();
|
2019-11-21 08:52:33 +00:00
|
|
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
2019-04-19 20:53:49 +00:00
|
|
|
|
|
|
|
fn new_transform(&self, service: S) -> Self::Future {
|
2020-12-23 00:19:20 +00:00
|
|
|
ready(Ok(NormalizePathNormalization {
|
2019-04-19 20:53:49 +00:00
|
|
|
service,
|
2019-04-20 00:23:17 +00:00
|
|
|
merge_slash: Regex::new("//+").unwrap(),
|
2020-08-19 11:21:52 +00:00
|
|
|
trailing_slash_behavior: self.0,
|
2020-12-23 00:19:20 +00:00
|
|
|
}))
|
2019-04-19 20:53:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct NormalizePathNormalization<S> {
|
|
|
|
service: S,
|
|
|
|
merge_slash: Regex,
|
2020-08-19 11:21:52 +00:00
|
|
|
trailing_slash_behavior: TrailingSlash,
|
2019-04-19 20:53:49 +00:00
|
|
|
}
|
|
|
|
|
2021-01-03 23:47:04 +00:00
|
|
|
impl<S, B> Service<ServiceRequest> for NormalizePathNormalization<S>
|
2019-04-19 20:53:49 +00:00
|
|
|
where
|
2021-01-03 23:47:04 +00:00
|
|
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
2019-05-01 18:47:51 +00:00
|
|
|
S::Future: 'static,
|
2019-04-19 20:53:49 +00:00
|
|
|
{
|
2019-05-01 18:47:51 +00:00
|
|
|
type Response = ServiceResponse<B>;
|
2019-04-25 18:14:32 +00:00
|
|
|
type Error = Error;
|
2019-04-19 20:53:49 +00:00
|
|
|
type Future = S::Future;
|
|
|
|
|
2021-01-05 09:51:58 +00:00
|
|
|
actix_service::forward_ready!(service);
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2021-02-07 01:00:40 +00:00
|
|
|
fn call(&self, mut req: ServiceRequest) -> Self::Future {
|
2019-04-19 20:53:49 +00:00
|
|
|
let head = req.head_mut();
|
2020-05-21 08:56:53 +00:00
|
|
|
|
2020-06-17 09:54:20 +00:00
|
|
|
let original_path = head.uri.path();
|
|
|
|
|
2021-06-05 16:19:45 +00:00
|
|
|
// An empty path here means that the URI has no valid path. We skip normalization in this
|
|
|
|
// case, because adding a path can make the URI invalid
|
|
|
|
if !original_path.is_empty() {
|
|
|
|
// Either adds a string to the end (duplicates will be removed anyways) or trims all
|
|
|
|
// slashes from the end
|
|
|
|
let path = match self.trailing_slash_behavior {
|
|
|
|
TrailingSlash::Always => format!("{}/", original_path),
|
|
|
|
TrailingSlash::MergeOnly => original_path.to_string(),
|
|
|
|
TrailingSlash::Trim => original_path.trim_end_matches('/').to_string(),
|
2019-05-01 19:40:56 +00:00
|
|
|
};
|
|
|
|
|
2021-06-05 16:19:45 +00:00
|
|
|
// normalize multiple /'s to one /
|
|
|
|
let path = self.merge_slash.replace_all(&path, "/");
|
|
|
|
|
|
|
|
// Ensure root paths are still resolvable. If resulting path is blank after previous
|
|
|
|
// step it means the path was one or more slashes. Reduce to single slash.
|
|
|
|
let path = if path.is_empty() { "/" } else { path.as_ref() };
|
|
|
|
|
|
|
|
// Check whether the path has been changed
|
|
|
|
//
|
|
|
|
// This check was previously implemented as string length comparison
|
|
|
|
//
|
|
|
|
// That approach fails when a trailing slash is added,
|
|
|
|
// and a duplicate slash is removed,
|
|
|
|
// since the length of the strings remains the same
|
|
|
|
//
|
|
|
|
// For example, the path "/v1//s" will be normalized to "/v1/s/"
|
|
|
|
// Both of the paths have the same length,
|
|
|
|
// so the change can not be deduced from the length comparison
|
|
|
|
if path != original_path {
|
|
|
|
let mut parts = head.uri.clone().into_parts();
|
|
|
|
let query = parts.path_and_query.as_ref().and_then(|pq| pq.query());
|
|
|
|
|
|
|
|
let path = match query {
|
|
|
|
Some(q) => Bytes::from(format!("{}?{}", path, q)),
|
|
|
|
None => Bytes::copy_from_slice(path.as_bytes()),
|
|
|
|
};
|
|
|
|
parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap());
|
|
|
|
|
|
|
|
let uri = Uri::from_parts(parts).unwrap();
|
|
|
|
req.match_info_mut().get_mut().update(&uri);
|
|
|
|
req.head_mut().uri = uri;
|
|
|
|
}
|
2019-04-19 20:53:49 +00:00
|
|
|
}
|
|
|
|
self.service.call(req)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2021-06-05 16:19:45 +00:00
|
|
|
use actix_http::StatusCode;
|
2019-05-12 15:34:51 +00:00
|
|
|
use actix_service::IntoService;
|
2019-04-19 20:53:49 +00:00
|
|
|
|
|
|
|
use super::*;
|
2020-12-23 00:19:20 +00:00
|
|
|
use crate::{
|
|
|
|
dev::ServiceRequest,
|
2021-06-03 02:28:09 +00:00
|
|
|
guard::fn_guard,
|
2020-12-23 00:19:20 +00:00
|
|
|
test::{call_service, init_service, TestRequest},
|
|
|
|
web, App, HttpResponse,
|
|
|
|
};
|
2019-05-01 19:40:56 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_wrap() {
|
2021-02-07 01:00:40 +00:00
|
|
|
let app = init_service(
|
2019-11-26 05:25:50 +00:00
|
|
|
App::new()
|
|
|
|
.wrap(NormalizePath::default())
|
2020-09-15 10:32:31 +00:00
|
|
|
.service(web::resource("/").to(HttpResponse::Ok))
|
2021-06-03 02:28:09 +00:00
|
|
|
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
|
|
|
.service(
|
|
|
|
web::resource("/v2/something")
|
2021-12-28 02:37:13 +00:00
|
|
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
2021-06-03 02:28:09 +00:00
|
|
|
.to(HttpResponse::Ok),
|
|
|
|
),
|
2019-11-26 05:25:50 +00:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
let test_uris = vec![
|
|
|
|
"/",
|
|
|
|
"/?query=test",
|
|
|
|
"///",
|
|
|
|
"/v1//something",
|
|
|
|
"/v1//something////",
|
|
|
|
"//v1/something",
|
|
|
|
"//v1//////something",
|
|
|
|
"/v2//something?query=test",
|
|
|
|
"/v2//something////?query=test",
|
|
|
|
"//v2/something?query=test",
|
|
|
|
"//v2//////something?query=test",
|
|
|
|
];
|
|
|
|
|
|
|
|
for uri in test_uris {
|
|
|
|
let req = TestRequest::with_uri(uri).to_request();
|
|
|
|
let res = call_service(&app, req).await;
|
|
|
|
assert!(res.status().is_success(), "Failed uri: {}", uri);
|
|
|
|
}
|
|
|
|
}
|
2020-09-15 10:32:31 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn trim_trailing_slashes() {
|
|
|
|
let app = init_service(
|
|
|
|
App::new()
|
|
|
|
.wrap(NormalizePath(TrailingSlash::Trim))
|
|
|
|
.service(web::resource("/").to(HttpResponse::Ok))
|
|
|
|
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
|
|
|
.service(
|
|
|
|
web::resource("/v2/something")
|
2021-12-28 02:37:13 +00:00
|
|
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
2021-06-03 02:28:09 +00:00
|
|
|
.to(HttpResponse::Ok),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await;
|
2020-09-15 10:32:31 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
let test_uris = vec![
|
|
|
|
"/",
|
|
|
|
"///",
|
|
|
|
"/v1/something",
|
|
|
|
"/v1/something/",
|
|
|
|
"/v1/something////",
|
|
|
|
"//v1//something",
|
|
|
|
"//v1//something//",
|
|
|
|
"/v2/something?query=test",
|
|
|
|
"/v2/something/?query=test",
|
|
|
|
"/v2/something////?query=test",
|
|
|
|
"//v2//something?query=test",
|
|
|
|
"//v2//something//?query=test",
|
|
|
|
];
|
2020-09-15 10:32:31 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
for uri in test_uris {
|
|
|
|
let req = TestRequest::with_uri(uri).to_request();
|
|
|
|
let res = call_service(&app, req).await;
|
|
|
|
assert!(res.status().is_success(), "Failed uri: {}", uri);
|
|
|
|
}
|
|
|
|
}
|
2020-04-04 18:26:40 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn trim_root_trailing_slashes_with_query() {
|
|
|
|
let app = init_service(
|
|
|
|
App::new().wrap(NormalizePath(TrailingSlash::Trim)).service(
|
|
|
|
web::resource("/")
|
2021-12-28 02:37:13 +00:00
|
|
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
2021-06-03 02:28:09 +00:00
|
|
|
.to(HttpResponse::Ok),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await;
|
2020-04-04 18:26:40 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
let test_uris = vec!["/?query=test", "//?query=test", "///?query=test"];
|
2020-06-17 09:54:20 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
for uri in test_uris {
|
|
|
|
let req = TestRequest::with_uri(uri).to_request();
|
|
|
|
let res = call_service(&app, req).await;
|
|
|
|
assert!(res.status().is_success(), "Failed uri: {}", uri);
|
|
|
|
}
|
2019-05-01 19:40:56 +00:00
|
|
|
}
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2020-08-19 11:21:52 +00:00
|
|
|
#[actix_rt::test]
|
2021-06-03 02:28:09 +00:00
|
|
|
async fn ensure_trailing_slash() {
|
2021-02-07 01:00:40 +00:00
|
|
|
let app = init_service(
|
2020-08-19 11:21:52 +00:00
|
|
|
App::new()
|
2021-06-03 02:28:09 +00:00
|
|
|
.wrap(NormalizePath(TrailingSlash::Always))
|
2020-09-15 10:32:31 +00:00
|
|
|
.service(web::resource("/").to(HttpResponse::Ok))
|
2021-06-03 02:28:09 +00:00
|
|
|
.service(web::resource("/v1/something/").to(HttpResponse::Ok))
|
|
|
|
.service(
|
|
|
|
web::resource("/v2/something/")
|
2021-12-28 02:37:13 +00:00
|
|
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
2021-06-03 02:28:09 +00:00
|
|
|
.to(HttpResponse::Ok),
|
|
|
|
),
|
2020-08-19 11:21:52 +00:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
let test_uris = vec![
|
|
|
|
"/",
|
|
|
|
"///",
|
|
|
|
"/v1/something",
|
|
|
|
"/v1/something/",
|
|
|
|
"/v1/something////",
|
|
|
|
"//v1//something",
|
|
|
|
"//v1//something//",
|
|
|
|
"/v2/something?query=test",
|
|
|
|
"/v2/something/?query=test",
|
|
|
|
"/v2/something////?query=test",
|
|
|
|
"//v2//something?query=test",
|
|
|
|
"//v2//something//?query=test",
|
|
|
|
];
|
2020-09-15 10:32:31 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
for uri in test_uris {
|
|
|
|
let req = TestRequest::with_uri(uri).to_request();
|
|
|
|
let res = call_service(&app, req).await;
|
|
|
|
assert!(res.status().is_success(), "Failed uri: {}", uri);
|
|
|
|
}
|
|
|
|
}
|
2020-08-19 11:21:52 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn ensure_root_trailing_slash_with_query() {
|
|
|
|
let app = init_service(
|
|
|
|
App::new()
|
|
|
|
.wrap(NormalizePath(TrailingSlash::Always))
|
|
|
|
.service(
|
|
|
|
web::resource("/")
|
2021-12-28 02:37:13 +00:00
|
|
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
2021-06-03 02:28:09 +00:00
|
|
|
.to(HttpResponse::Ok),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
.await;
|
2020-08-19 11:21:52 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
let test_uris = vec!["/?query=test", "//?query=test", "///?query=test"];
|
2020-08-19 11:21:52 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
for uri in test_uris {
|
|
|
|
let req = TestRequest::with_uri(uri).to_request();
|
|
|
|
let res = call_service(&app, req).await;
|
|
|
|
assert!(res.status().is_success(), "Failed uri: {}", uri);
|
|
|
|
}
|
2020-08-19 11:21:52 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 11:50:59 +00:00
|
|
|
#[actix_rt::test]
|
2020-12-23 00:19:20 +00:00
|
|
|
async fn keep_trailing_slash_unchanged() {
|
2021-02-07 01:00:40 +00:00
|
|
|
let app = init_service(
|
2020-09-25 11:50:59 +00:00
|
|
|
App::new()
|
|
|
|
.wrap(NormalizePath(TrailingSlash::MergeOnly))
|
|
|
|
.service(web::resource("/").to(HttpResponse::Ok))
|
|
|
|
.service(web::resource("/v1/something").to(HttpResponse::Ok))
|
2021-06-03 02:28:09 +00:00
|
|
|
.service(web::resource("/v1/").to(HttpResponse::Ok))
|
|
|
|
.service(
|
|
|
|
web::resource("/v2/something")
|
2021-12-28 02:37:13 +00:00
|
|
|
.guard(fn_guard(|ctx| ctx.head().uri.query() == Some("query=test")))
|
2021-06-03 02:28:09 +00:00
|
|
|
.to(HttpResponse::Ok),
|
|
|
|
),
|
2020-09-25 11:50:59 +00:00
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
|
|
|
let tests = vec![
|
|
|
|
("/", true), // root paths should still work
|
|
|
|
("/?query=test", true),
|
|
|
|
("///", true),
|
|
|
|
("/v1/something////", false),
|
|
|
|
("/v1/something/", false),
|
|
|
|
("//v1//something", true),
|
|
|
|
("/v1/", true),
|
|
|
|
("/v1", false),
|
|
|
|
("/v1////", true),
|
|
|
|
("//v1//", true),
|
|
|
|
("///v1", false),
|
2021-06-03 02:28:09 +00:00
|
|
|
("/v2/something?query=test", true),
|
|
|
|
("/v2/something/?query=test", false),
|
|
|
|
("/v2/something//?query=test", false),
|
|
|
|
("//v2//something?query=test", true),
|
2020-09-25 11:50:59 +00:00
|
|
|
];
|
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
for (uri, success) in tests {
|
|
|
|
let req = TestRequest::with_uri(uri).to_request();
|
2021-02-07 01:00:40 +00:00
|
|
|
let res = call_service(&app, req).await;
|
2021-06-03 02:28:09 +00:00
|
|
|
assert_eq!(res.status().is_success(), success, "Failed uri: {}", uri);
|
2020-09-25 11:50:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-05 16:19:45 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn no_path() {
|
|
|
|
let app = init_service(
|
|
|
|
App::new()
|
|
|
|
.wrap(NormalizePath::default())
|
|
|
|
.service(web::resource("/").to(HttpResponse::Ok)),
|
|
|
|
)
|
|
|
|
.await;
|
|
|
|
|
|
|
|
// This URI will be interpreted as an authority form, i.e. there is no path nor scheme
|
|
|
|
// (https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3)
|
|
|
|
let req = TestRequest::with_uri("eh").to_request();
|
|
|
|
let res = call_service(&app, req).await;
|
|
|
|
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn test_in_place_normalization() {
|
|
|
|
let srv = |req: ServiceRequest| {
|
2021-01-05 09:51:58 +00:00
|
|
|
assert_eq!("/v1/something", req.path());
|
2020-12-23 00:19:20 +00:00
|
|
|
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
2019-11-26 05:25:50 +00:00
|
|
|
};
|
|
|
|
|
2021-02-07 01:00:40 +00:00
|
|
|
let normalize = NormalizePath::default()
|
2019-11-26 05:25:50 +00:00
|
|
|
.new_transform(srv.into_service())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
let test_uris = vec![
|
|
|
|
"/v1//something////",
|
|
|
|
"///v1/something",
|
|
|
|
"//v1///something",
|
|
|
|
"/v1//something",
|
|
|
|
];
|
2020-06-17 09:54:20 +00:00
|
|
|
|
2021-06-03 02:28:09 +00:00
|
|
|
for uri in test_uris {
|
|
|
|
let req = TestRequest::with_uri(uri).to_srv_request();
|
|
|
|
let res = normalize.call(req).await.unwrap();
|
|
|
|
assert!(res.status().is_success(), "Failed uri: {}", uri);
|
|
|
|
}
|
2019-04-19 20:53:49 +00:00
|
|
|
}
|
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
#[actix_rt::test]
|
|
|
|
async fn should_normalize_nothing() {
|
2021-01-05 09:51:58 +00:00
|
|
|
const URI: &str = "/v1/something";
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2019-11-26 05:25:50 +00:00
|
|
|
let srv = |req: ServiceRequest| {
|
|
|
|
assert_eq!(URI, req.path());
|
2020-12-23 00:19:20 +00:00
|
|
|
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
2019-11-26 05:25:50 +00:00
|
|
|
};
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2021-02-07 01:00:40 +00:00
|
|
|
let normalize = NormalizePath::default()
|
2020-04-04 18:26:40 +00:00
|
|
|
.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());
|
|
|
|
}
|
|
|
|
|
2020-05-21 08:56:53 +00:00
|
|
|
#[actix_rt::test]
|
2020-12-23 00:19:20 +00:00
|
|
|
async fn should_normalize_no_trail() {
|
2020-04-04 18:26:40 +00:00
|
|
|
let srv = |req: ServiceRequest| {
|
2021-01-05 09:51:58 +00:00
|
|
|
assert_eq!("/v1/something", req.path());
|
2020-12-23 00:19:20 +00:00
|
|
|
ready(Ok(req.into_response(HttpResponse::Ok().finish())))
|
2020-04-04 18:26:40 +00:00
|
|
|
};
|
|
|
|
|
2021-02-07 01:00:40 +00:00
|
|
|
let normalize = NormalizePath::default()
|
2019-11-26 05:25:50 +00:00
|
|
|
.new_transform(srv.into_service())
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2019-04-19 20:53:49 +00:00
|
|
|
|
2021-01-05 09:51:58 +00:00
|
|
|
let req = TestRequest::with_uri("/v1/something/").to_srv_request();
|
2019-11-26 05:25:50 +00:00
|
|
|
let res = normalize.call(req).await.unwrap();
|
|
|
|
assert!(res.status().is_success());
|
2019-04-19 20:53:49 +00:00
|
|
|
}
|
|
|
|
}
|