// SPDX-License-Identifier: GPL-3.0-or-later //! Link rewriting for mirrored HTML and CSS. //! //! HTML is scanned with a small tokenizer following the HTML5 tokenizer's //! rules for what matters here: tag and attribute syntax (quoted, unquoted //! and valueless attributes), comments, doctype and other `` elements and `style` //! attributes. CSS files get the `url(...)` rewrite only, with the same //! matching as the Go version's regular expression //! `(?i)(url\(\s*["']?)([^"')\s]+)`. Character references in attribute //! values are decoded before a URL is parsed: numeric ones and the named //! `amp`, `lt`, `gt`, `quot`, `apos` and `nbsp`; other named references //! are left as written. /// Something the tokenizer found. #[derive(Debug, PartialEq, Eq)] pub enum Item { /// An attribute value on a start tag: lower-case tag and attribute /// names, the value's byte span, and whether it was quoted. Attr { tag: String, name: String, start: usize, end: usize, quoted: bool, }, /// The raw text inside a `"), "" ); assert_eq!( rw(r#"
"#), r#"
"# ); let mut m = |s: &str| Some(format!("<{s}>")); assert_eq!( rewrite_css(b"a{b:url(x)} c{d:url()} url(\"y\")", false, &mut m), b"a{b:url()} c{d:url()} url(\"\")" ); } #[test] fn base_href_and_escaping() { let mut seen = Vec::new(); let out = rewrite_html( br#""#, &mut |b| { seen.push(b.to_string()); Some("p.html".into()) }, &mut upper, ); assert_eq!(seen, ["http://h/d/"]); assert_eq!(out, br#""#); let out = rewrite_html(b"", &mut |_| None, &mut |_| Some("a b\"&".into())); assert_eq!(out, br#""#); // Non-UTF-8 bytes around and inside values survive. let latin1 = b"

caf\xe9

"; assert_eq!(rewrite_html(latin1, &mut |_| None, &mut |_| None), latin1); assert_eq!(unescape("a&b//<&unknown;'&apos"), "a&b//<&unknown;'&apos"); } #[test] fn unterminated_input() { for s in ["", "e"#, " ", ]; let mut rng = crate::rng::os(); let alphabet = b"<>/!-='\" \tabcdefhrsxyzlptu()&;#"; for i in 0..20000 { let mut page: Vec = seeds[i % 2].as_bytes().to_vec(); for _ in 0..rng.below(8) { let at = rng.below(page.len() as u64 + 1) as usize; match rng.below(3) { 0 => page.insert(at, alphabet[rng.below(alphabet.len() as u64) as usize]), 1 if at < page.len() => { page.remove(at); } _ => page.truncate(at), } } if i % 3 == 0 { page.push(0xff); } assert_eq!(rewrite_html(&page, &mut |_| None, &mut |_| None), page); // Rewriting everything must not panic either. let _ = rewrite_html(&page, &mut |b| Some(b.into()), &mut |v| Some(format!("{v}&x"))); let _ = tokenize(&page); } } }