From fb5b50f059d39144c2425c75f5d523622d63aac9 Mon Sep 17 00:00:00 2001 From: sigoden Date: Fri, 31 Mar 2023 22:52:07 +0800 Subject: [PATCH] fix: URL-encoded filename when downloading in safari (#203) * fix: URL-encoded filename when downloading in safari * add test --- src/server.rs | 28 +++++++++++++++++----------- tests/http.rs | 11 +++++++++++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/server.rs b/src/server.rs index d026d8c..d43b450 100644 --- a/src/server.rs +++ b/src/server.rs @@ -479,13 +479,7 @@ impl Server { async fn handle_zip_dir(&self, path: &Path, head_only: bool, res: &mut Response) -> Result<()> { let (mut writer, reader) = tokio::io::duplex(BUF_SIZE); let filename = try_get_file_name(path)?; - res.headers_mut().insert( - CONTENT_DISPOSITION, - HeaderValue::from_str(&format!( - "attachment; filename=\"{}.zip\"", - encode_uri(filename), - ))?, - ); + set_content_diposition(res, false, &format!("{}.zip", filename))?; res.headers_mut() .insert("content-type", HeaderValue::from_static("application/zip")); if head_only { @@ -644,10 +638,7 @@ impl Server { ); let filename = try_get_file_name(path)?; - res.headers_mut().insert( - CONTENT_DISPOSITION, - HeaderValue::from_str(&format!("inline; filename=\"{}\"", encode_uri(filename),))?, - ); + set_content_diposition(res, true, filename)?; res.headers_mut().typed_insert(AcceptRanges::bytes()); @@ -1359,6 +1350,21 @@ fn status_no_content(res: &mut Response) { *res.status_mut() = StatusCode::NO_CONTENT; } +fn set_content_diposition(res: &mut Response, inline: bool, filename: &str) -> Result<()> { + let kind = if inline { "inline" } else { "attachment" }; + let value = if filename.is_ascii() { + HeaderValue::from_str(&format!("{kind}; filename=\"{}\"", filename,))? + } else { + HeaderValue::from_str(&format!( + "{kind}; filename=\"{}\"; filename*=UTF-8''{}", + filename, + encode_uri(filename), + ))? + }; + res.headers_mut().insert(CONTENT_DISPOSITION, value); + Ok(()) +} + fn is_hidden(hidden: &[String], file_name: &str, is_dir_type: bool) -> bool { hidden.iter().any(|v| { if is_dir_type { diff --git a/tests/http.rs b/tests/http.rs index 6ae7790..cc2e295 100644 --- a/tests/http.rs +++ b/tests/http.rs @@ -184,6 +184,17 @@ fn get_file_404(server: TestServer) -> Result<(), Error> { Ok(()) } +#[rstest] +fn get_file_emoji_path(server: TestServer) -> Result<(), Error> { + let resp = reqwest::blocking::get(format!("{}{BIN_FILE}", server.url()))?; + assert_eq!(resp.status(), 200); + assert_eq!( + resp.headers().get("content-disposition").unwrap(), + "inline; filename=\"😀.bin\"; filename*=UTF-8''%F0%9F%98%80.bin" + ); + Ok(()) +} + #[rstest] fn get_file_edit(server: TestServer) -> Result<(), Error> { let resp = fetch!(b"GET", format!("{}index.html?edit", server.url())).send()?;