mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-11 02:19:56 -06:00
Merge pull request #5408 from storytold/fix/sec-svg-css-string-escapes
CSS export: line breaks and angle brackets in names can't end a CSS string
This commit is contained in:
+31
-3
@@ -26,7 +26,28 @@ pub(crate) fn declarations<'a>(props: impl IntoIterator<Item = &'a (&'static str
|
||||
|
||||
/// A CSS string.
|
||||
pub(crate) fn css_string(s: &str) -> String {
|
||||
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
|
||||
quoted(s, '"')
|
||||
}
|
||||
|
||||
/// `s` as a CSS string in `quote`s. A backslash and the quote are escaped, and so are line breaks
|
||||
/// and other control characters (a newline would end the string and let the rest be rules) and
|
||||
/// `<` and `>` (which could end a `<style>` element), as hex escapes.
|
||||
fn quoted(s: &str, quote: char) -> String {
|
||||
let mut out = String::with_capacity(s.len() + 2);
|
||||
out.push(quote);
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'\\' => out.push_str("\\\\"),
|
||||
c if c == quote => {
|
||||
out.push('\\');
|
||||
out.push(c);
|
||||
}
|
||||
c if c.is_control() || matches!(c, '<' | '>') => out.push_str(&format!("\\{:x} ", u32::from(c))),
|
||||
c => out.push(c),
|
||||
}
|
||||
}
|
||||
out.push(quote);
|
||||
out
|
||||
}
|
||||
|
||||
pub(crate) fn blend_css(b: BlendMode) -> &'static str {
|
||||
@@ -108,11 +129,11 @@ fn generic_fallback(family: &str) -> &'static str {
|
||||
/// and the `wdth` setting is `font-stretch` too ([`font_stretch`]).
|
||||
pub(crate) fn font_props(st: &CharStyle, len: &dyn Fn(f64) -> String) -> Props {
|
||||
let mut p = Props::new();
|
||||
// A quoted family escapes `\` and `'`, which would otherwise end the string early.
|
||||
// A quoted family escapes `\`, `'` and line breaks, which would otherwise end the string early.
|
||||
// Only a plain identifier (no leading digit) can be written bare; anything else is quoted.
|
||||
let bare = st.font_family.chars().next().is_some_and(|c| !c.is_ascii_digit() && c != '-')
|
||||
&& st.font_family.chars().all(|c| c.is_alphanumeric() || matches!(c, '-' | '_'));
|
||||
let fam = if !bare { format!("'{}'", st.font_family.replace('\\', "\\\\").replace('\'', "\\'")) } else { st.font_family.clone() };
|
||||
let fam = if !bare { quoted(&st.font_family, '\'') } else { st.font_family.clone() };
|
||||
p.push(("font-family", format!("{fam}{}", generic_fallback(&st.font_family))));
|
||||
p.push(("font-size", len(st.size * st.v_scale / 100.0)));
|
||||
let (weight, italic) = font_descriptor(st);
|
||||
@@ -751,4 +772,11 @@ mod tests {
|
||||
}
|
||||
assert_eq!(w.next_suffix["b"], 20_001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_breaks_and_tags_cannot_end_a_string() {
|
||||
assert!(family("a\n} * {color:red").starts_with(r"'a\a } * {color:red'"), "{}", family("a\n} * {color:red"));
|
||||
assert_eq!(css_string("w\r\"</style>"), r#""w\d \"\3c /style\3e ""#);
|
||||
assert_eq!(css_string("wght"), "\"wght\"");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user