diff --git a/Cargo.lock b/Cargo.lock index d3dffa4f9..de57cfcc4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3096,15 +3096,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" -[[package]] -name = "quick-xml" -version = "0.38.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" -dependencies = [ - "memchr", -] - [[package]] name = "quick-xml" version = "0.41.0" @@ -4404,7 +4395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" dependencies = [ "proc-macro2", - "quick-xml 0.41.0", + "quick-xml", "quote", ] @@ -5079,7 +5070,7 @@ dependencies = [ "image", "log", "proptest", - "quick-xml 0.38.4", + "quick-xml", "sha1", "sha2", "thiserror 2.0.21", @@ -5133,7 +5124,7 @@ dependencies = [ "image", "log", "proptest", - "quick-xml 0.38.4", + "quick-xml", "wordcraft-doc", "wordcraft-geom", "zip", diff --git a/Cargo.toml b/Cargo.toml index 79a258648..73cd150c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ harfrust = "0.13" vello_cpu = "0.2" image = { version = "0.25", default-features = false, features = ["png", "jpeg", "webp", "gif", "bmp", "tiff"] } krilla = { version = "0.8", features = ["pdf"] } -quick-xml = "0.38" +quick-xml = "0.41" zip = { version = "3", default-features = false, features = ["deflate"] } cfb = "0.15" # Password-protected (agile-encrypted) Word documents: pure-Rust RustCrypto primitives. diff --git a/crates/docx/src/xml.rs b/crates/docx/src/xml.rs index 5f1d10499..4460a7c25 100644 --- a/crates/docx/src/xml.rs +++ b/crates/docx/src/xml.rs @@ -4,6 +4,7 @@ //! `r:id`, `a:blip`…), so the reader doesn't care which prefixes a producer chose. Names in //! unknown namespaces keep their local name behind a `?:` prefix. +use quick_xml::XmlVersion; use quick_xml::events::Event; use quick_xml::name::ResolveResult; use quick_xml::reader::NsReader; @@ -261,13 +262,13 @@ pub fn parse(bytes: &[u8]) -> Result { if raw_key == b"xmlns" || raw_key.starts_with(b"xmlns:") { continue; } - let (ares, alocal) = r.resolve_attribute(key); + let (ares, alocal) = r.resolver_mut().resolve_attribute(key); let alocal = String::from_utf8_lossy(alocal.as_ref()).into_owned(); let aname = match ares { ResolveResult::Bound(ns) => format!("{}:{alocal}", prefix_for(ns.as_ref())), _ => alocal, }; - let val = match a.unescape_value() { + let val = match a.normalized_value(XmlVersion::Explicit1_0) { Ok(v) => v.into_owned(), Err(_) => String::from_utf8_lossy(&a.value).into_owned(), }; @@ -481,6 +482,18 @@ mod tests { assert!(parse(s.as_bytes()).is_err()); } + /// A start tag with thousands of `xmlns:` declarations is refused instead of allocated + /// (RUSTSEC-2026-0195: unbounded namespace bindings in the resolver). + #[test] + fn too_many_namespace_declarations_is_error() { + let mut s = String::from(""); + assert!(parse(s.as_bytes()).is_err()); + } + #[test] fn garbage_is_error_not_panic() { assert!(parse(b"").is_err()); diff --git a/crates/formats/src/odt.rs b/crates/formats/src/odt.rs index 9bacc65a1..1cbc047db 100644 --- a/crates/formats/src/odt.rs +++ b/crates/formats/src/odt.rs @@ -14,6 +14,7 @@ use std::collections::HashMap; use std::io::{Cursor, Read, Write}; +use quick_xml::XmlVersion; use quick_xml::events::{BytesStart, Event}; use wordcraft_doc::{Align, Document, Rgb}; @@ -659,7 +660,8 @@ fn attrs_of(e: &BytesStart<'_>) -> Attrs { let mut v = Vec::new(); for a in e.attributes().with_checks(false).flatten().take(128) { let k = String::from_utf8_lossy(a.key.as_ref()).into_owned(); - let val = a.unescape_value().map(|c| c.into_owned()).unwrap_or_else(|_| String::from_utf8_lossy(&a.value).into_owned()); + let val = + a.normalized_value(XmlVersion::Explicit1_0).map(|c| c.into_owned()).unwrap_or_else(|_| String::from_utf8_lossy(&a.value).into_owned()); v.push((k, val)); } v