mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-10-09 04:42:30 -04:00
Merge pull request #3609 from lightpanda-io/xml-declaration-validation
xml: validate xml declaration
This commit is contained in:
3 files changed
+48
-8
No files matched your search
@@ -123,13 +123,5 @@ pub fn xmlDocument(frame: *Frame, xml: []const u8) !?*Document.XMLDocument {
|
||||
if (parser.err != null or parser.xml_error or doc_node.firstChild() == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If first node is a `ProcessingInstruction` (e.g. the <?xml?>
|
||||
// declaration), skip it.
|
||||
const first_child = doc_node.firstChild().?;
|
||||
if (first_child.getNodeType() == 7) {
|
||||
_ = try doc_node.removeChild(first_child, frame);
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
@@ -521,6 +521,11 @@
|
||||
'<a>�</a>',
|
||||
'<a><?1x?></a>', // invalid processing instruction target
|
||||
'<?1x?><a/>',
|
||||
'<?xml version="2.0"?><a/>', // only 1.x versions are accepted
|
||||
'<?xml version="10.0"?><a/>',
|
||||
'<?xml version="1."?><a/>',
|
||||
'<?xml version=1.0?><a/>',
|
||||
'<?xml encoding="UTF-8"?><a/>', // version is required
|
||||
]) {
|
||||
testing.expectEqual(bad + ' -> error', bad + (isError(p.parseFromString(bad, 'text/xml')) ? ' -> error' : ' -> ok'));
|
||||
}
|
||||
@@ -529,6 +534,8 @@
|
||||
for (const good of [
|
||||
'<a/>',
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n<a/>\n',
|
||||
"<?xml version = '1.1075' ?><a/>",
|
||||
'<?xml version="1.000" standalone="yes"?><a/>',
|
||||
'\uFEFF<a/>',
|
||||
'<a><![CDATA[<x>]]><!-- c --><?pi d?></a><!-- trailing -->',
|
||||
'<a b="x"y'<©"/>',
|
||||
@@ -548,6 +555,12 @@
|
||||
testing.expectEqual('svg', svg.doctype.name);
|
||||
testing.expectEqual('-//W3C//DTD SVG 1.1//EN', svg.doctype.publicId);
|
||||
testing.expectEqual('svg', svg.documentElement.localName);
|
||||
|
||||
// the XML declaration creates no node, a leading processing instruction does
|
||||
const decl = p.parseFromString('<?xml version="1.0"?><?xml-stylesheet href="a.css"?><a/>', 'text/xml');
|
||||
testing.expectEqual(2, decl.childNodes.length);
|
||||
testing.expectEqual('xml-stylesheet', decl.firstChild.target);
|
||||
testing.expectEqual('xml-stylesheet', p.parseFromString('<?xml-stylesheet href="a.css"?><a/>', 'text/xml').firstChild.target);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -874,6 +874,17 @@ impl<'arena> xml5ever::tokenizer::TokenSink for UnclosedTagSink<'arena> {
|
||||
.parse_error(std::borrow::Cow::Borrowed("Unclosed element at EOF"));
|
||||
}
|
||||
}
|
||||
// The XML declaration isn't a processing instruction, so no node
|
||||
// is created for it. xml5ever doesn't validate it.
|
||||
Token::ProcessingInstruction(pi) if &*pi.target == "xml" => {
|
||||
if !is_valid_xml_declaration(&pi.data) {
|
||||
use xml5ever::tree_builder::TreeSink;
|
||||
self.tb
|
||||
.sink
|
||||
.parse_error(std::borrow::Cow::Borrowed("Invalid XML declaration"));
|
||||
}
|
||||
return xml5ever::tokenizer::ProcessResult::Continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.tb.process_token(token)
|
||||
@@ -884,6 +895,30 @@ impl<'arena> xml5ever::tokenizer::TokenSink for UnclosedTagSink<'arena> {
|
||||
}
|
||||
}
|
||||
|
||||
// The declaration must start with `version="1.x"`: browsers accept any 1.x
|
||||
// (XML 1.0 5th edition's VersionNum is `1.[0-9]+`) and reject everything else.
|
||||
fn is_valid_xml_declaration(data: &str) -> bool {
|
||||
fn trim(s: &str) -> &str {
|
||||
s.trim_start_matches([' ', '\t', '\r', '\n'])
|
||||
}
|
||||
let Some(rest) = trim(data).strip_prefix("version").map(trim) else {
|
||||
return false;
|
||||
};
|
||||
let Some(rest) = rest.strip_prefix('=').map(trim) else {
|
||||
return false;
|
||||
};
|
||||
let Some(quote) = rest.chars().next().filter(|c| *c == '"' || *c == '\'') else {
|
||||
return false;
|
||||
};
|
||||
let Some((version, _)) = rest[1..].split_once(quote) else {
|
||||
return false;
|
||||
};
|
||||
match version.strip_prefix("1.") {
|
||||
Some(minor) => !minor.is_empty() && minor.bytes().all(|b| b.is_ascii_digit()),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
// xml5ever::driver::XmlParser, minus the tree-builder-typed tokenizer so the
|
||||
// UnclosedTagSink can sit in between.
|
||||
struct XmlDocumentParser<'arena> {
|
||||
|
||||
Reference in new issue
Block a user