From 7a8e64be30ee9a9b4c84581916af570f3ce4857a Mon Sep 17 00:00:00 2001 From: qdequele Date: Thu, 7 May 2020 12:28:41 +0200 Subject: [PATCH] add normalize_slashes middleware --- Cargo.lock | 2 + meilisearch-http/Cargo.toml | 2 + meilisearch-http/src/data.rs | 2 +- .../src/helpers/authentication.rs | 1 - meilisearch-http/src/helpers/mod.rs | 2 + .../src/helpers/normalize_slashes.rs | 87 +++++++++++++++++++ meilisearch-http/src/main.rs | 2 + meilisearch-http/src/option.rs | 2 +- meilisearch-http/tests/common.rs | 4 +- 9 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 meilisearch-http/src/helpers/normalize_slashes.rs diff --git a/Cargo.lock b/Cargo.lock index 4b718210a..26b4bb72c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1641,6 +1641,7 @@ dependencies = [ "actix-web", "actix-web-macros", "assert-json-diff", + "bytes 0.5.4", "chrono", "crossbeam-channel", "env_logger", @@ -1660,6 +1661,7 @@ dependencies = [ "pretty-bytes", "rand 0.7.3", "sentry", + "regex", "serde", "serde_json", "serde_qs", diff --git a/meilisearch-http/Cargo.toml b/meilisearch-http/Cargo.toml index 1f891f091..8e4876397 100644 --- a/meilisearch-http/Cargo.toml +++ b/meilisearch-http/Cargo.toml @@ -24,6 +24,7 @@ actix-rt = "1" actix-service = "1.0.5" actix-web = "2" actix-web-macros = "0.1.0" +bytes = "0.5.4" chrono = { version = "0.4.11", features = ["serde"] } crossbeam-channel = "0.4.2" env_logger = "0.7.1" @@ -39,6 +40,7 @@ meilisearch-tokenizer = {path = "../meilisearch-tokenizer", version = "0.10.1"} mime = "0.3.16" pretty-bytes = "0.2.2" rand = "0.7.3" +regex = "1.3.6" serde = { version = "1.0.105", features = ["derive"] } serde_json = { version = "1.0.50", features = ["preserve_order"] } serde_qs = "0.5.2" diff --git a/meilisearch-http/src/data.rs b/meilisearch-http/src/data.rs index da171b53d..056efe73d 100644 --- a/meilisearch-http/src/data.rs +++ b/meilisearch-http/src/data.rs @@ -134,7 +134,7 @@ impl Data { let db_opt = DatabaseOptions { main_map_size: opt.main_map_size, - update_map_size: opt.update_map_size + update_map_size: opt.update_map_size, }; let db = Arc::new(Database::open_or_create(opt.db_path, db_opt).unwrap()); diff --git a/meilisearch-http/src/helpers/authentication.rs b/meilisearch-http/src/helpers/authentication.rs index f42898683..894718d53 100644 --- a/meilisearch-http/src/helpers/authentication.rs +++ b/meilisearch-http/src/helpers/authentication.rs @@ -17,7 +17,6 @@ pub enum Authentication { Admin, } - impl Transform for Authentication where S: Service, Error = Error>, diff --git a/meilisearch-http/src/helpers/mod.rs b/meilisearch-http/src/helpers/mod.rs index 996333141..83a206bc8 100644 --- a/meilisearch-http/src/helpers/mod.rs +++ b/meilisearch-http/src/helpers/mod.rs @@ -1,4 +1,6 @@ pub mod authentication; pub mod meilisearch; +pub mod normalize_slashes; pub use authentication::Authentication; +pub use normalize_slashes::NormalizeSlashes; diff --git a/meilisearch-http/src/helpers/normalize_slashes.rs b/meilisearch-http/src/helpers/normalize_slashes.rs new file mode 100644 index 000000000..ed2650690 --- /dev/null +++ b/meilisearch-http/src/helpers/normalize_slashes.rs @@ -0,0 +1,87 @@ +/// +/// This middleware normalizes slashes in paths +/// * consecutive instances of `/` get collapsed into one `/` +/// * any ending `/` is removed. +/// +/// 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(format!("{}", new_path)) + }; + + 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 46d8ff96d..7d48fe12b 100644 --- a/meilisearch-http/src/main.rs +++ b/meilisearch-http/src/main.rs @@ -5,6 +5,7 @@ use actix_web::{middleware, HttpServer}; use log::info; use main_error::MainError; use meilisearch_http::data::Data; +use meilisearch_http::helpers::NormalizeSlashes; use meilisearch_http::option::Opt; use meilisearch_http::{create_app, index_update_callback}; use structopt::StructOpt; @@ -72,6 +73,7 @@ async fn main() -> Result<(), MainError> { ) .wrap(middleware::Logger::default()) .wrap(middleware::Compress::default()) + .wrap(NormalizeSlashes) }) .bind(opt.http_addr)? .run() diff --git a/meilisearch-http/src/option.rs b/meilisearch-http/src/option.rs index 2c93f320a..d9fe3599c 100644 --- a/meilisearch-http/src/option.rs +++ b/meilisearch-http/src/option.rs @@ -33,5 +33,5 @@ pub struct Opt { /// The maximum size, in bytes, of the update lmdb database directory #[structopt(long, env = "MEILI_UPDATE_MAP_SIZE", default_value = "107374182400")] // 100GB - pub update_map_size: usize + pub update_map_size: usize, } diff --git a/meilisearch-http/tests/common.rs b/meilisearch-http/tests/common.rs index 731d69987..0e02b6707 100644 --- a/meilisearch-http/tests/common.rs +++ b/meilisearch-http/tests/common.rs @@ -4,9 +4,9 @@ use serde_json::{json, Value}; use std::time::Duration; use actix_web::{http::StatusCode, test}; +use meilisearch_core::DatabaseOptions; use meilisearch_http::data::Data; use meilisearch_http::option::Opt; -use meilisearch_core::DatabaseOptions; use tempdir::TempDir; use tokio::time::delay_for; @@ -28,7 +28,7 @@ impl Server { env: "development".to_owned(), no_analytics: true, main_map_size: default_db_options.main_map_size, - update_map_size: default_db_options.update_map_size + update_map_size: default_db_options.update_map_size, }; let data = Data::new(opt.clone());