Skip to content

Commit 14e208b

Browse files
committed
Support path prefix in reverse proxy scenarios.
1 parent aac3d94 commit 14e208b

12 files changed

Lines changed: 56 additions & 29 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
@@ -15,7 +15,7 @@ pub async fn delete(
1515
async {
1616
let id = id.parse()?;
1717
db.delete_for(id, uid).await?;
18-
Ok(Redirect::to("/"))
18+
Ok(crate::redirect("/"))
1919
}
2020
.await
2121
.map_err(|err| make_error(err, page.clone(), theme))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ pub async fn post<E: std::fmt::Debug>(
8585
.same_site(SameSite::Strict)
8686
.build();
8787

88-
Ok((jar.add(cookie), Redirect::to(&url)))
88+
Ok((jar.add(cookie), crate::redirect(&url)))
8989
}
9090
.await
9191
.map_err(|err| make_error(err, page, theme))

crates/wastebin_server/src/handlers/theme.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pub async fn get(headers: HeaderMap, Query(pref): Query<Preference>) -> impl Int
1010
let response = headers
1111
.get(REFERER)
1212
.and_then(|referer| referer.to_str().ok())
13-
.map_or_else(|| Redirect::to("/"), Redirect::to);
13+
.map_or_else(|| crate::redirect("/"), Redirect::to);
1414

1515
([(SET_COOKIE, format!("pref={}", pref.pref))], response)
1616
}

crates/wastebin_server/src/main.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::time::Duration;
1414
use axum::extract::{DefaultBodyLimit, FromRef, Request, State};
1515
use axum::http::{HeaderName, HeaderValue, StatusCode};
1616
use axum::middleware::{Next, from_fn, from_fn_with_state};
17-
use axum::response::{IntoResponse, Response};
17+
use axum::response::{IntoResponse, Redirect, Response};
1818
use axum::routing::{Router, get, post};
1919
use axum_extra::extract::cookie::Key;
2020
use futures::future::TryFutureExt;
@@ -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+
static PATH_PREFIX: String;
40+
}
41+
3742
/// Reference counted [`page::Page`] wrapper.
3843
pub(crate) type Page = Arc<page::Page>;
3944

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

87+
fn path_prefix() -> String {
88+
PATH_PREFIX.with(|val| val.to_string())
89+
}
90+
91+
fn redirect(uri: &str) -> Redirect {
92+
let prefixed_uri = path_prefix() + uri;
93+
Redirect::to(&prefixed_uri)
94+
}
95+
96+
async fn path_prefix_layer(req: Request, next: Next) -> Response {
97+
let prefix = req
98+
.headers()
99+
.get("x-forwarded-prefix")
100+
.and_then(|val| val.to_str().ok())
101+
.unwrap_or("")
102+
.to_string();
103+
104+
PATH_PREFIX.scope(prefix, next.run(req)).await
105+
}
106+
82107
async fn security_headers_layer(req: Request, next: Next) -> impl IntoResponse {
83108
const SECURITY_HEADERS: [(HeaderName, HeaderValue); 7] = [
84109
(SERVER, HeaderValue::from_static(env!("CARGO_PKG_NAME"))),
@@ -213,6 +238,7 @@ fn make_app(state: AppState, timeout: Duration, max_body_size: usize) -> Router
213238
timeout,
214239
))
215240
.layer(from_fn_with_state(state.clone(), handle_service_errors))
241+
.layer(from_fn(path_prefix_layer))
216242
.layer(from_fn(security_headers_layer)),
217243
)
218244
.with_state(state)

crates/wastebin_server/templates/base.html

Lines changed: 14 additions & 13 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="en">
34
<head>
@@ -7,26 +8,26 @@
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">
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">
2223
{% block head %}{% endblock %}
2324
</head>
2425
<body>
2526
<div id="main-container">
2627
<header>
2728
<div class="nav-group">
2829
<div class="nav-item">
29-
<a href="/" class="nav-button" title="home" aria-label="home">
30+
<a href="{{ path_prefix }}/" class="nav-button" title="home" aria-label="home">
3031
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
3132
<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"/>
3233
</svg>
@@ -40,30 +41,30 @@
4041
{% match theme %}
4142
{% when None %}
4243
<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">
44+
<a href="{{ path_prefix }}/theme?pref=dark" class="nav-button" title="switch to dark" aria-label="switch to dark">
4445
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
4546
<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"/>
4647
</svg>
4748
</a>
4849
</div>
4950
{% when Some with (Theme::System) %}
5051
<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">
52+
<a href="{{ path_prefix }}/theme?pref=dark" class="nav-button" title="switch to dark" aria-label="switch to dark">
5253
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
5354
<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"/>
5455
</svg>
5556
</a>
5657
</div>
5758
{% when Some with (Theme::Dark) %}
5859
<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">
60+
<a href="{{ path_prefix }}/theme?pref=light" class="nav-button" title="switch to light" aria-label="switch to light">
6061
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
6162
<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"/>
6263
</svg>
6364
</a>
6465
</div>
6566
{% 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">
67+
<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">
6768
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
6869
<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"/>
6970
</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">

crates/wastebin_server/templates/encrypted.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
{% block content %}
44
<div class="flex-center">
5-
<form action="/{{ id }}" method="post">
5+
<form action="{{ path_prefix }}/{{ id }}" method="post">
66
<div class="controls">
77
<div class="controls-row">
88
<input type="password" id="password" name="password" placeholder="Password ..." autofocus>

crates/wastebin_server/templates/error.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
{% block content %}
33
<div class="flex-center">
44
<p>😢 {{ description }}</p>
5-
<p><a class="text-link" href="/">go back</a></p>
5+
<p><a class="text-link" href="{{ path_prefix }}/">go back</a></p>
66
</div>
77
{% endblock %}

crates/wastebin_server/templates/formatted.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
</div>
4242
{{ html|safe }}
4343
{% if !is_available %}
44-
<script defer src="{{ page.assets.burn_js.route()}}"></script>
44+
<script defer src="{{ path_prefix }}{{ page.assets.burn_js.route()}}"></script>
4545
{% endif %}
4646
{% endblock %}

crates/wastebin_server/templates/index.html

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

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

77
{% block nav_specific %}
@@ -15,7 +15,7 @@
1515
{% endblock %}
1616

1717
{%- block content -%}
18-
<form id="form" action="/new" method="post">
18+
<form id="form" action="{{ path_prefix }}/new" method="post">
1919
<div class="container">
2020
<div class="content">
2121
<textarea id="text" name="text" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" placeholder="<paste text or drop file here>" autofocus required></textarea>

0 commit comments

Comments
 (0)