From ef6819b6fd58951557c8986bc6ad6f9aa8cc1d11 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sat, 24 Oct 2026 09:31:47 +0000 Subject: [PATCH] Reject '+' sign in OPC target percent-decoding Found while testing crates/pptx/src/opc.rs:263-277 (percent_decode, used by resolve) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/pptx/src/opc.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/crates/pptx/src/opc.rs b/crates/pptx/src/opc.rs index f39b88bf..d7628da7 100644 --- a/crates/pptx/src/opc.rs +++ b/crates/pptx/src/opc.rs @@ -267,7 +267,11 @@ fn percent_decode(s: &str) -> String { while i < b.len() { let c = b.get(i).copied().unwrap_or(0); if c == b'%' - && let Some(h) = b.get(i + 1..i + 3).and_then(|h| std::str::from_utf8(h).ok()).and_then(|h| u8::from_str_radix(h, 16).ok()) + && let Some(h) = b + .get(i + 1..i + 3) + .filter(|h| h.iter().all(u8::is_ascii_hexdigit)) + .and_then(|h| std::str::from_utf8(h).ok()) + .and_then(|h| u8::from_str_radix(h, 16).ok()) { out.push(h); i += 3; @@ -452,6 +456,12 @@ mod tests { assert_eq!(resolve("ppt/slides", "../media/a.png#frag"), "ppt/media/a.png"); } + #[test] + fn resolve_percent_decode_requires_two_hex_digits() { + assert_eq!(resolve("ppt/slides", "../media/a%+5b.png"), "ppt/media/a%+5b.png"); + assert_eq!(resolve("ppt/slides", "../media/a%20b.png"), "ppt/media/a b.png"); + } + fn zip_of(entries: &[(&str, usize)]) -> Vec { let mut zw = zip::ZipWriter::new(Cursor::new(Vec::new())); let o = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);