DOCX strict parse: reject an undefined entity and markup after the root

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
fluorine
2026-10-23 16:35:46 -04:00
co-authored by Claude Sonnet 5.5
parent 2fcb589b72
commit 457f7069fd
2 changed files with 25 additions and 4 deletions
+1 -1
View File
@@ -395,7 +395,7 @@ mod tests {
// A hyperlink whose relationship id is written without the prefix: the reader resolves
// it (the alias `El::attr` accepts), so the part it names is not carried over twice.
let named = package(
r#"<w:p><w:r><w:hyperlink id="rIdDoc1"><w:r><w:t>link</w:t></w:r></w:hyperlink></w:r></w:p>"#,
r#"<w:p><w:hyperlink id="rIdDoc1"><w:r><w:t>link</w:t></w:r></w:hyperlink></w:p>"#,
&[("rIdDoc1", "keep.bin")],
&[("word/keep.bin", b"kept")],
);
+24 -3
View File
@@ -346,6 +346,9 @@ fn parse_with(bytes: &[u8], extra: &[(&'static str, &str)], strict: bool) -> Res
};
match ev {
Event::Start(ref e) | Event::Empty(ref e) => {
if strict && root.is_some() && stack.is_empty() {
return Err(DocxError::Xml("more than one root element".into()));
}
count += 1;
if count > MAX_ELEMENTS {
return Err(DocxError::Limit("too many XML elements".into()));
@@ -433,15 +436,17 @@ fn parse_with(bytes: &[u8], extra: &[(&'static str, &str)], strict: bool) -> Res
}
Err(_) => None,
};
if let Some(c) = ch {
push_text(top, &c);
match ch {
Some(c) => push_text(top, &c),
None if strict => return Err(DocxError::Xml("an entity is not defined".into())),
None => {}
}
}
}
Event::Eof => break,
_ => {}
}
if root.is_some() && stack.is_empty() {
if !strict && root.is_some() && stack.is_empty() {
break;
}
}
@@ -567,6 +572,22 @@ pub fn body_ns() -> Vec<(String, String)> {
mod tests {
use super::*;
#[test]
fn strict_parsing_rejects_a_dropped_entity_and_trailing_markup() {
let bad = [&b"<w:t>before &unknown; after</w:t>"[..], b"<a></a><b></b>", b"<a></a><b>"];
for xml in bad {
assert!(parse_strict(xml).is_err(), "{}", String::from_utf8_lossy(xml));
}
assert!(
parse_strict(
b"<a>x &amp; y</a>
"
)
.is_ok()
);
assert!(parse(b"<a></a><b></b>").is_ok(), "lenient parsing still stops at the root");
}
#[test]
fn a_relationship_attribute_is_the_namespaced_one_or_the_alias_of_one() {
// The relationship namespace, and VML's `o:relid`.