From b2b9adb0af5c686bc2747d7b401c78c120bc8709 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 7 Mar 2026 16:55:43 +0300 Subject: [PATCH] pinakes-server: sanitize Content-Disposition filenames in dls Signed-off-by: NotAShelf Change-Id: Id8769e010ed634b9baf0e2c76905ad336a6a6964 --- crates/pinakes-server/src/routes/upload.rs | 20 +++++++++++++++++-- .../src/components/markdown_viewer.rs | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/crates/pinakes-server/src/routes/upload.rs b/crates/pinakes-server/src/routes/upload.rs index 941bb1a..9db7932 100644 --- a/crates/pinakes-server/src/routes/upload.rs +++ b/crates/pinakes-server/src/routes/upload.rs @@ -14,6 +14,22 @@ use crate::{ state::AppState, }; +/// Sanitize a filename for use in Content-Disposition headers. +/// Strips characters that could break header parsing or enable injection. +fn sanitize_content_disposition(filename: &str) -> String { + let safe: String = filename + .chars() + .map(|c| { + if c == '"' || c == '\\' || c == '\n' || c == '\r' { + '_' + } else { + c + } + }) + .collect(); + format!("attachment; filename=\"{safe}\"") +} + /// Upload a file to managed storage /// POST /api/upload pub async fn upload_file( @@ -105,7 +121,7 @@ pub async fn download_file( (header::CONTENT_TYPE, content_type), ( header::CONTENT_DISPOSITION, - format!("attachment; filename=\"{}\"", filename), + sanitize_content_disposition(&filename), ), ], body, @@ -130,7 +146,7 @@ pub async fn download_file( (header::CONTENT_TYPE, content_type), ( header::CONTENT_DISPOSITION, - format!("attachment; filename=\"{}\"", filename), + sanitize_content_disposition(&filename), ), ], body, diff --git a/crates/pinakes-ui/src/components/markdown_viewer.rs b/crates/pinakes-ui/src/components/markdown_viewer.rs index cbdf635..ec3454f 100644 --- a/crates/pinakes-ui/src/components/markdown_viewer.rs +++ b/crates/pinakes-ui/src/components/markdown_viewer.rs @@ -50,7 +50,7 @@ pub fn MarkdownViewer( }); // Set up global wikilink click handler that the inline onclick attributes - // will call This bridges JavaScript → Rust communication + // will call This bridges JavaScript -> Rust communication use_effect(move || { if let Some(handler) = on_wikilink_click { spawn(async move {