From 3dfe1ec958a70556c689dde51f48aaa4576a16fc Mon Sep 17 00:00:00 2001 From: Adriano Di Luzio Date: Mon, 12 Feb 2024 19:29:37 +0100 Subject: [PATCH 1/5] Iteration #1: Handle serving from a sub-path --- src/env.rs | 8 ++++++++ src/highlight.rs | 2 +- src/id.rs | 2 +- src/main.rs | 11 ++++++++--- src/routes/assets.rs | 3 ++- src/routes/form.rs | 18 +++++++++++------- src/routes/json.rs | 6 +++++- src/routes/mod.rs | 12 ++++++++++++ src/routes/paste.rs | 4 +++- src/test_helpers.rs | 2 +- templates/base.html | 6 +++--- templates/burn.html | 2 +- templates/error.html | 2 +- templates/index.html | 2 +- templates/paste.html | 16 ++++++++-------- 15 files changed, 66 insertions(+), 30 deletions(-) diff --git a/src/env.rs b/src/env.rs index c69c0e2..58cf9f7 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,3 +1,4 @@ +use crate::routes::{base_path, DEFAULT_BASE_PATH}; use crate::{db, highlight}; use axum_extra::extract::cookie::Key; use std::env::VarError; @@ -12,6 +13,7 @@ pub struct Metadata<'a> { pub title: String, pub version: &'a str, pub highlight: &'a highlight::Data<'a>, + pub base_path: &'a str, } pub const DEFAULT_HTTP_TIMEOUT: Duration = Duration::from_secs(5); @@ -56,10 +58,16 @@ pub fn metadata() -> &'static Metadata<'static> { let version = env!("CARGO_PKG_VERSION"); let highlight = &highlight::data(); + let base_path = match base_url() { + Err(_) => DEFAULT_BASE_PATH, + Ok(base_url) => String::from(base_path(&base_url)).leak(), + }; + Metadata { title, version, highlight, + base_path, } }) } diff --git a/src/highlight.rs b/src/highlight.rs index 30ac368..c0f6656 100644 --- a/src/highlight.rs +++ b/src/highlight.rs @@ -73,7 +73,7 @@ pub struct Data<'a> { impl<'a> Css<'a> { fn new(name: &str, content: &'a str) -> Self { let name = format!( - "/{name}.{}.css", + "{name}.{}.css", hex::encode(Sha256::digest(content.as_bytes())) .get(0..16) .expect("at least 16 characters") diff --git a/src/id.rs b/src/id.rs index f8ba49a..313044f 100644 --- a/src/id.rs +++ b/src/id.rs @@ -27,7 +27,7 @@ impl Id { entry .extension .as_ref() - .map_or_else(|| format!("/{self}"), |ext| format!("/{self}.{ext}")) + .map_or_else(|| format!("{self}"), |ext| format!("{self}.{ext}")) } } diff --git a/src/main.rs b/src/main.rs index e3a3233..42c60e3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,8 +41,13 @@ impl FromRef for Key { } } -pub(crate) fn make_app(max_body_size: usize, timeout: Duration) -> Router { - Router::new().merge(routes::routes()).layer( +pub(crate) fn make_app( + max_body_size: usize, + timeout: Duration, + base_url: &Option, +) -> Router { + let base_path = routes::base_path(base_url); // TODO: must end with slash + Router::new().nest(base_path, routes::routes()).layer( ServiceBuilder::new() .layer(DefaultBodyLimit::max(max_body_size)) .layer(DefaultBodyLimit::disable()) @@ -101,7 +106,7 @@ async fn start() -> Result<(), Box> { tracing::debug!("caching {cache_size} paste highlights"); tracing::debug!("restricting maximum body size to {max_body_size} bytes"); - let service = make_app(max_body_size, timeout).with_state(state); + let service = make_app(max_body_size, timeout, &state.base_url).with_state(state); let listener = TcpListener::bind(&addr).await?; axum::serve(listener, service) diff --git a/src/routes/assets.rs b/src/routes/assets.rs index 0f7fe59..a43e990 100644 --- a/src/routes/assets.rs +++ b/src/routes/assets.rs @@ -33,9 +33,10 @@ fn favicon() -> impl IntoResponse { } pub fn routes() -> Router { + let style_name = &data().style.name; Router::new() .route("/favicon.png", get(|| async { favicon() })) - .route(&data().style.name, get(|| async { style_css() })) + .route(&format!("/{style_name}"), get(|| async { style_css() })) .route("/dark.css", get(|| async { dark_css() })) .route("/light.css", get(|| async { light_css() })) } diff --git a/src/routes/form.rs b/src/routes/form.rs index 9d23527..731ef4b 100644 --- a/src/routes/form.rs +++ b/src/routes/form.rs @@ -7,6 +7,8 @@ use axum_extra::extract::cookie::{Cookie, SignedCookieJar}; use rand::Rng; use serde::{Deserialize, Serialize}; +use super::base_path; + #[derive(Debug, Serialize, Deserialize)] pub struct Entry { pub text: String, @@ -62,16 +64,18 @@ pub async fn insert( let mut entry: write::Entry = entry.into(); entry.uid = Some(uid); - let url = id.to_url_path(&entry); + let base_path = base_path(&state.base_url); + let mut url = id.to_url_path(&entry); + let burn_after_reading = entry.burn_after_reading.unwrap_or(false); + if burn_after_reading { + url = format!("burn/{url}"); + } + + let url_with_base = format!("{base_path}{url}"); state.db.insert(id, entry).await?; let jar = jar.add(Cookie::new("uid", uid.to_string())); - - if burn_after_reading { - Ok((jar, Redirect::to(&format!("/burn{url}")))) - } else { - Ok((jar, Redirect::to(&url))) - } + Ok((jar, Redirect::to(&url_with_base))) } diff --git a/src/routes/json.rs b/src/routes/json.rs index 7958b04..a9868dd 100644 --- a/src/routes/json.rs +++ b/src/routes/json.rs @@ -7,6 +7,8 @@ use axum::Json; use rand::Rng; use serde::{Deserialize, Serialize}; +use super::base_path; + #[derive(Debug, Serialize, Deserialize)] pub struct Entry { pub text: String, @@ -47,8 +49,10 @@ pub async fn insert( .into(); let entry: write::Entry = entry.into(); - let path = id.to_url_path(&entry); + let base_path = base_path(&state.base_url); + let url = id.to_url_path(&entry); + let path = format!("{base_path}{url}"); state.db.insert(id, entry).await?; Ok(Json::from(RedirectResponse { path })) diff --git a/src/routes/mod.rs b/src/routes/mod.rs index f3146bb..9656fd9 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -2,12 +2,22 @@ use crate::pages::{Burn, Index}; use crate::AppState; use axum::extract::Path; use axum::routing::{get, Router}; +use url::Url; mod assets; mod form; mod json; pub(crate) mod paste; +pub const DEFAULT_BASE_PATH: &str = "/"; + +pub fn base_path(base_url: &Option) -> &str { + match base_url { + Some(base_url) => base_url.path(), + None => DEFAULT_BASE_PATH, + } +} + pub fn routes() -> Router { Router::new() .route("/", get(|| async { Index::default() }).post(paste::insert)) @@ -29,6 +39,8 @@ mod tests { use reqwest::header; use serde::Serialize; + // TODO: Add tests for base path + #[tokio::test] async fn unknown_paste() -> Result<(), Box> { let client = Client::new(make_app()?).await; diff --git a/src/routes/paste.rs b/src/routes/paste.rs index 6f4795e..dd84f67 100644 --- a/src/routes/paste.rs +++ b/src/routes/paste.rs @@ -16,6 +16,8 @@ use axum_extra::headers::{HeaderMapExt, HeaderValue}; use serde::Deserialize; use url::Url; +use super::base_path; + #[derive(Deserialize, Debug)] pub enum Format { #[serde(rename(deserialize = "raw"))] @@ -232,5 +234,5 @@ pub async fn delete( state.db.delete(id).await?; - Ok(Redirect::to("/")) + Ok(Redirect::to(base_path(&state.base_url))) } diff --git a/src/test_helpers.rs b/src/test_helpers.rs index c9d1f32..7f15697 100644 --- a/src/test_helpers.rs +++ b/src/test_helpers.rs @@ -64,5 +64,5 @@ pub(crate) fn make_app() -> Result> { base_url, }; - Ok(crate::make_app(4096, Duration::new(30, 0)).with_state(state)) + Ok(crate::make_app(4096, Duration::new(30, 0), &None).with_state(state)) } diff --git a/templates/base.html b/templates/base.html index 3f0805d..d2c169b 100644 --- a/templates/base.html +++ b/templates/base.html @@ -5,13 +5,13 @@ {{ meta.title }} - - + + {% block head %}{% endblock %}
- +