index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2022-04-27 23:41:31.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2022-04-27 23:44:19.0 +00:00:00
commit
62ebe52f7a255db0e3f0a322b3980e58ed6104f4 [patch]
tree
301db9c3d56cfede126d0e77c04dfa22072f0bf3
parent
eb540e554f0997a95f0068ed750e6e8f81a47938
download
62ebe52f7a255db0e3f0a322b3980e58ed6104f4.tar.gz

feat: custom http headers support via config file

resolves #30 #100

Diff

 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(-)

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<Vec<HeadersOpt>>,
    resp: &mut Response<Body>,
) {
    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<AdvancedOpts>,
}

/// 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<Vec<Headers>>,
}

/// 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<Vec<HeadersOpt>>,
}

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<Advanced>,
    pub advanced: Option<AdvancedOpts>,
}

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<Advanced> = None;
        let mut settings_advanced: Option<AdvancedOpts> = 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<HeadersOpt> = 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"