diff --git a/crates/crw-renderer/src/lib.rs b/crates/crw-renderer/src/lib.rs index 762a279d..f16ff0d6 100644 --- a/crates/crw-renderer/src/lib.rs +++ b/crates/crw-renderer/src/lib.rs @@ -4475,6 +4475,60 @@ impl FallbackRenderer { } } +/// Byte offset of the document's real ` Option { + const SKIPPED: [&str; 4] = ["script", "style", "template", "textarea"]; + let b = html.as_bytes(); + // `name` at `b[at..]`, case-insensitive, followed by a tag-name boundary. + let tag_at = |at: usize, name: &str| { + b.get(at..at + name.len()) + .is_some_and(|w| w.eq_ignore_ascii_case(name.as_bytes())) + && matches!( + b.get(at + name.len()), + None | Some(b'>' | b'/' | b' ' | b'\t' | b'\n' | b'\r' | b'\x0c') + ) + }; + let find_from = |from: usize, needle: &[u8]| { + b[from..] + .windows(needle.len()) + .position(|w| w.eq_ignore_ascii_case(needle)) + .map(|i| from + i) + }; + let mut i = 0; + while let Some(off) = b[i..].iter().position(|&c| c == b'<') { + let at = i + off; + if b[at..].starts_with(b"` and `` close too. + i = find_from(at + 2, b"-->")? + 3; + } else if tag_at(at + 1, "body") { + return Some(at); + } else if let Some(name) = SKIPPED.iter().find(|n| tag_at(at + 1, n)) { + // An unterminated raw-text element swallows the rest of the document. + i = find_from(at + 1 + name.len(), format!(" tag"`) is text. + let mut quote = None; + let mut j = at + 1; + while let Some(&c) = b.get(j) { + match (quote, c) { + (None, b'>') => break, + (None, b'"' | b'\'') => quote = Some(c), + (Some(q), _) if c == q => quote = None, + _ => {} + } + j += 1; + } + i = (j + 1).min(b.len()); + } else { + i = at + 1; + } + } + None +} + /// Rough estimate of visible text length in an HTML document. /// Strips tags and collapses whitespace. Used to detect "thin" renders /// where a renderer returned HTML but failed to execute JavaScript. @@ -4488,7 +4542,18 @@ fn html_body_text_len(html: &str) -> usize { // opening tag. `&html[start..end]` then panics with // "byte range starts at 198294 but ends at 197897" and kills the request. // Seen in production 2026-08-11, 9 times in 30 minutes. - let body = if let Some(start) = html.find("`, `", + "", + "", + "", + ] { + let html = format!("{skipped}{real}"); + assert_eq!(find_body_open(&html), html.find("")); + } + + #[test] + fn find_body_open_gives_up_on_an_unterminated_raw_text_element() { + // Nothing after an unclosed