From 62ebe52f7a255db0e3f0a322b3980e58ed6104f4 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Thu, 28 Apr 2022 01:41:31 +0200 Subject: [PATCH] feat: custom http headers support via config file resolves #30 #100 --- Cargo.lock | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/custom_headers.rs | 22 ++++++++++++++++++++++ src/handler.rs | 14 +++++++++++--- src/lib.rs | 1 + src/server.rs | 5 +++-- src/settings/file.rs | 6 +++--- src/settings/mod.rs | 36 +++++++++++++++++++++++++++++++----- tests/toml/config.toml | 22 +++++++++++----------- 9 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 src/custom_headers.rs diff --git a/Cargo.lock b/Cargo.lock index 34a1c4e..57bfade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,6 +9,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] name = "alloc-no-stdlib" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -123,6 +132,15 @@ dependencies = [ ] [[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] name = "bumpalo" version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -312,6 +330,20 @@ dependencies = [ ] [[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", + "serde", +] + +[[package]] name = "h2" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -732,6 +764,23 @@ dependencies = [ ] [[package]] +name = "regex" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -902,6 +951,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", + "globset", "headers", "http", "http-serde", diff --git a/Cargo.toml b/Cargo.toml index f8727ab..ede34ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ serde = { version = "1.0", default-features = false, features = ["derive"] } serde_ignored = "0.1" toml = "0.5" http-serde = "1.1" +globset = { version = "0.4", features = ["serde1"] } [target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.tikv-jemallocator] version = "0.4" diff --git a/src/custom_headers.rs b/src/custom_headers.rs new file mode 100644 index 0000000..a60b864 --- /dev/null +++ b/src/custom_headers.rs @@ -0,0 +1,22 @@ +use hyper::{Body, Response}; + +use crate::settings::HeadersOpt; + +/** Append custom HTTP headers to current response. */ +pub fn append_headers( + uri: &str, + multiple_headers: &Option>, + resp: &mut Response, +) { + if let Some(multiple_headers) = multiple_headers { + for headers_entry in multiple_headers.iter() { + // Match header glob pattern against request uri + if headers_entry.source.is_match(uri) { + // Add/update headers if uri matches + for (name, value) in &headers_entry.headers { + resp.headers_mut().insert(name, value.to_owned()); + } + } + } + } +} diff --git a/src/handler.rs b/src/handler.rs index 840db08..dadbbb1 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -2,13 +2,13 @@ use hyper::{header::WWW_AUTHENTICATE, Body, Method, Request, Response, StatusCod use std::{future::Future, path::PathBuf, sync::Arc}; use crate::{ - basic_auth, compression, control_headers, cors, error_page, fallback_page, security_headers, - static_files, + basic_auth, compression, control_headers, cors, custom_headers, error_page, fallback_page, + security_headers, settings::AdvancedOpts, static_files, Error, Result, }; -use crate::{Error, Result}; /// It defines options for a request handler. pub struct RequestHandlerOpts { + // General options pub root_dir: PathBuf, pub compression: bool, pub dir_listing: bool, @@ -20,6 +20,9 @@ pub struct RequestHandlerOpts { pub page50x: String, pub page_fallback: String, pub basic_auth: String, + + // Advanced options + pub advanced_opts: Option, } /// It defines the main request handler used by the Hyper service request. @@ -154,6 +157,11 @@ impl RequestHandler { security_headers::append_headers(&mut resp); } + // Add/update custom headers + if let Some(advanced) = &self.opts.advanced_opts { + custom_headers::append_headers(uri_path, &advanced.headers, &mut resp) + } + Ok(resp) } Err(status) => { diff --git a/src/lib.rs b/src/lib.rs index 7061658..49c9ddc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod basic_auth; pub mod compression; pub mod control_headers; pub mod cors; +pub mod custom_headers; pub mod error_page; pub mod fallback_page; pub mod handler; diff --git a/src/server.rs b/src/server.rs index 6e60c67..372c17a 100644 --- a/src/server.rs +++ b/src/server.rs @@ -54,8 +54,8 @@ impl Server { async fn start_server(self) -> Result { let general = self.opts.general; - // TODO: handle advaced options - // let advanced = self.opts.advanced; + // TODO: handle advanced options + let advanced_opts = self.opts.advanced; // Logging system initialization let log_level = &general.log_level.to_lowercase(); @@ -159,6 +159,7 @@ impl Server { page50x, page_fallback, basic_auth, + advanced_opts, }), }); diff --git a/src/settings/file.rs b/src/settings/file.rs index 48f1dee..bff1623 100644 --- a/src/settings/file.rs +++ b/src/settings/file.rs @@ -33,7 +33,7 @@ impl LogLevel { #[serde(rename_all = "kebab-case")] pub struct Headers { pub source: String, - #[serde(with = "http_serde::header_map")] + #[serde(rename(deserialize = "headers"), with = "http_serde::header_map")] pub headers: HeaderMap, } @@ -42,11 +42,11 @@ pub struct Headers { #[serde(rename_all = "kebab-case")] pub struct Advanced { // Headers - #[serde(rename(deserialize = "headers"))] pub headers: Option>, } -/// General server options available configuration file mode. +/// General server options available in configuration file mode. +/// Note that the `--config-file` option is excluded from itself. #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "kebab-case")] pub struct General { diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 24b6827..33474d9 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -1,19 +1,29 @@ +use globset::{Glob, GlobMatcher}; +use headers::HeaderMap; use structopt::StructOpt; use crate::{Context, Result}; mod cli; -mod file; +pub mod file; use cli::General; -use file::Advanced; + +pub struct AdvancedOpts { + pub headers: Option>, +} + +pub struct HeadersOpt { + pub source: GlobMatcher, + pub headers: HeaderMap, +} /// The Server CLI and File settings. pub struct Settings { /// General server options pub general: General, /// Advanced server options - pub advanced: Option, + pub advanced: Option, } impl Settings { @@ -46,7 +56,7 @@ impl Settings { let mut page_fallback = opts.page_fallback.to_owned(); // Define the advanced file options - let mut settings_advanced: Option = None; + let mut settings_advanced: Option = None; // Handle config file options and set them when available // NOTE: All config file based options shouldn't be mandatory, therefore `Some()` wrapped @@ -127,7 +137,23 @@ impl Settings { } } - settings_advanced = settings.advanced + if let Some(advanced) = settings.advanced { + if let Some(multiple_headers) = advanced.headers { + let mut headers_vec: Vec = Vec::new(); + + // Compile glob patterns for header sources + for headers_entry in multiple_headers.iter() { + let source = Glob::new(&headers_entry.source)?.compile_matcher(); + headers_vec.push(HeadersOpt { + source, + headers: headers_entry.headers.to_owned(), + }); + } + settings_advanced = Some(AdvancedOpts { + headers: Some(headers_vec), + }); + } + } } } diff --git a/tests/toml/config.toml b/tests/toml/config.toml index 71afae2..f5392e0 100644 --- a/tests/toml/config.toml +++ b/tests/toml/config.toml @@ -6,7 +6,7 @@ port = 8087 root = "docker/public" #### Logging -log-level = "debug" +log-level = "trace" #### Cache Control headers cache-control-headers = true @@ -50,19 +50,19 @@ page-fallback = "" #### HTTP Headers customization #### a. Oneline version -[[headers]] -source = "**/*.@(eot|otf|ttf|ttc|woff|font.css)" +[[advanced.headers]] +source = "**/*.{js,css}" headers = { Access-Control-Allow-Origin = "*", X-XSS-PROTECTION = "1; mode=block" } -#### b. Multiline version -[[headers]] - source = "**/*.@(jpg|jpeg|gif|png)" -[headers.headers] - Cache-Control = "max-age=7200" +# #### b. Multiline version +[[advanced.headers]] + source = "*.html" + [advanced.headers.headers] + Cache-Control = "public, max-age=36000" Content-Security-Policy = "frame-ancestors 'self'" + Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload" #### c. Multiline version with explicit key (dotted) -[[headers]] -source = "404.html" -headers.Cache-Control = "max-age=300" +[[advanced.headers]] +source = "**/*.{jpg,jpeg,png,ico,gif}" headers.Strict-Transport-Security = "max-age=63072000; includeSubDomains; preload" -- libgit2 1.7.2