From 5c2834b970a2d1dffcd45515132316aee89d8ba2 Mon Sep 17 00:00:00 2001 From: 81reap Date: Mon, 31 Aug 2026 15:51:11 -0400 Subject: [PATCH] fix(blobs_to_data_url) :: parse `.docx` blobs correctly Word documents always fell back to `applicaiton/zip` as the six-byte windows did not match a five-byte litral. --- CHANGELOG.md | 1 + src/webserver/database/blob_to_data_url.rs | 30 +++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ac7ab93..361593fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ``` SQLPage now keeps the variable value, producing `https://api.example.com/john.doe` as expected. +- Word documents stored as `BLOB`, `BYTEA`, or `VARBINARY` in database columns are now detected correctly as `.docx`. Before, `.docx` files were parsed as `application/zip`; now they report as `application/vnd.openxmlformats-officedocument.wordprocessingml.document`. - A `content_security_policy` that does not contain `'nonce-{NONCE}'` is now sent as written, instead of being silently dropped and leaving the response with no `Content-Security-Policy` header at all. Setting the option to the empty string still disables the header, as documented. ## v0.46 diff --git a/src/webserver/database/blob_to_data_url.rs b/src/webserver/database/blob_to_data_url.rs index c451e2ba..cd2905ed 100644 --- a/src/webserver/database/blob_to_data_url.rs +++ b/src/webserver/database/blob_to_data_url.rs @@ -31,7 +31,7 @@ pub fn detect_mime_type(bytes: &[u8]) -> &'static str { // Check for Office document types in ZIP central directory if bytes.len() >= 50 { let central_dir = &bytes[30..bytes.len().min(50)]; - if central_dir.windows(6).any(|w| w == b"word/") { + if central_dir.windows(5).any(|w| w == b"word/") { return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; } if central_dir.windows(3).any(|w| w == b"xl/") { @@ -189,6 +189,34 @@ mod tests { ); } + fn zip_starting_with(first_entry_name: &[u8]) -> Vec { + let mut blob = b"PK\x03\x04".to_vec(); + blob.resize(30, 0); + blob.extend_from_slice(first_entry_name); + blob.resize(50, 0); + blob + } + + #[test] + fn test_detect_office_documents() { + assert_eq!( + detect_mime_type(&zip_starting_with(b"word/document.xml")), + "application/vnd.openxmlformats-officedocument.wordprocessingml.document" + ); + assert_eq!( + detect_mime_type(&zip_starting_with(b"xl/workbook.xml")), + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + ); + assert_eq!( + detect_mime_type(&zip_starting_with(b"ppt/presentation.xml")), + "application/vnd.openxmlformats-officedocument.presentationml.presentation" + ); + assert_eq!( + detect_mime_type(&zip_starting_with(b"other/thing.txt")), + "application/zip" + ); + } + #[test] fn decodes_base64_and_percent_encoded_data_urls() { assert_eq!(