mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
fix: preserve non-HTML angle brackets in sanitize_summary
The previous strip_html_tags() implementation blindly removed all content between angle brackets, mangling legitimate text like Vec<String>, shell redirects (cat < input.txt), and comparison operators in LLM/error output. Replace the naive char-by-char scanner with a regex that only matches known HTML tag names (div, script, img, a, b, etc.), preserving generic angle-bracket content that appears in code snippets and error messages. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
+57
-12
@@ -1988,19 +1988,37 @@ fn sanitize_summary(s: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove HTML/XML tags from a string.
|
||||
/// Remove actual HTML tags from a string while preserving non-HTML angle brackets.
|
||||
///
|
||||
/// Only strips patterns that look like real HTML/XML tags (e.g. `<div>`, `</p>`,
|
||||
/// `<img src=...>`), not generic angle-bracket content like `Vec<String>`,
|
||||
/// `cat < input.txt`, or comparison operators.
|
||||
fn strip_html_tags(s: &str) -> String {
|
||||
let mut result = String::with_capacity(s.len());
|
||||
let mut in_tag = false;
|
||||
for c in s.chars() {
|
||||
match c {
|
||||
'<' => in_tag = true,
|
||||
'>' if in_tag => in_tag = false,
|
||||
_ if !in_tag => result.push(c),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
result
|
||||
use std::sync::LazyLock;
|
||||
|
||||
// Matches opening tags like <div>, <a href="...">, <img src=x onerror=...>,
|
||||
// closing tags like </p>, </script>, and self-closing tags like <br/>.
|
||||
// Does NOT match things like Vec<String>, x<10, or < input.txt because those
|
||||
// don't have a letter immediately after '<' followed by valid tag structure,
|
||||
// or they aren't among recognized HTML tag names.
|
||||
static HTML_TAG_RE: LazyLock<Regex> = LazyLock::new(|| {
|
||||
// Match <tagname ...> or </tagname> where tagname starts with a letter.
|
||||
// We restrict to known HTML tag names to avoid false positives on generic
|
||||
// identifiers like Vec<String>.
|
||||
let tags = "a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|blockquote|\
|
||||
body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|\
|
||||
details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|\
|
||||
form|h[1-6]|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|label|\
|
||||
legend|li|link|main|map|mark|meta|meter|nav|noscript|object|ol|optgroup|\
|
||||
option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|\
|
||||
section|select|slot|small|source|span|strong|style|sub|summary|sup|table|\
|
||||
tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|\
|
||||
video|wbr";
|
||||
Regex::new(&format!(r"(?i)</?(?:{})(?:\s[^>]*)?>", tags))
|
||||
.expect("HTML_TAG_RE is a valid static regex")
|
||||
});
|
||||
|
||||
HTML_TAG_RE.replace_all(s, "").into_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -2574,6 +2592,33 @@ mod tests {
|
||||
assert_eq!(sanitize_summary("<img src=x onerror=alert(1)>"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_summary_preserves_non_html_angle_brackets() {
|
||||
use super::sanitize_summary;
|
||||
|
||||
// Rust/Java generics must pass through unchanged
|
||||
assert_eq!(
|
||||
sanitize_summary("expected Vec<String>"),
|
||||
"expected Vec<String>"
|
||||
);
|
||||
assert_eq!(
|
||||
sanitize_summary("HashMap<String, Vec<u8>>"),
|
||||
"HashMap<String, Vec<u8>>"
|
||||
);
|
||||
|
||||
// Shell redirects must pass through unchanged
|
||||
assert_eq!(sanitize_summary("cat < input.txt"), "cat < input.txt");
|
||||
|
||||
// Comparison operators must pass through unchanged
|
||||
assert_eq!(sanitize_summary("x < 10 && y > 20"), "x < 10 && y > 20");
|
||||
|
||||
// Mixed: real HTML stripped but generics preserved
|
||||
assert_eq!(
|
||||
sanitize_summary("Error in Vec<String>: <b>failed</b>"),
|
||||
"Error in Vec<String>: failed"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_summary_multibyte_truncation() {
|
||||
use super::sanitize_summary;
|
||||
|
||||
Reference in New Issue
Block a user