Skip to content

Commit 452a61b

Browse files
committed
Support path prefix in reverse proxy scenarios.
1 parent aac3d94 commit 452a61b

15 files changed

Lines changed: 62 additions & 29 deletions

File tree

crates/wastebin_server/src/handlers/download.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use axum::http::header;
33
use axum::response::{IntoResponse, Response};
44
use axum_extra::headers::HeaderValue;
55

6-
use crate::Page;
76
use crate::cache::Key;
87
use crate::handlers::extract::{Password, Theme};
98
use crate::handlers::html::{ErrorResponse, PasswordInput, make_error};
9+
use crate::{PATH_PREFIX, Page};
1010
use wastebin_core::db::read::{Data, Entry};
1111
use wastebin_core::db::{self, Database};
1212

@@ -27,6 +27,7 @@ pub async fn get(
2727
Ok(get_download(&key, data).into_response())
2828
}
2929
Err(db::Error::NoPassword) => Ok(PasswordInput {
30+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
3031
page: page.clone(),
3132
theme: theme.clone(),
3233
id: key.id.to_string(),

crates/wastebin_server/src/handlers/html/burn.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::cache::Key;
66
use crate::handlers::extract::Theme;
77
use crate::handlers::html::qr::{code_from, dark_modules};
88
use crate::handlers::html::{ErrorResponse, make_error};
9-
use crate::{Error, Page};
9+
use crate::{Error, PATH_PREFIX, Page};
1010

1111
/// GET handler for the burn page.
1212
pub async fn get(
@@ -25,6 +25,7 @@ pub async fn get(
2525
.map_err(Error::from)??;
2626

2727
Ok(Burn {
28+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
2829
page: page.clone(),
2930
key,
3031
code,
@@ -39,6 +40,7 @@ pub async fn get(
3940
#[derive(Template, WebTemplate)]
4041
#[template(path = "burn.html", escape = "none")]
4142
pub(crate) struct Burn {
43+
path_prefix: String,
4244
page: Page,
4345
key: Key,
4446
code: qrcodegen::QrCode,

crates/wastebin_server/src/handlers/html/index.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use askama::Template;
22
use askama_web::WebTemplate;
33
use axum::extract::State;
44

5-
use crate::{Highlighter, Page, handlers::extract::Theme};
5+
use crate::{Highlighter, PATH_PREFIX, Page, handlers::extract::Theme};
66

77
/// GET handler for the index page.
88
pub async fn get(
@@ -11,6 +11,7 @@ pub async fn get(
1111
theme: Option<Theme>,
1212
) -> Index {
1313
Index {
14+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
1415
page,
1516
theme,
1617
highlighter,
@@ -21,6 +22,7 @@ pub async fn get(
2122
#[derive(Template, WebTemplate)]
2223
#[template(path = "index.html")]
2324
pub(crate) struct Index {
25+
path_prefix: String,
2426
page: Page,
2527
theme: Option<Theme>,
2628
highlighter: Highlighter,

crates/wastebin_server/src/handlers/html/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ use askama::Template;
77
use askama_web::WebTemplate;
88
use axum::http::StatusCode;
99

10-
use crate::Page;
1110
use crate::handlers::extract::Theme;
11+
use crate::{PATH_PREFIX, Page};
1212

1313
/// Error page showing a message.
1414
#[derive(Template, WebTemplate)]
1515
#[template(path = "error.html")]
1616
pub(crate) struct Error {
17+
pub path_prefix: String,
1718
pub page: Page,
1819
pub theme: Option<Theme>,
1920
pub description: String,
@@ -23,6 +24,7 @@ pub(crate) struct Error {
2324
#[derive(Template, WebTemplate)]
2425
#[template(path = "encrypted.html")]
2526
pub(crate) struct PasswordInput {
27+
pub path_prefix: String,
2628
pub page: Page,
2729
pub theme: Option<Theme>,
2830
pub id: String,
@@ -38,6 +40,7 @@ pub fn make_error(error: crate::Error, page: Page, theme: Option<Theme>) -> Erro
3840
(
3941
error.into(),
4042
Error {
43+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
4144
page,
4245
theme,
4346
description,

crates/wastebin_server/src/handlers/html/paste.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::Deserialize;
77
use crate::cache::Key;
88
use crate::handlers::extract::{Theme, Uid};
99
use crate::handlers::html::{ErrorResponse, PasswordInput, make_error};
10-
use crate::{Cache, Database, Highlighter, Page};
10+
use crate::{Cache, Database, Highlighter, PATH_PREFIX, Page};
1111
use wastebin_core::crypto::Password;
1212
use wastebin_core::db;
1313
use wastebin_core::db::read::{Data, Entry, Metadata};
@@ -22,6 +22,7 @@ pub(crate) struct PasswordForm {
2222
#[derive(Template, WebTemplate)]
2323
#[template(path = "formatted.html")]
2424
pub(crate) struct Paste {
25+
path_prefix: String,
2526
page: Page,
2627
key: Key,
2728
theme: Option<Theme>,
@@ -57,6 +58,7 @@ pub async fn get<E>(
5758
Ok(Entry::Burned(data)) => (data, false),
5859
Err(db::Error::NoPassword) => {
5960
return Ok(PasswordInput {
61+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
6062
page: page.clone(),
6163
theme: theme.clone(),
6264
id,
@@ -95,6 +97,7 @@ pub async fn get<E>(
9597
};
9698

9799
let paste = Paste {
100+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
98101
page: page.clone(),
99102
key,
100103
theme: theme.clone(),

crates/wastebin_server/src/handlers/html/qr.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use url::Url;
77
use crate::cache::Key;
88
use crate::handlers::extract::{Theme, Uid};
99
use crate::handlers::html::{ErrorResponse, make_error};
10-
use crate::{Error, Page};
10+
use crate::{Error, PATH_PREFIX, Page};
1111
use wastebin_core::db::Database;
1212
use wastebin_core::db::read::Metadata;
1313
use wastebin_core::expiration::Expiration;
@@ -42,6 +42,7 @@ pub async fn get(
4242
.is_some_and(|(Uid(user_uid), owner_uid)| user_uid == owner_uid);
4343

4444
Ok(Qr {
45+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
4546
page: page.clone(),
4647
theme: theme.clone(),
4748
key,
@@ -60,6 +61,7 @@ pub async fn get(
6061
#[derive(Template, WebTemplate)]
6162
#[template(path = "qr.html", escape = "none")]
6263
pub(crate) struct Qr {
64+
path_prefix: String,
6365
page: Page,
6466
theme: Option<Theme>,
6567
key: Key,

crates/wastebin_server/src/handlers/raw.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use axum::response::{IntoResponse, Response};
44
use crate::cache::Key;
55
use crate::handlers::extract::{Password, Theme};
66
use crate::handlers::html::{ErrorResponse, PasswordInput, make_error};
7-
use crate::{Database, Page};
7+
use crate::{Database, PATH_PREFIX, Page};
88
use wastebin_core::db;
99
use wastebin_core::db::read::Entry;
1010

@@ -23,6 +23,7 @@ pub async fn get(
2323
match db.get(key.id, password).await {
2424
Ok(Entry::Regular(data) | Entry::Burned(data)) => Ok(data.text.into_response()),
2525
Err(db::Error::NoPassword) => Ok(PasswordInput {
26+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
2627
page: page.clone(),
2728
theme: theme.clone(),
2829
id: key.id.to_string(),

crates/wastebin_server/src/main.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use http::header::{
2323
X_XSS_PROTECTION,
2424
};
2525
use tokio::net::{TcpListener, UnixListener};
26+
use tokio::task_local;
2627
use tower::ServiceBuilder;
2728
use tower_http::compression::CompressionLayer;
2829
use tower_http::timeout::TimeoutLayer;
@@ -34,6 +35,10 @@ use crate::handlers::extract::Theme;
3435
use crate::handlers::{delete, download, html, insert, raw, robots, theme};
3536
use wastebin_core::db::Database;
3637

38+
task_local! {
39+
pub static PATH_PREFIX: String;
40+
}
41+
3742
/// Reference counted [`page::Page`] wrapper.
3843
pub(crate) type Page = Arc<page::Page>;
3944

@@ -79,6 +84,17 @@ impl FromRef<AppState> for Cache {
7984
}
8085
}
8186

87+
async fn path_prefix_layer(req: Request, next: Next) -> Response {
88+
let prefix = req
89+
.headers()
90+
.get("x-forwarded-prefix")
91+
.and_then(|val| val.to_str().ok())
92+
.unwrap_or("")
93+
.to_string();
94+
95+
PATH_PREFIX.scope(prefix, next.run(req)).await
96+
}
97+
8298
async fn security_headers_layer(req: Request, next: Next) -> impl IntoResponse {
8399
const SECURITY_HEADERS: [(HeaderName, HeaderValue); 7] = [
84100
(SERVER, HeaderValue::from_static(env!("CARGO_PKG_NAME"))),
@@ -113,6 +129,7 @@ async fn handle_service_errors(
113129
StatusCode::PAYLOAD_TOO_LARGE => (
114130
StatusCode::PAYLOAD_TOO_LARGE,
115131
html::Error {
132+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
116133
page,
117134
theme,
118135
description: String::from("payload exceeded limit"),
@@ -122,6 +139,7 @@ async fn handle_service_errors(
122139
StatusCode::UNSUPPORTED_MEDIA_TYPE => (
123140
StatusCode::UNSUPPORTED_MEDIA_TYPE,
124141
html::Error {
142+
path_prefix: PATH_PREFIX.with(|val| val.to_string()),
125143
page,
126144
theme,
127145
description: String::from("unsupported media type"),
@@ -213,6 +231,7 @@ fn make_app(state: AppState, timeout: Duration, max_body_size: usize) -> Router
213231
timeout,
214232
))
215233
.layer(from_fn_with_state(state.clone(), handle_service_errors))
234+
.layer(from_fn(path_prefix_layer))
216235
.layer(from_fn(security_headers_layer)),
217236
)
218237
.with_state(state)

crates/wastebin_server/templates/base.html

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,26 @@
77
<title>{{ page.title }}{% block title_content %}{% endblock %}</title>
88
{% match theme %}
99
{% when Some with (Theme::Dark) %}
10-
<link rel="stylesheet" href="{{ page.assets.css.dark.route() }}">
10+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.dark.route() }}">
1111
{% when Some with (Theme::Light) %}
12-
<link rel="stylesheet" href="{{ page.assets.css.light.route() }}">
12+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.light.route() }}">
1313
{% when Some with (Theme::System) %}
14-
<link rel="stylesheet" href="{{ page.assets.css.dark.route() }}" media="(prefers-color-scheme: dark)">
15-
<link rel="stylesheet" href="{{ page.assets.css.light.route() }}" media="(prefers-color-scheme: light)">
14+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.dark.route() }}" media="(prefers-color-scheme: dark)">
15+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.light.route() }}" media="(prefers-color-scheme: light)">
1616
{% when None %}
17-
<link rel="stylesheet" href="{{ page.assets.css.dark.route() }}" media="(prefers-color-scheme: dark)">
18-
<link rel="stylesheet" href="{{ page.assets.css.light.route() }}" media="(prefers-color-scheme: light)">
17+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.dark.route() }}" media="(prefers-color-scheme: dark)">
18+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.light.route() }}" media="(prefers-color-scheme: light)">
1919
{% endmatch %}
20-
<link rel="stylesheet" href="{{ page.assets.css.style.route() }}">
21-
<link rel="icon" href="{{ page.assets.favicon.route() }}" type="image/png">
20+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.style.route() }}">
21+
<link rel="icon" href="{{ path_prefix }}{{ page.assets.favicon.route() }}" type="image/png">
2222
{% block head %}{% endblock %}
2323
</head>
2424
<body>
2525
<div id="main-container">
2626
<header>
2727
<div class="nav-group">
2828
<div class="nav-item">
29-
<a href="/" class="nav-button" title="home" aria-label="home">
29+
<a href="{{ path_prefix }}/" class="nav-button" title="home" aria-label="home">
3030
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
3131
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m4 12 8-8 8 8M6 10.5V19a1 1 0 0 0 1 1h3v-3a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1v3h3a1 1 0 0 0 1-1v-8.5"/>
3232
</svg>
@@ -40,30 +40,30 @@
4040
{% match theme %}
4141
{% when None %}
4242
<div class="nav-item" id="dark-switch">
43-
<a href="/theme?pref=dark" class="nav-button" title="switch to dark" aria-label="switch to dark">
43+
<a href="{{ path_prefix }}/theme?pref=dark" class="nav-button" title="switch to dark" aria-label="switch to dark">
4444
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
4545
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 0 1-.5-17.986V3c-.354.966-.5 1.911-.5 3a9 9 0 0 0 9 9c.239 0 .254.018.488 0A9.004 9.004 0 0 1 12 21Z"/>
4646
</svg>
4747
</a>
4848
</div>
4949
{% when Some with (Theme::System) %}
5050
<div class="nav-item" id="dark-switch">
51-
<a href="/theme?pref=dark" class="nav-button" title="switch to dark" aria-label="switch to dark">
51+
<a href="{{ path_prefix }}/theme?pref=dark" class="nav-button" title="switch to dark" aria-label="switch to dark">
5252
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
5353
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 21a9 9 0 0 1-.5-17.986V3c-.354.966-.5 1.911-.5 3a9 9 0 0 0 9 9c.239 0 .254.018.488 0A9.004 9.004 0 0 1 12 21Z"/>
5454
</svg>
5555
</a>
5656
</div>
5757
{% when Some with (Theme::Dark) %}
5858
<div class="nav-item" id="light-switch">
59-
<a href="/theme?pref=light" class="nav-button" title="switch to light" aria-label="switch to light">
59+
<a href="{{ path_prefix }}/theme?pref=light" class="nav-button" title="switch to light" aria-label="switch to light">
6060
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
6161
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 5V3m0 18v-2M7.05 7.05 5.636 5.636m12.728 12.728L16.95 16.95M5 12H3m18 0h-2M7.05 16.95l-1.414 1.414M18.364 5.636 16.95 7.05M16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"/>
6262
</svg>
6363
</a>
6464
</div>
6565
{% when Some with (Theme::Light) %}
66-
<div class="nav-item" id="system-switch"> <a href="/theme?pref=system" class="nav-button" title="switch to system" aria-label="switch to system">
66+
<div class="nav-item" id="system-switch"> <a href="{{ path_prefix }}/theme?pref=system" class="nav-button" title="switch to system" aria-label="switch to system">
6767
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
6868
<path stroke="currentColor" stroke-linecap="square" stroke-linejoin="round" stroke-width="2" d="M10 19H5a1 1 0 0 1-1-1v-1a3 3 0 0 1 3-3h2m10 1a3 3 0 0 1-3 3m3-3a3 3 0 0 0-3-3m3 3h1m-4 3a3 3 0 0 1-3-3m3 3v1m-3-4a3 3 0 0 1 3-3m-3 3h-1m4-3v-1m-2.121 1.879-.707-.707m5.656 5.656-.707-.707m-4.242 0-.707.707m5.656-5.656-.707.707M12 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/>
6969
</svg>

crates/wastebin_server/templates/burn.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{% block content %}
33
<div class="flex-center">
44
<div>
5-
Copy and send <a class="text-link" href="/{{ key }}">this link</a>. After opening it for the first time, it will be deleted.
5+
Copy and send <a class="text-link" href="{{ path_prefix }}/{{ key }}">this link</a>. After opening it for the first time, it will be deleted.
66
</div>
77
<div>
88
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 {{ code.size() + 4 }} {{ code.size() + 4 }}" stroke="none" width="16rem">

0 commit comments

Comments
 (0)