mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-09 09:29:44 -06:00
Drop invalid XML characters from SVG link URLs and aria-labels
Found while testing crates/svg/src/export/nodes.rs:492 (Writer::node, `xlink:href` built with xml_escape) 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:
co-authored by
Claude Haiku 5.5
parent
4ee0ef231a
commit
6ecf286f6b
@@ -1,4 +1,4 @@
|
||||
use super::output::{sanitize_id, valid_xml_value, xml_attr_escape};
|
||||
use super::output::{sanitize_id, valid_xml_value, xml_attr_escape, xml_attr_valid};
|
||||
use super::paint::{StrokePlan, has_raster, pixel_gradient};
|
||||
use super::text::OUTLINE_TOLERANCE;
|
||||
use super::{
|
||||
@@ -489,7 +489,10 @@ impl Writer<'_> {
|
||||
self.layer_nest += layer;
|
||||
match n.url().filter(|_| n.visible && !self.anonymous) {
|
||||
Some(url) => {
|
||||
self.line(&format!("<a xlink:href=\"{}\">", xml_escape(url)));
|
||||
if !valid_xml_value(url) {
|
||||
self.warn("Link URLs containing invalid XML characters had those characters removed");
|
||||
}
|
||||
self.line(&format!("<a xlink:href=\"{}\">", xml_attr_valid(url)));
|
||||
self.depth += 1;
|
||||
self.node_body(n);
|
||||
self.depth -= 1;
|
||||
|
||||
@@ -19,6 +19,12 @@ pub(super) fn xml_text(s: &str) -> String {
|
||||
xml_escape(&s)
|
||||
}
|
||||
|
||||
/// `s` without the characters XML 1.0 does not allow, ready for a double-quoted attribute.
|
||||
pub(super) fn xml_attr_valid(s: &str) -> String {
|
||||
let s: String = s.chars().filter(|c| valid_xml_value(c.encode_utf8(&mut [0; 4]))).collect();
|
||||
xml_attr_escape(&s)
|
||||
}
|
||||
|
||||
pub(super) fn valid_xml_value(s: &str) -> bool {
|
||||
s.chars().all(|c| matches!(c, '\t' | '\n' | '\r' | '\u{20}'..='\u{d7ff}' | '\u{e000}'..='\u{fffd}' | '\u{10000}'..='\u{10ffff}'))
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::output::xml_text;
|
||||
use super::output::{xml_attr_valid, xml_text};
|
||||
use super::{
|
||||
Affine, AppearanceItem, Arc, BTreeSet, BezPath, BlendMode, CharStyle, FillRule, FontDb, FontFace, GradientKind, HashMap, Justify, Node, NodeId,
|
||||
NodeKind, Paint, PathData, Props, Rect, StrokeAlign, Styling, TextKind, TextObject, Writer, base64_encode, css, css_string, fmt_num,
|
||||
@@ -163,7 +163,7 @@ impl Writer<'_> {
|
||||
// Glyphs bent along a path (joined scripts on a curve) can only be outlines: the group
|
||||
// names its text for screen readers and search.
|
||||
let label = if lay.glyphs.iter().any(|g| g.bend.is_some()) {
|
||||
format!(" role=\"img\" aria-label=\"{}\"", xml_escape(&t.plain_text()))
|
||||
format!(" role=\"img\" aria-label=\"{}\"", xml_attr_valid(&t.plain_text()))
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
@@ -277,3 +277,17 @@ fn invalid_symbol_name_alias_does_not_break_xml() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn link_url_with_invalid_xml_characters_does_not_break_export() {
|
||||
let mut doc = import(&svg(r#"<rect id="shape" width="20" height="20"/>"#)).unwrap();
|
||||
let id = named(&doc, "shape").id;
|
||||
doc.node_mut(id).unwrap().edit_attrs(|a| a.url = "http://a/\u{1}b\u{ffff}".into());
|
||||
for styling in styles() {
|
||||
let out = export_full(&doc, &ExportOptions { styling, ..Default::default() }, None);
|
||||
let parsed = xml(&out.svg);
|
||||
let a = parsed.descendants().find(|n| n.has_tag_name("a")).unwrap();
|
||||
assert_eq!(a.attribute(("http://www.w3.org/1999/xlink", "href")), Some("http://a/b"));
|
||||
assert!(out.warnings.iter().any(|w| w.contains("invalid XML characters")));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user