mirror of
https://github.com/lexmount/moli.git
synced 2026-10-30 16:07:24 +00:00
fix(xml): validate numeric references using the XML Char production
Validate XML numeric character references using the XML Char production rather than HTML recovery rules. Keep carriage returns and C1/noncharacter values which XML permits, and reject forbidden controls/surrogates. Parser regressions cover decimal and hexadecimal references and retain strict failures.
This commit is contained in:
@@ -601,6 +601,75 @@ mod tests {
|
||||
use moli_dom::native::{DomHost, NativeDom, NativeNodeId, Node, NodeType};
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn xml_numeric_references_follow_xml_chars_without_html_replacements() {
|
||||
for unit in [
|
||||
0x9, 0xa, 0xd, 0x20, 0x7f, 0x80, 0x85, 0x91, 0x9f, 0xd7ff, 0xe000, 0xfdd0, 0xfdef,
|
||||
0xfffd, 0x10000, 0x1fffe, 0x1ffff, 0x10ffff,
|
||||
] {
|
||||
let expected = char::from_u32(unit).unwrap().to_string();
|
||||
for reference in [format!("&#{unit};"), format!("&#x{unit:X};")] {
|
||||
for quote in ['\'', '"'] {
|
||||
let source = format!(
|
||||
"<root xmlns:p='urn:test' p:v={quote}{reference}{quote}>{reference}</root>"
|
||||
);
|
||||
let dom = XmlParser.parse(
|
||||
Url::parse("https://xml.test/character-reference.xml").unwrap(),
|
||||
source.clone(),
|
||||
);
|
||||
assert!(
|
||||
dom.parse_errors().is_empty(),
|
||||
"{source}: {:?}",
|
||||
dom.parse_errors()
|
||||
);
|
||||
let root = dom.document_element_node_id().unwrap();
|
||||
let element = dom.node(root).and_then(Node::as_element).unwrap();
|
||||
let attribute = element
|
||||
.attributes()
|
||||
.iter()
|
||||
.find(|attribute| attribute.namespace() == "urn:test")
|
||||
.unwrap();
|
||||
assert_eq!(attribute.value(), expected, "{source}");
|
||||
assert_eq!(
|
||||
dom.text_content(root).as_deref(),
|
||||
Some(expected.as_str()),
|
||||
"{source}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xml_numeric_references_reject_values_outside_xml_char() {
|
||||
for value in [
|
||||
"0",
|
||||
"1",
|
||||
"8",
|
||||
"11",
|
||||
"12",
|
||||
"14",
|
||||
"31",
|
||||
"55296",
|
||||
"57343",
|
||||
"65534",
|
||||
"65535",
|
||||
"1114112",
|
||||
"999999999999999999999999999999999999",
|
||||
] {
|
||||
for source in [
|
||||
format!("<root v='&#{value};'/>"),
|
||||
format!("<root>&#{value};</root>"),
|
||||
] {
|
||||
let dom = XmlParser.parse(
|
||||
Url::parse("https://xml.test/invalid-character-reference.xml").unwrap(),
|
||||
source.clone(),
|
||||
);
|
||||
assert!(!dom.parse_errors().is_empty(), "{source}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xml_style_children_finish_at_close() {
|
||||
for namespace in ["http://www.w3.org/1999/xhtml", "http://www.w3.org/2000/svg"] {
|
||||
|
||||
+2
@@ -4,6 +4,8 @@ This directory contains the source of the crates.io `xml5ever` 0.39.0 release.
|
||||
It is patched locally because Moli needs the tokenizer to preserve CDATA as a
|
||||
distinct token, the XML tree builder to surface CDATA nodes to its sink, and
|
||||
namespace declarations to remain available as DOM attributes after binding.
|
||||
Numeric character references use the XML 1.0 Char production, preserving
|
||||
referenced carriage returns and C1 values without HTML's Windows-1252 mapping.
|
||||
|
||||
The upstream source is licensed under MIT or Apache-2.0; see `LICENSE-MIT` and
|
||||
`LICENSE-APACHE` in this directory.
|
||||
|
||||
+11
-16
@@ -237,24 +237,19 @@ impl CharRefTokenizer {
|
||||
}
|
||||
|
||||
fn finish_numeric<Sink: TokenSink>(&mut self, tokenizer: &XmlTokenizer<Sink>) -> Status {
|
||||
fn conv(n: u32) -> char {
|
||||
from_u32(n).expect("invalid char missed by error handling cases")
|
||||
}
|
||||
|
||||
let (c, error) = match self.num {
|
||||
n if (n > 0x10FFFF) || self.num_too_big => ('\u{fffd}', true),
|
||||
0x00 | 0xD800..=0xDFFF => ('\u{fffd}', true),
|
||||
|
||||
0x80..=0x9F => match data::C1_REPLACEMENTS[(self.num - 0x80) as usize] {
|
||||
Some(c) => (c, true),
|
||||
None => (conv(self.num), true),
|
||||
n if !self.num_too_big
|
||||
&& matches!(
|
||||
n,
|
||||
0x9 | 0xa | 0xd | 0x20..=0xd7ff | 0xe000..=0xfffd | 0x10000..=0x10ffff
|
||||
) =>
|
||||
{
|
||||
(
|
||||
from_u32(n).expect("the XML Char production contains only Unicode scalars"),
|
||||
false,
|
||||
)
|
||||
},
|
||||
|
||||
0x01..=0x08 | 0x0B | 0x0D..=0x1F | 0x7F | 0xFDD0..=0xFDEF => (conv(self.num), true),
|
||||
|
||||
n if (n & 0xFFFE) == 0xFFFE => (conv(n), true),
|
||||
|
||||
n => (conv(n), false),
|
||||
_ => ('\u{fffd}', true),
|
||||
};
|
||||
|
||||
if error {
|
||||
|
||||
Reference in New Issue
Block a user