diff --git a/meilisearch-http/src/helpers/mod.rs b/meilisearch-http/src/helpers/mod.rs index 83a206bc8..0062152b1 100644 --- a/meilisearch-http/src/helpers/mod.rs +++ b/meilisearch-http/src/helpers/mod.rs @@ -1,6 +1,6 @@ pub mod authentication; pub mod meilisearch; -pub mod normalize_slashes; +pub mod normalize_path; pub use authentication::Authentication; -pub use normalize_slashes::NormalizeSlashes; +pub use normalize_path::NormalizePath; diff --git a/meilisearch-http/src/helpers/normalize_path.rs b/meilisearch-http/src/helpers/normalize_path.rs new file mode 100644 index 000000000..e669b9d94 --- /dev/null +++ b/meilisearch-http/src/helpers/normalize_path.rs @@ -0,0 +1,86 @@ +/// From https://docs.rs/actix-web/3.0.0-alpha.2/src/actix_web/middleware/normalize.rs.html#34 +use actix_http::Error; +use actix_service::{Service, Transform}; +use actix_web::{ + dev::ServiceRequest, + dev::ServiceResponse, + http::uri::{PathAndQuery, Uri}, +}; +use futures::future::{ok, Ready}; +use regex::Regex; +use std::task::{Context, Poll}; +pub struct NormalizePath; + +impl Transform for NormalizePath +where + S: Service, Error = Error>, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type InitError = (); + type Transform = NormalizePathNormalization; + type Future = Ready>; + + fn new_transform(&self, service: S) -> Self::Future { + ok(NormalizePathNormalization { + service, + merge_slash: Regex::new("//+").unwrap(), + }) + } +} + +pub struct NormalizePathNormalization { + service: S, + merge_slash: Regex, +} + +impl Service for NormalizePathNormalization +where + S: Service, Error = Error>, + S::Future: 'static, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + 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() + "/"; + + if self.merge_slash.find(&path).is_some() { + // normalize multiple /'s to one / + let path = self.merge_slash.replace_all(&path, "/"); + + let path = if path.len() > 1 { + path.trim_end_matches('/') + } else { + &path + }; + + 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::Bytes::from(format!("{}?{}", path, q)) + } else { + bytes::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; + } + + self.service.call(req) + } +} diff --git a/meilisearch-http/src/helpers/normalize_slashes.rs b/meilisearch-http/src/helpers/normalize_slashes.rs deleted file mode 100644 index 2e19cd889..000000000 --- a/meilisearch-http/src/helpers/normalize_slashes.rs +++ /dev/null @@ -1,88 +0,0 @@ -/// -/// This middleware normalizes slashes in paths -/// * consecutive instances of `/` get collapsed into one `/` -/// * any ending `/` is removed. -/// Original source from: https://gitlab.com/snippets/1884466 -/// -/// Ex: -/// /this///url/ -/// becomes : /this/url -/// -use actix_service::{Service, Transform}; -use actix_web::{ - dev::ServiceRequest, - dev::ServiceResponse, - http::uri::{PathAndQuery, Uri}, - Error as ActixError, -}; -use futures::future::{ok, Ready}; -use regex::Regex; -use std::task::{Context, Poll}; - -pub struct NormalizeSlashes; - -impl Transform for NormalizeSlashes -where - S: Service, Error = ActixError>, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = ActixError; - type InitError = (); - type Transform = SlashNormalization; - type Future = Ready>; - - fn new_transform(&self, service: S) -> Self::Future { - ok(SlashNormalization { service }) - } -} - -pub struct SlashNormalization { - service: S, -} - -impl Service for SlashNormalization -where - S: Service, Error = ActixError>, - S::Future: 'static, -{ - type Request = ServiceRequest; - type Response = ServiceResponse; - type Error = ActixError; - type Future = S::Future; - - fn poll_ready(&mut self, cx: &mut Context) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let head = req.head(); - - let path = head.uri.path(); - let original_len = path.len(); - let slash_regex = Regex::new("//+").unwrap(); - let new_path = slash_regex.replace_all(path, "/"); - let new_path = new_path.trim_end_matches("/"); - - if original_len != new_path.len() { - let mut parts = head.uri.clone().into_parts(); - - let path = match parts.path_and_query.as_ref().map(|pq| pq.query()).flatten() { - Some(q) => bytes::Bytes::from(format!("{}?{}", new_path, q)), - None => bytes::Bytes::from(new_path.to_string()), - }; - - if let Ok(pq) = PathAndQuery::from_maybe_shared(path) { - parts.path_and_query = Some(pq); - - if let Ok(uri) = Uri::from_parts(parts) { - req.match_info_mut().get_mut().update(&uri); - req.head_mut().uri = uri; - } - } - } - - self.service.call(req) - } -} diff --git a/meilisearch-http/src/main.rs b/meilisearch-http/src/main.rs index f6df9d2f3..c5ac6e933 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -4,7 +4,7 @@ use actix_cors::Cors; use actix_web::{middleware, HttpServer}; use main_error::MainError; use meilisearch_http::data::Data; -use meilisearch_http::helpers::NormalizeSlashes; +use meilisearch_http::helpers::NormalizePath; use meilisearch_http::option::Opt; use meilisearch_http::{create_app, index_update_callback}; use structopt::StructOpt; @@ -72,7 +72,7 @@ async fn main() -> Result<(), MainError> { ) .wrap(middleware::Logger::default()) .wrap(middleware::Compress::default()) - .wrap(NormalizeSlashes) + .wrap(NormalizePath) }) .bind(opt.http_addr)? .run() diff --git a/meilisearch-http/tests/common.rs b/meilisearch-http/tests/common.rs index b8401f58b..f9800ac10 100644 --- a/meilisearch-http/tests/common.rs +++ b/meilisearch-http/tests/common.rs @@ -7,7 +7,7 @@ use actix_web::{http::StatusCode, test}; use meilisearch_core::DatabaseOptions; use meilisearch_http::data::Data; use meilisearch_http::option::Opt; -use meilisearch_http::helpers::NormalizeSlashes; +use meilisearch_http::helpers::NormalizePath; use tempdir::TempDir; use tokio::time::delay_for; @@ -128,7 +128,7 @@ impl Server { pub async fn get_request(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("get_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizeSlashes)).await; + let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::get().uri(url).to_request(); let res = test::call_service(&mut app, req).await; @@ -142,7 +142,7 @@ impl Server { pub async fn post_request(&mut self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("post_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizeSlashes)).await; + let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::post() .uri(url) @@ -171,7 +171,7 @@ impl Server { pub async fn put_request(&mut self, url: &str, body: Value) -> (Value, StatusCode) { eprintln!("put_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizeSlashes)).await; + let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::put() .uri(url) @@ -199,7 +199,7 @@ impl Server { pub async fn delete_request(&mut self, url: &str) -> (Value, StatusCode) { eprintln!("delete_request: {}", url); - let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizeSlashes)).await; + let mut app = test::init_service(meilisearch_http::create_app(&self.data).wrap(NormalizePath)).await; let req = test::TestRequest::delete().uri(url).to_request(); let res = test::call_service(&mut app, req).await;