changesin headers

This commit is contained in:
Mateusz Gruszczyński
2026-08-02 11:04:04 +02:00
parent ede6d1de92
commit 464660a274
3 changed files with 47 additions and 36 deletions
-17
View File
@@ -524,23 +524,6 @@ pub async fn download_file(
serve_token_file(&state, &token, &filename).await serve_token_file(&state, &token, &filename).await
} }
pub async fn download_legacy_file(
State(state): State<SharedState>,
Path((directory, filename)): Path<(String, String)>,
) -> Result<Response, ApiError> {
let Some((id_part, token)) = directory.split_once('_') else {
return Err(ApiError::not_found_file());
};
let id: i64 = id_part.parse().map_err(|_| ApiError::not_found_file())?;
let owner = db::find_file_owner(&state.db, token)
.await?
.ok_or_else(ApiError::not_found_file)?;
if owner.id != id {
return Err(ApiError::not_found_file());
}
serve_token_file(&state, token, &filename).await
}
async fn serve_token_file( async fn serve_token_file(
state: &SharedState, state: &SharedState,
token: &str, token: &str,
+46 -18
View File
@@ -70,10 +70,6 @@ pub fn router(
.route("/icons/favicon-32.png", get(favicon_png)) .route("/icons/favicon-32.png", get(favicon_png))
.route("/icons/apple-touch-icon.png", get(apple_touch_icon)) .route("/icons/apple-touch-icon.png", get(apple_touch_icon))
.route("/f/{token}/{filename}", get(api::download_file)) .route("/f/{token}/{filename}", get(api::download_file))
.route(
"/files/{directory}/{filename}",
get(api::download_legacy_file),
)
.route("/api/auth/identity", post(auth::identity)) .route("/api/auth/identity", post(auth::identity))
.route( .route(
"/api/security/csrf", "/api/security/csrf",
@@ -245,7 +241,7 @@ async fn apply_response_header_policy(request: Request, next: Next) -> Response
fn apply_response_headers(policy: ResponseHeaderPolicy, headers: &mut HeaderMap) { fn apply_response_headers(policy: ResponseHeaderPolicy, headers: &mut HeaderMap) {
match policy { match policy {
ResponseHeaderPolicy::StaticAsset => { ResponseHeaderPolicy::StaticAsset | ResponseHeaderPolicy::File => {
headers headers
.entry(header::X_CONTENT_TYPE_OPTIONS) .entry(header::X_CONTENT_TYPE_OPTIONS)
.or_insert(HeaderValue::from_static("nosniff")); .or_insert(HeaderValue::from_static("nosniff"));
@@ -284,16 +280,23 @@ fn apply_response_headers(policy: ResponseHeaderPolicy, headers: &mut HeaderMap)
enum ResponseHeaderPolicy { enum ResponseHeaderPolicy {
Application, Application,
StaticAsset, StaticAsset,
File,
} }
fn response_header_policy(path: &str) -> ResponseHeaderPolicy { fn response_header_policy(path: &str) -> ResponseHeaderPolicy {
if is_asset_path(path) || is_icon_path(path) { if is_file_path(path) {
ResponseHeaderPolicy::File
} else if is_asset_path(path) || is_icon_path(path) {
ResponseHeaderPolicy::StaticAsset ResponseHeaderPolicy::StaticAsset
} else { } else {
ResponseHeaderPolicy::Application ResponseHeaderPolicy::Application
} }
} }
fn is_file_path(path: &str) -> bool {
path == "/f" || path.starts_with("/f/")
}
fn is_asset_path(path: &str) -> bool { fn is_asset_path(path: &str) -> bool {
path == "/assets" || path.starts_with("/assets/") path == "/assets" || path.starts_with("/assets/")
} }
@@ -329,23 +332,21 @@ mod tests {
} }
#[test] #[test]
fn keeps_file_routes_on_the_application_policy() { fn classifies_file_routes_as_files() {
for path in [ for path in ["/f", "/f/token/image.png"] {
"/f", assert_eq!(response_header_policy(path), ResponseHeaderPolicy::File);
"/f/token/image.png",
"/files",
"/files/legacy/image.png",
] {
assert_eq!(
response_header_policy(path),
ResponseHeaderPolicy::Application
);
} }
} }
#[test] #[test]
fn classifies_other_routes_as_application() { fn classifies_other_routes_as_application() {
for path in ["/", "/api/auth/me", "/static/missing.css", "/unknown"] { for path in [
"/",
"/api/auth/me",
"/static/missing.css",
"/files/legacy/image.png",
"/unknown",
] {
assert_eq!( assert_eq!(
response_header_policy(path), response_header_policy(path),
ResponseHeaderPolicy::Application ResponseHeaderPolicy::Application
@@ -372,6 +373,33 @@ mod tests {
assert!(!headers.contains_key("permissions-policy")); assert!(!headers.contains_key("permissions-policy"));
} }
#[test]
fn file_policy_keeps_file_headers_without_document_policies() {
let mut headers = HeaderMap::new();
headers.insert(
"content-security-policy",
HeaderValue::from_static("default-src 'none'; sandbox"),
);
headers.insert(
header::CONTENT_DISPOSITION,
HeaderValue::from_static("attachment; filename=\"manual.pdf\""),
);
apply_response_headers(ResponseHeaderPolicy::File, &mut headers);
assert_eq!(headers[header::X_CONTENT_TYPE_OPTIONS], "nosniff");
assert_eq!(
headers["content-security-policy"],
"default-src 'none'; sandbox"
);
assert!(headers.contains_key(header::CONTENT_DISPOSITION));
assert!(!headers.contains_key("x-frame-options"));
assert!(!headers.contains_key("cross-origin-opener-policy"));
assert!(!headers.contains_key("cross-origin-resource-policy"));
assert!(!headers.contains_key("referrer-policy"));
assert!(!headers.contains_key("permissions-policy"));
}
#[test] #[test]
fn application_policy_preserves_handler_headers() { fn application_policy_preserves_handler_headers() {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
+1 -1
View File
@@ -133,7 +133,7 @@ pub(super) async fn apple_touch_icon() -> Response {
} }
pub(super) async fn robots_txt() -> Response { pub(super) async fn robots_txt() -> Response {
let mut response = "User-agent: *\nDisallow: /f/\nDisallow: /files/\n".into_response(); let mut response = "User-agent: *\nDisallow: /f/\n".into_response();
response.headers_mut().insert( response.headers_mut().insert(
header::CONTENT_TYPE, header::CONTENT_TYPE,
HeaderValue::from_static("text/plain; charset=utf-8"), HeaderValue::from_static("text/plain; charset=utf-8"),