From 05017111baa43019dbb4c6f5d2f886389d2ef636 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 19 Oct 2026 20:04:55 +0000 Subject: [PATCH] Filter control characters in SVG export title and description Found while testing crates/svg/src/export.rs:138 and :143 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/svg/src/export.rs | 4 ++-- crates/svg/tests/title_control_chars.rs | 26 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 crates/svg/tests/title_control_chars.rs diff --git a/crates/svg/src/export.rs b/crates/svg/src/export.rs index 661b6381e..1f9f79ac0 100644 --- a/crates/svg/src/export.rs +++ b/crates/svg/src/export.rs @@ -135,12 +135,12 @@ fn write(doc: &Document, opts: &ExportOptions, native: Option<&[u8]>, id_prefix: } if !doc.title.is_empty() { out.push_str(&w.indent(1)); - out.push_str(&format!("{}{nl}", xml_escape(&doc.title))); + out.push_str(&format!("{}{nl}", xml_text(&doc.title))); } // File Info's description. if !doc.metadata.description.trim().is_empty() { out.push_str(&w.indent(1)); - out.push_str(&format!("{}{nl}", xml_escape(&doc.metadata.description))); + out.push_str(&format!("{}{nl}", xml_text(&doc.metadata.description))); } // Window › SVG Interactivity › Link JavaScript Files. for file in &doc.svg_scripts { diff --git a/crates/svg/tests/title_control_chars.rs b/crates/svg/tests/title_control_chars.rs new file mode 100644 index 000000000..c7f842c0b --- /dev/null +++ b/crates/svg/tests/title_control_chars.rs @@ -0,0 +1,26 @@ +// Integration tests: unwrapping and panicking on failure is fine here, unlike in shipped code (AGENTS.md › Robustness). +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] +use vectorcraft_doc::Document; +use vectorcraft_svg::{ExportOptions, export, import}; + +#[test] +fn title_and_description_control_chars_are_filtered() { + let mut d = Document::new(200.0, 200.0); + d.title = "Poster\u{b}v2".into(); + d.metadata.description = "a\u{1}b".into(); + let svg = export(&d, &ExportOptions::default()); + assert!(import(&svg).is_ok(), "export wrote invalid XML: {svg}"); + assert!(svg.contains("Poster v2")); + assert!(svg.contains("ab")); +} + +#[test] +fn title_and_description_markup_is_escaped() { + let mut d = Document::new(200.0, 200.0); + d.title = "A & ".into(); + d.metadata.description = "x & y".into(); + let svg = export(&d, &ExportOptions::default()); + assert!(import(&svg).is_ok(), "export wrote invalid XML: {svg}"); + assert!(svg.contains("A & <B>")); + assert!(svg.contains("x & y")); +}