Skip to content

Commit 4cb0f72

Browse files
committed
Support path prefix in reverse proxy scenarios.
1 parent b494662 commit 4cb0f72

14 files changed

Lines changed: 91 additions & 62 deletions

File tree

crates/wastebin_server/src/handlers/delete/form.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub async fn delete(
1717
async {
1818
let id = id.parse()?;
1919
db.delete_for(id, &uids).await?;
20-
Ok(Redirect::to("/"))
20+
Ok(crate::redirect("/"))
2121
}
2222
.await
2323
.map_err(|err| make_error(err, page.clone(), theme, lang))

crates/wastebin_server/src/handlers/extract.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ where
186186
.and_then(|referer| referer.to_str().ok())
187187
.map(|referer| {
188188
if referer.starts_with('/') && !referer.starts_with("//") {
189-
Redirect::to(referer)
189+
crate::redirect(referer)
190190
} else {
191191
referer
192192
.parse::<url::Url>()
@@ -198,10 +198,10 @@ where
198198
|q| Redirect::to(&format!("{path}?{q}")),
199199
)
200200
})
201-
.unwrap_or_else(|| Redirect::to("/"))
201+
.unwrap_or_else(|| crate::redirect("/"))
202202
}
203203
})
204-
.unwrap_or_else(|| Redirect::to("/"));
204+
.unwrap_or_else(|| crate::redirect("/"));
205205

206206
Ok(SafeReferer(redirect))
207207
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ pub async fn get<E>(
9090
}
9191
let mut cookie = cookie("uid", serialize_uids(&new_uids));
9292
cookie.set_secure(true);
93-
return Ok((jar.add(cookie), Redirect::to(&format!("/{id}"))).into_response());
93+
return Ok((jar.add(cookie), crate::redirect(&format!("/{id}"))).into_response());
9494
}
9595

9696
async {

crates/wastebin_server/src/handlers/insert/form.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ pub async fn post<E: std::fmt::Debug>(
8787
let mut cookie = cookie("uid", serialize_uids(&uids));
8888
cookie.set_secure(true);
8989

90-
Ok((jar.add(cookie), Redirect::to(&url)))
90+
Ok((jar.add(cookie), crate::redirect(&url)))
9191
}
9292
.await
9393
.map_err(|err| make_error(err, page, theme, lang))

crates/wastebin_server/src/main.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::time::Duration;
1515
use axum::extract::{DefaultBodyLimit, FromRef, Request, State};
1616
use axum::http::{HeaderName, HeaderValue, StatusCode};
1717
use axum::middleware::{Next, from_fn, from_fn_with_state};
18-
use axum::response::{IntoResponse, Response};
18+
use axum::response::{IntoResponse, Redirect, Response};
1919
use axum::routing::{Router, get, post};
2020
use axum_extra::extract::cookie::Key;
2121
use futures::future::TryFutureExt;
@@ -24,6 +24,7 @@ use http::header::{
2424
X_XSS_PROTECTION,
2525
};
2626
use tokio::net::{TcpListener, UnixListener};
27+
use tokio::task_local;
2728
use tower::ServiceBuilder;
2829
use tower_http::compression::CompressionLayer;
2930
use tower_http::timeout::TimeoutLayer;
@@ -36,6 +37,10 @@ use crate::handlers::{delete, download, html, insert, raw, robots, theme};
3637
use crate::i18n::Lang;
3738
use wastebin_core::db::Database;
3839

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

@@ -81,6 +86,26 @@ impl FromRef<AppState> for Cache {
8186
}
8287
}
8388

89+
fn path_prefix() -> String {
90+
PATH_PREFIX.with(|val| val.to_string())
91+
}
92+
93+
fn redirect(uri: &str) -> Redirect {
94+
let prefixed_uri = path_prefix() + uri;
95+
Redirect::to(&prefixed_uri)
96+
}
97+
98+
async fn path_prefix_layer(req: Request, next: Next) -> Response {
99+
let prefix = req
100+
.headers()
101+
.get("x-forwarded-prefix")
102+
.and_then(|val| val.to_str().ok())
103+
.unwrap_or("")
104+
.to_string();
105+
106+
PATH_PREFIX.scope(prefix, next.run(req)).await
107+
}
108+
84109
async fn security_headers_layer(req: Request, next: Next) -> impl IntoResponse {
85110
// Rendered Markdown may embed remote images via `![](…)`; relax img-src for that route only.
86111
const CSP_STRICT: HeaderValue = HeaderValue::from_static(
@@ -239,6 +264,7 @@ fn make_app(state: AppState, timeout: Duration, max_body_size: usize) -> Router
239264
timeout,
240265
))
241266
.layer(from_fn_with_state(state.clone(), handle_service_errors))
267+
.layer(from_fn(path_prefix_layer))
242268
.layer(from_fn(security_headers_layer)),
243269
)
244270
.with_state(state)

crates/wastebin_server/templates/base.html

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{% let path_prefix = crate::path_prefix() %}
12
<!DOCTYPE html>
23
<html lang="{{ lang.code() }}">
34
<head>
@@ -7,27 +8,27 @@
78
<title>{{ page.title }}{% block title_content %}{% endblock %}</title>
89
{% match theme %}
910
{% when Some with (Theme::Dark) %}
10-
<link rel="stylesheet" href="{{ page.assets.css.dark.route() }}">
11+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.dark.route() }}">
1112
{% when Some with (Theme::Light) %}
12-
<link rel="stylesheet" href="{{ page.assets.css.light.route() }}">
13+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.light.route() }}">
1314
{% 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)">
15+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.dark.route() }}" media="(prefers-color-scheme: dark)">
16+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.light.route() }}" media="(prefers-color-scheme: light)">
1617
{% 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)">
18+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.dark.route() }}" media="(prefers-color-scheme: dark)">
19+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.light.route() }}" media="(prefers-color-scheme: light)">
1920
{% endmatch %}
20-
<link rel="stylesheet" href="{{ page.assets.css.style.route() }}">
21-
<link rel="icon" href="{{ page.assets.favicon.route() }}" type="image/png">
22-
<noscript><link rel="stylesheet" href="{{ page.assets.css.no_js.route() }}"></noscript>
21+
<link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.style.route() }}">
22+
<link rel="icon" href="{{ path_prefix }}{{ page.assets.favicon.route() }}" type="image/png">
23+
<noscript><link rel="stylesheet" href="{{ path_prefix }}{{ page.assets.css.no_js.route() }}"></noscript>
2324
{% block head %}{% endblock %}
2425
</head>
2526
<body{% block body_attrs %}{% endblock %}>
2627
{% block body %}
2728
<div id="main-container">
2829
<header>
2930
<div class="nav-group">
30-
<a href="/" class="nav-button" title="{{ lang.t("nav.home") }}" aria-label="{{ lang.t("nav.home") }}">
31+
<a href="{{ path_prefix }}/" class="nav-button" title="{{ lang.t("nav.home") }}" aria-label="{{ lang.t("nav.home") }}">
3132
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/></svg>
3233
</a>
3334
{% block title %}{% endblock %}
@@ -37,43 +38,43 @@
3738
<div class="theme-switcher">
3839
{% match theme %}
3940
{% when None %}
40-
<a href="/theme?pref=dark" class="theme-opt" title="{{ lang.t("theme.dark") }}">
41+
<a href="{{ path_prefix }}/theme?pref=dark" class="theme-opt" title="{{ lang.t("theme.dark") }}">
4142
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
4243
</a>
43-
<a href="/theme?pref=light" class="theme-opt" title="{{ lang.t("theme.light") }}">
44+
<a href="{{ path_prefix }}/theme?pref=light" class="theme-opt" title="{{ lang.t("theme.light") }}">
4445
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
4546
</a>
46-
<a href="/theme?pref=system" class="theme-opt active" title="{{ lang.t("theme.auto") }}">
47+
<a href="{{ path_prefix }}/theme?pref=system" class="theme-opt active" title="{{ lang.t("theme.auto") }}">
4748
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>
4849
</a>
4950
{% when Some with (Theme::System) %}
50-
<a href="/theme?pref=dark" class="theme-opt" title="{{ lang.t("theme.dark") }}">
51+
<a href="{{ path_prefix }}/theme?pref=dark" class="theme-opt" title="{{ lang.t("theme.dark") }}">
5152
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
5253
</a>
53-
<a href="/theme?pref=light" class="theme-opt" title="{{ lang.t("theme.light") }}">
54+
<a href="{{ path_prefix }}/theme?pref=light" class="theme-opt" title="{{ lang.t("theme.light") }}">
5455
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
5556
</a>
56-
<a href="/theme?pref=system" class="theme-opt active" title="{{ lang.t("theme.auto") }}">
57+
<a href="{{ path_prefix }}/theme?pref=system" class="theme-opt active" title="{{ lang.t("theme.auto") }}">
5758
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>
5859
</a>
5960
{% when Some with (Theme::Dark) %}
60-
<a href="/theme?pref=dark" class="theme-opt active" title="{{ lang.t("theme.dark") }}">
61+
<a href="{{ path_prefix }}/theme?pref=dark" class="theme-opt active" title="{{ lang.t("theme.dark") }}">
6162
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
6263
</a>
63-
<a href="/theme?pref=light" class="theme-opt" title="{{ lang.t("theme.light") }}">
64+
<a href="{{ path_prefix }}/theme?pref=light" class="theme-opt" title="{{ lang.t("theme.light") }}">
6465
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
6566
</a>
66-
<a href="/theme?pref=system" class="theme-opt" title="{{ lang.t("theme.auto") }}">
67+
<a href="{{ path_prefix }}/theme?pref=system" class="theme-opt" title="{{ lang.t("theme.auto") }}">
6768
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>
6869
</a>
6970
{% when Some with (Theme::Light) %}
70-
<a href="/theme?pref=dark" class="theme-opt" title="{{ lang.t("theme.dark") }}">
71+
<a href="{{ path_prefix }}/theme?pref=dark" class="theme-opt" title="{{ lang.t("theme.dark") }}">
7172
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/></svg>
7273
</a>
73-
<a href="/theme?pref=light" class="theme-opt active" title="{{ lang.t("theme.light") }}">
74+
<a href="{{ path_prefix }}/theme?pref=light" class="theme-opt active" title="{{ lang.t("theme.light") }}">
7475
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></svg>
7576
</a>
76-
<a href="/theme?pref=system" class="theme-opt" title="{{ lang.t("theme.auto") }}">
77+
<a href="{{ path_prefix }}/theme?pref=system" class="theme-opt" title="{{ lang.t("theme.auto") }}">
7778
<svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M8 21h8"/><path d="M12 17v4"/></svg>
7879
</a>
7980
{% endmatch %}

crates/wastebin_server/templates/burn-confirmation.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
{% if let Some(title) = title %}
1010
<div class="dialog-section dialog-subtitle">{{ title }}</div>
1111
{% endif %}
12-
<form class="dialog-section dialog-actions" action="/{{ id }}" method="post">
12+
<form class="dialog-section dialog-actions" action="{{ path_prefix }}/{{ id }}" method="post">
1313
<input type="hidden" name="confirm_burn" value="1">
14-
<a class="button button-secondary" href="/">{{ lang.t("burn_confirm.cancel") }}</a>
14+
<a class="button button-secondary" href="{{ path_prefix }}/">{{ lang.t("burn_confirm.cancel") }}</a>
1515
<button class="button" type="submit">{{ lang.t("burn_confirm.reveal") }}</button>
1616
</form>
1717
</div>

crates/wastebin_server/templates/encrypted.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{% extends "base.html" %}
22

33
{% block head %}
4-
<script defer src="{{ page.assets.password_toggle_js.route() }}"></script>
4+
<script defer src="{{path_prefix}}{{ page.assets.password_toggle_js.route() }}"></script>
55
{% endblock %}
66

77
{% block content %}
88
<div class="flex-center">
99
<div class="dialog">
1010
<div class="dialog-header">{{ lang.t("encrypted.title") }}</div>
11-
<form action="/{{ id }}" method="post">
12-
<input type="hidden" name="confirm_burn" value="1">
11+
<form action="{{ path_prefix }}/{{ id }}" method="post">
12+
<input type="hidden" name="``confirm_burn" value="1">
1313
<div class="dialog-section">
1414
<div class="password-input-wrap">
1515
<input type="password" id="password" name="password" placeholder="{{ lang.t("encrypted.placeholder") }}" autofocus>
@@ -20,7 +20,7 @@
2020
</div>
2121
</div>
2222
<div class="dialog-section dialog-actions">
23-
<a class="button button-secondary" href="/">{{ lang.t("encrypted.cancel") }}</a>
23+
<a class="button button-secondary" href="{{ path_prefix }}/">{{ lang.t("encrypted.cancel") }}</a>
2424
<button class="button" type="submit">{{ lang.t("encrypted.decrypt") }}</button>
2525
</div>
2626
</form>

crates/wastebin_server/templates/error.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
{{ description }}
88
</div>
99
<div class="dialog-section dialog-actions">
10-
<a class="button button-secondary" href="/">{{ lang.t("error.back") }}</a>
10+
<a class="button button-secondary" href="{{ path_prefix }}/">{{ lang.t("error.back") }}</a>
1111
</div>
1212
</div>
1313
</div>

crates/wastebin_server/templates/formatted.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
{% block view_toggle %}
44
{% if is_markdown %}
5-
<a id="view-toggle" href="/md/{{ key }}" class="nav-button" title="{{ lang.t("nav.rendered") }}" aria-label="{{ lang.t("nav.rendered") }}">
5+
<a id="view-toggle" href="{{ path_prefix }}/md/{{ key }}" class="nav-button" title="{{ lang.t("nav.rendered") }}" aria-label="{{ lang.t("nav.rendered") }}">
66
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor" stroke="none"><path d="M22.269 19.385H1.731A1.73 1.73 0 0 1 0 17.654V6.345a1.73 1.73 0 0 1 1.731-1.73h20.538A1.73 1.73 0 0 1 24 6.345v11.308a1.73 1.73 0 0 1-1.731 1.731zm-16.5-3.462v-4.5l2.308 2.885 2.307-2.885v4.5h2.308V8.078h-2.308l-2.307 2.885-2.308-2.885H3.46v7.847zM21.231 12h-2.308V8.077h-2.307V12h-2.308l3.461 4.039z"/></svg>
77
</a>
88
{% endif %}
@@ -14,6 +14,6 @@
1414
</div>
1515
{% if !is_available %}
1616
<div id="burn-message" hidden data-message="{{ lang.t("paste.toast.burned") }}"></div>
17-
<script defer src="{{ page.assets.burn_js.route()}}"></script>
17+
<script defer src="{{ path_prefix }}{{ page.assets.burn_js.route()}}"></script>
1818
{% endif %}
1919
{% endblock %}

0 commit comments

Comments
 (0)