From 5163564817b5e9ee058a1bf83e680a13bd983fc2 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Wed, 6 Jul 2022 21:32:46 +0200 Subject: [PATCH] feat: rewrites with pattern matching support --- src/handler.rs | 13 ++++++++++--- src/lib.rs | 1 + src/rewrites.rs | 19 +++++++++++++++++++ src/settings/file.rs | 11 ++++++++++- src/settings/mod.rs | 40 ++++++++++++++++++++++++++++++++++++++-- tests/toml/config.toml | 16 ++++++++++++---- 6 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 src/rewrites.rs diff --git a/src/handler.rs b/src/handler.rs index dbb7905..a749670 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -3,7 +3,7 @@ use std::{future::Future, net::SocketAddr, path::PathBuf, sync::Arc}; use crate::{ basic_auth, compression, control_headers, cors, custom_headers, error_page, fallback_page, - security_headers, settings::Advanced, static_files, Error, Result, + rewrites, security_headers, settings::Advanced, static_files, Error, Result, }; /// It defines options for a request handler. @@ -43,7 +43,7 @@ impl RequestHandler { let uri = req.uri(); let root_dir = &self.opts.root_dir; - let uri_path = uri.path(); + let mut uri_path = uri.path(); let uri_query = uri.query(); let dir_listing = self.opts.dir_listing; let dir_listing_order = self.opts.dir_listing_order; @@ -65,7 +65,7 @@ impl RequestHandler { ); async move { - // Check for disallowed HTTP methods and reject request accordently + // Check for disallowed HTTP methods and reject requests accordingly if !(method == Method::GET || method == Method::HEAD || method == Method::OPTIONS) { return error_page::error_response( uri, @@ -128,6 +128,13 @@ impl RequestHandler { } } + // Rewrites + if let Some(advanced) = &self.opts.advanced_opts { + if let Some(uri) = rewrites::rewrite_uri_path(uri_path, &advanced.rewrites) { + uri_path = uri + } + } + // Static files match static_files::handle( method, diff --git a/src/lib.rs b/src/lib.rs index 9caa7ea..8b72a33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,6 +19,7 @@ pub mod fallback_page; pub mod handler; pub mod helpers; pub mod logger; +pub mod rewrites; pub mod security_headers; pub mod server; pub mod service; diff --git a/src/rewrites.rs b/src/rewrites.rs new file mode 100644 index 0000000..a71eee3 --- /dev/null +++ b/src/rewrites.rs @@ -0,0 +1,19 @@ +use crate::settings::Rewrites; + +/// It returns a rewrite's destination path if the current request uri +/// matches againt the provided rewrites array. +pub fn rewrite_uri_path<'a>( + uri_path: &'a str, + rewrites_opts_vec: &'a Option>, +) -> Option<&'a str> { + if let Some(rewrites_vec) = rewrites_opts_vec { + for rewrites_entry in rewrites_vec.iter() { + // Match source glob pattern against request uri path + if rewrites_entry.source.is_match(uri_path) { + return Some(rewrites_entry.destination.as_str()); + } + } + } + + None +} diff --git a/src/settings/file.rs b/src/settings/file.rs index 22be141..d54f356 100644 --- a/src/settings/file.rs +++ b/src/settings/file.rs @@ -7,7 +7,7 @@ use std::{collections::BTreeSet, path::PathBuf}; use crate::{helpers, Context, Result}; -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub enum LogLevel { Error, @@ -37,12 +37,21 @@ pub struct Headers { pub headers: HeaderMap, } +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "kebab-case")] +pub struct Rewrites { + pub source: String, + pub destination: String, +} + /// Advanced server options only available in configuration file mode. #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct Advanced { // Headers pub headers: Option>, + // Rewrites + pub rewrites: Option>, } /// General server options available in configuration file mode. diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 4b793de..1806a02 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -20,9 +20,18 @@ pub struct Headers { pub headers: HeaderMap, } +/// The `Rewrites` file options. +pub struct Rewrites { + /// Source pattern glob matcher + pub source: GlobMatcher, + /// A local file that must exist + pub destination: String, +} + /// The `advanced` file options. pub struct Advanced { pub headers: Option>, + pub rewrites: Option>, } /// The full server CLI and File options. @@ -81,7 +90,7 @@ impl Settings { config_file = Some(path_resolved); - // Assign the corresponding file option values + // File-based "general" options if let Some(general) = settings.general { if let Some(v) = general.host { host = v @@ -151,7 +160,7 @@ impl Settings { } } - // Prepare the "advanced" options + // File-based "advanced" options if let Some(advanced) = settings.advanced { // 1. Custom HTTP headers assignment let headers_entries = match advanced.headers { @@ -179,8 +188,35 @@ impl Settings { _ => None, }; + // 2. Rewrites assignment + let rewrites_entries = match advanced.rewrites { + Some(rewrites_entries) => { + let mut rewrites_vec: Vec = Vec::new(); + + // Compile a glob pattern for each rewrite sources entry + for rewrites_entry in rewrites_entries.iter() { + let source = Glob::new(&rewrites_entry.source) + .with_context(|| { + format!( + "can not compile glob pattern for rewrite source: {}", + &rewrites_entry.source + ) + })? + .compile_matcher(); + + rewrites_vec.push(Rewrites { + source, + destination: rewrites_entry.destination.to_owned(), + }); + } + Some(rewrites_vec) + } + _ => None, + }; + settings_advanced = Some(Advanced { headers: headers_entries, + rewrites: rewrites_entries, }); } } diff --git a/tests/toml/config.toml b/tests/toml/config.toml index 47ff090..838c37c 100644 --- a/tests/toml/config.toml +++ b/tests/toml/config.toml @@ -2,7 +2,7 @@ #### Address & Root dir host = "::" -port = 8087 +port = 8787 root = "docker/public" #### Logging @@ -45,9 +45,6 @@ grace-period = 0 #### Page fallback for 404s page-fallback = "" -#### Page fallback for 404s -page-fallback = "" - #### Log request Remote Address if available log-remote-address = false @@ -79,3 +76,14 @@ Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload" [[advanced.headers]] source = "**/*.{jpg,jpeg,png,ico,gif}" headers.Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload" + + +### URL Rewrites + +[[advanced.rewrites]] +source = "**/*.{png,ico,gif}" +destination = "/assets/favicon.ico" + +[[advanced.rewrites]] +source = "**/*.{jpg,jpeg}" +destination = "/images/nomad.png" -- libgit2 1.7.2