pub(crate) fn string_body(text: &str) -> String {
use std::fmt::Write as _;
let mut out = String::with_capacity(text.len());
for c in text.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'<' => out.push_str("\\3c "),
c if c.is_control() => {
let _ = write!(out, "\\{:x} ", u32::from(c));
}
c => out.push(c),
}
}
out
}
pub(crate) fn font_stack(value: &str) -> Option<&str> {
let families: Vec<&str> = value.split(',').map(str::trim).collect();
(!families.is_empty() && families.iter().all(|f| is_font_family(f))).then_some(value)
}
fn is_font_family(family: &str) -> bool {
if let Some(quoted) = family.strip_prefix('"').and_then(|f| f.strip_suffix('"')) {
!quoted.is_empty()
&& !quoted
.chars()
.any(|c| c.is_control() || matches!(c, '"' | '\\' | '<'))
} else {
let mut chars = family.chars();
matches!(chars.next(), Some(c) if c.is_ascii_alphabetic() || c == '_' || c == '-')
&& chars.all(|c| c.is_ascii_alphanumeric() || matches!(c, ' ' | '-' | '_'))
}
}
pub(crate) fn keyword(value: &str) -> Option<&str> {
let bare = !value.trim().is_empty()
&& value
.chars()
.all(|c| c.is_ascii_alphanumeric() || matches!(c, ' ' | '-' | '.' | '%'));
bare.then_some(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_string_body_cannot_close_its_string() {
assert_eq!(string_body(r#"Quasi "Mono""#), r#"Quasi \"Mono\""#);
assert_eq!(string_body(r"a\b"), r"a\\b");
assert_eq!(string_body("a\nb"), "a\\a b");
assert_eq!(string_body("plain name"), "plain name");
assert_eq!(string_body("</style>"), r"\3c /style>");
assert_eq!(string_body("</style><script>"), r"\3c /style>\3c script>");
}
#[test]
fn only_a_font_family_list_is_a_font_stack() {
for good in [
r#""Young Serif", serif"#,
r#""Departure Mono", monospace"#,
"serif",
"sans-serif",
"system-ui",
r#""Segoe UI", sans-serif"#,
"monospace",
] {
assert_eq!(font_stack(good), Some(good), "{good:?}");
}
for hostile in [
r#""Young Serif", serif; } body { x: y"#,
"monospace}",
r#"", serif"#,
r#""unclosed, serif"#,
r#""a\"b", serif"#,
r#""a<b", serif"#,
"",
" ",
"serif, ",
] {
assert_eq!(font_stack(hostile), None, "{hostile:?}");
}
}
#[test]
fn only_a_bare_keyword_is_a_keyword() {
assert_eq!(keyword("700"), Some("700"));
assert_eq!(keyword("100 900"), Some("100 900"));
assert_eq!(keyword("oblique 10deg"), Some("oblique 10deg"));
for hostile in ["700; } body { x: y", "normal}", "", " ", "a/*"] {
assert_eq!(keyword(hostile), None, "{hostile:?}");
}
}
}