Fix looks_like_svg rejecting DOCTYPE with an internal subset

Found while testing crates/svg/src/import/mod.rs:723-740 (looks_like_svg, the `<!` arm using s.find('>'))

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:
NetterSilverandClaude Haiku 5.5 committed 2026-10-27 04:13:59 +01:00
1 parent 43af145a12
commit 213cc75ff2
2 files changed
+51 -1

No files matched your search

+40 -1
View File
@@ -718,6 +718,45 @@ fn options(data: &[u8], limits: budget::Limits) -> usvg::Options<'static> {
o
}
/// Length of the `<!...>` declaration at the start of `s`, skipping `>` inside quotes and an
/// internal subset (`[ ... ]`).
fn doctype_end(s: &str) -> Option<usize> {
let mut quote = None;
let mut depth = 0u32;
let mut skip_to = 0;
for (i, c) in s.char_indices() {
if i < skip_to {
continue;
}
if quote.is_none() && depth > 0 {
// Comments and processing instructions in the subset may hold quotes and brackets.
let rest = &s[i..];
let close = if rest.starts_with("<!--") {
Some("-->")
} else if rest.starts_with("<?") {
Some("?>")
} else {
None
};
if let Some(close) = close {
let len = rest.find(close)? + close.len();
skip_to = i + len;
continue;
}
}
match (quote, c) {
(Some(q), _) if c == q => quote = None,
(Some(_), _) => {}
(None, '"' | '\'') => quote = Some(c),
(None, '[') => depth += 1,
(None, ']') => depth = depth.saturating_sub(1),
(None, '>') if depth == 0 => return Some(i + 1),
_ => {}
}
}
None
}
/// Whether `text` (from the clipboard, say) is an SVG document: `<svg` after an optional byte
/// order mark, XML declaration, comments, doctype and whitespace.
pub fn looks_like_svg(text: &str) -> bool {
@@ -731,7 +770,7 @@ pub fn looks_like_svg(text: &str) -> bool {
} else if s.starts_with("<!--") {
s.find("-->").map(|i| i + 3)
} else if s.starts_with("<!") {
s.find('>').map(|i| i + 1)
doctype_end(s)
} else {
None
};
+11
View File
@@ -107,3 +107,14 @@ fn layer_names_decode_id_escapes() {
assert_eq!(layer_name("", "Group"), "Group");
assert_eq!(layer_name("é_x41_", "Group"), "éA");
}
#[test]
fn doctype_with_internal_subset_is_still_svg() {
let s = "<?xml version=\"1.0\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\" [\n\t<!ENTITY ns_svg \"http://www.w3.org/2000/svg\">\n]>\n<svg xmlns=\"&ns_svg;\" width=\"10\" height=\"10\"/>";
assert!(super::looks_like_svg(s));
let quoted = "<!DOCTYPE svg [ <!ENTITY a \"x>y]\"> ]><svg/>";
assert!(super::looks_like_svg(quoted));
let commented = "<!DOCTYPE svg [ <!-- don't ] > --> <!ENTITY a \"b\"> ]><svg/>";
assert!(super::looks_like_svg(commented));
assert!(!super::looks_like_svg("<!DOCTYPE html [ <svg/> ]><html/>"));
}