From e37267f548d1e57f5ea3a1e1c8c15ede072ed717 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Tue, 20 Oct 2026 18:36:13 +0200 Subject: [PATCH] Strip URI fragment before percent-decoding OPC targets Found while testing crates/pptx/src/opc.rs:283-285 (resolve) Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/pptx/src/opc.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/pptx/src/opc.rs b/crates/pptx/src/opc.rs index 0e7f86fe..b8406dc8 100644 --- a/crates/pptx/src/opc.rs +++ b/crates/pptx/src/opc.rs @@ -281,8 +281,8 @@ fn percent_decode(s: &str) -> String { /// Resolve a relative target against a base directory into a part name. pub fn resolve(base: &str, target: &str) -> String { - let target = percent_decode(target); - let target = target.split('#').next().unwrap_or(""); + let target = percent_decode(target.split('#').next().unwrap_or("")); + let target = target.as_str(); let mut segs: Vec<&str> = if target.starts_with('/') { vec![] } else { base.split('/').filter(|s| !s.is_empty()).collect() }; for s in target.split('/') { match s { @@ -441,6 +441,12 @@ impl PackageOut { mod tests { use super::*; + #[test] + fn resolve_keeps_encoded_hash_and_strips_fragment() { + assert_eq!(resolve("ppt/slides", "../media/a%23b.png"), "ppt/media/a#b.png"); + assert_eq!(resolve("ppt/slides", "../media/a.png#frag"), "ppt/media/a.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);