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 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-24 09:31:47 +00:00
co-authored by Claude Haiku 5.5
parent f86928d0d5
commit ef6819b6fd
+11 -1
View File
@@ -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<u8> {
let mut zw = zip::ZipWriter::new(Cursor::new(Vec::new()));
let o = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated);