From 188ebaa30b71034fbbf57d2c9e5354697c024524 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Wed, 19 Jul 2023 16:18:13 +0200 Subject: [PATCH] Add add/remove_path_prefix fields to domain settings --- src/domain/manager.rs | 7 ++- src/domain/settings.rs | 105 +++++++++++++++++++++++++++++++ src/domain/store.rs | 4 ++ src/origin/proxy.rs | 6 +- src/service/http.rs | 10 ++- src/service/http/tpl/domain.html | 25 ++++++++ src/service/util.rs | 21 ++++++- 7 files changed, 166 insertions(+), 12 deletions(-) diff --git a/src/domain/manager.rs b/src/domain/manager.rs index c8e499c..e2ce452 100644 --- a/src/domain/manager.rs +++ b/src/domain/manager.rs @@ -252,7 +252,12 @@ impl Manager for ManagerImpl { return Err(unexpected::Error::from("origin is proxy, can't serve file").into()); } - let f = self.origin_store.get_file(&settings.origin_descr, path)?; + let path = settings.process_path(path); + + let f = self + .origin_store + .get_file(&settings.origin_descr, path.as_ref())?; + Ok(f) } diff --git a/src/domain/settings.rs b/src/domain/settings.rs index 791d95d..3112b11 100644 --- a/src/domain/settings.rs +++ b/src/domain/settings.rs @@ -1,6 +1,8 @@ use crate::error::unexpected::{self, Mappable}; use crate::origin; +use std::borrow; + use hex::ToHex; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -11,6 +13,9 @@ use sha2::{Digest, Sha256}; pub struct Settings { #[serde(flatten)] pub origin_descr: origin::Descr, + + pub remove_path_prefix: Option, + pub add_path_prefix: Option, } impl Settings { @@ -19,4 +24,104 @@ impl Settings { serde_json::to_writer(&mut h, self).or_unexpected()?; Ok(h.finalize().encode_hex::()) } + + fn remove_path_prefix<'path, 'prefix>( + path: borrow::Cow<'path, str>, + prefix: &'prefix str, + ) -> borrow::Cow<'path, str> { + if prefix.len() == 0 { + return path; + } + + let mut prefix = prefix.trim_end_matches('/'); + prefix = prefix.trim_start_matches('/'); + + let mut stripped_path = path.trim_start_matches('/'); + + if !stripped_path.starts_with(prefix) { + return path; + } + + stripped_path = stripped_path.strip_prefix(prefix).unwrap(); + if stripped_path.len() == 0 { + return borrow::Cow::Borrowed("/"); + } + + borrow::Cow::Owned(stripped_path.to_string()) + } + + fn add_path_prefix<'path, 'prefix>( + path: borrow::Cow<'path, str>, + prefix: &'prefix str, + ) -> borrow::Cow<'path, str> { + if prefix.len() == 0 { + return path; + } + + let mut prefix = prefix.trim_end_matches('/'); + prefix = prefix.trim_start_matches('/'); + + let mut prefixed_path = String::with_capacity(1 + prefix.len() + path.len()); + + prefixed_path.push('/'); + prefixed_path.push_str(prefix); + prefixed_path.push_str(path.as_ref()); + + borrow::Cow::Owned(prefixed_path) + } + + pub fn process_path<'a>(&self, path: &'a str) -> borrow::Cow<'a, str> { + let mut path = borrow::Cow::Borrowed(path); + + if let Some(ref prefix) = self.remove_path_prefix { + path = Self::remove_path_prefix(path, prefix); + } + + if let Some(ref prefix) = self.add_path_prefix { + path = Self::add_path_prefix(path, prefix); + } + + path + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::borrow; + + #[test] + fn remove_path_prefix() { + let assert_remove = |want: &str, path: &str, prefix: &str| { + assert_eq!( + want, + Settings::remove_path_prefix(borrow::Cow::Borrowed(path), prefix).as_ref(), + ) + }; + + assert_remove("/bar", "/foo/bar", "/foo"); + assert_remove("/foo/bar", "/foo/bar", "/baz"); + assert_remove("/bar", "/foo/bar", "/foo/"); + assert_remove("/bar", "/foo/bar", "/foo///"); + assert_remove("/", "/", "/"); + assert_remove("/", "/foo/bar/", "/foo/bar"); + assert_remove("/", "/foo/bar", "/foo/bar"); + assert_remove("/", "/foo/bar", "/foo/bar///"); + assert_remove("/bar", "/bar", ""); + } + + #[test] + fn add_path_prefix() { + let assert_add = |want: &str, path: &str, prefix: &str| { + assert_eq!( + want, + Settings::add_path_prefix(borrow::Cow::Borrowed(path), prefix).as_ref(), + ) + }; + + assert_add("/foo/bar", "/bar", "/foo"); + assert_add("/foo/", "/", "/foo"); + assert_add("/foo/", "/", "/foo///"); + assert_add("/bar", "/bar", ""); + } } diff --git a/src/domain/store.rs b/src/domain/store.rs index efe3a68..bc0297f 100644 --- a/src/domain/store.rs +++ b/src/domain/store.rs @@ -181,6 +181,8 @@ mod tests { url: "bar".to_string(), branch_name: "baz".to_string(), }, + remove_path_prefix: None, + add_path_prefix: None, }; assert!(matches!( @@ -203,6 +205,8 @@ mod tests { url: "BAR".to_string(), branch_name: "BAZ".to_string(), }, + remove_path_prefix: None, + add_path_prefix: None, }; store.set(&domain, &new_settings).expect("set"); diff --git a/src/origin/proxy.rs b/src/origin/proxy.rs index 9f6ac8c..057fa4a 100644 --- a/src/origin/proxy.rs +++ b/src/origin/proxy.rs @@ -30,7 +30,7 @@ pub async fn serve_http_request( mut req: hyper::Request, req_is_https: bool, ) -> unexpected::Result> { - let (proxy_url, request_http_headers) = if let origin::Descr::Proxy { + let (url, request_http_headers) = if let origin::Descr::Proxy { ref url, ref request_http_headers, } = settings.origin_descr @@ -67,12 +67,12 @@ pub async fn serve_http_request( .insert("x-forwarded-proto", HeaderValue::from_static("https")); } - match hyper_reverse_proxy::call(client_ip, proxy_url, req).await { + match hyper_reverse_proxy::call(client_ip, url, req).await { Ok(res) => Ok(res), // ProxyError doesn't actually implement Error :facepalm: so we have to format the error // manually Err(e) => Err(unexpected::Error::from( - format!("error while proxying to {proxy_url}: {e:?}").as_str(), + format!("error while proxying to {url}: {e:?}").as_str(), )), } } diff --git a/src/service/http.rs b/src/service/http.rs index 9e016df..a81e336 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -54,7 +54,7 @@ struct BasePresenter<'a, T> { } #[derive(Deserialize)] -struct DomainGetArgs { +struct DomainArgs { domain: domain::Name, } @@ -246,7 +246,7 @@ impl<'svc> Service { } } - fn domain_get(&self, args: DomainGetArgs) -> Response { + fn domain(&self, args: DomainArgs) -> Response { #[derive(Serialize)] struct Data { domain: domain::Name, @@ -485,10 +485,8 @@ impl<'svc> Service { self.render_page("/index.html", ()) } (form_method, "/domain.html") if form_method == config_form_method => { - self.with_query_req(&req, body, |args: DomainGetArgs| async { - self.domain_get(args) - }) - .await + self.with_query_req(&req, body, |args: DomainArgs| async { self.domain(args) }) + .await } (form_method, "/domain_init.html") if form_method == config_form_method => { self.with_query_req(&req, body, |args: DomainInitArgs| async { diff --git a/src/service/http/tpl/domain.html b/src/service/http/tpl/domain.html index 42bd611..9ad1a58 100644 --- a/src/service/http/tpl/domain.html +++ b/src/service/http/tpl/domain.html @@ -49,6 +49,31 @@ automatically updated too!

+
+ Advanced Settings +

+ +

+ +

+ +

+
+ diff --git a/src/service/util.rs b/src/service/util.rs index e4eec2f..7e5929b 100644 --- a/src/service/util.rs +++ b/src/service/util.rs @@ -1,10 +1,12 @@ use std::convert::TryFrom; use serde::{Deserialize, Serialize}; +use serde_with::{serde_as, NoneAsEmptyString}; use crate::{domain, error::unexpected, origin}; -#[derive(Serialize, Deserialize, Default)] +#[serde_as] +#[derive(Serialize, Deserialize, Default, Debug)] pub struct FlatDomainSettings { domain_setting_origin_descr_kind: String, @@ -12,6 +14,14 @@ pub struct FlatDomainSettings { domain_setting_origin_descr_git_branch_name: Option, domain_setting_origin_descr_proxy_url: Option, + + #[serde(default)] + #[serde_as(as = "NoneAsEmptyString")] + domain_setting_remove_path_prefix: Option, + + #[serde(default)] + #[serde_as(as = "NoneAsEmptyString")] + domain_setting_add_path_prefix: Option, } impl TryFrom for domain::Settings { @@ -31,7 +41,11 @@ impl TryFrom for domain::Settings { _ => Err("invalid domain_setting_origin_descr_kind".to_string()), }?; - Ok(Self { origin_descr }) + Ok(Self { + origin_descr, + remove_path_prefix: v.domain_setting_remove_path_prefix, + add_path_prefix: v.domain_setting_add_path_prefix, + }) } } @@ -54,6 +68,9 @@ impl TryFrom for FlatDomainSettings { } } + res.domain_setting_remove_path_prefix = v.remove_path_prefix; + res.domain_setting_add_path_prefix = v.add_path_prefix; + Ok(res) } }