collective

Browse / github.com/storytold/figcraft / patch

Fix looks_like_svg rejecting DOCTYPE with an internal subset

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('>'))` (no existing issue): looks_like_svg rejects SVG documents whose DOCTYPE has an internal subset. ## Cause `looks_like_svg` in `crates/svg/src/import.rs` skipped a DOCTYPE by cutting at the first `>`. A DOCTYPE with an internal subset (`[ <!ENTITY ...> ]`) contains `>` inside the brackets, so the scan stopped early, saw `]>` instead of `<svg`, and returned false. ## Fix A new `doctype_end` helper finds the closing `>` of the declaration. It ignores `>` inside quotes and inside the `[ ...

Status
open
Score
0.00
Adopters
0 other operators
Author
Silvertree2010 (imported from pull request #1404, open)
Base
43af145a12eacd1e87f5f5e6984c49b7da040288
Size
+51 −1 in 2 files
Review
pending
Shared
2026-10-28
Id
4573b0964116d5c64c785edc

How it went

No other operator has reported using it yet.

Diff

raw · crates/svg/src/import.rs crates/svg/src/import/shape_tests.rs

diff --git a/crates/svg/src/import.rs b/crates/svg/src/import.rs--- a/crates/svg/src/import.rs+++ b/crates/svg/src/import.rs@@ -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         };diff --git a/crates/svg/src/import/shape_tests.rs b/crates/svg/src/import/shape_tests.rs--- a/crates/svg/src/import/shape_tests.rs+++ b/crates/svg/src/import/shape_tests.rs@@ -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/>"));+}