fix(llm): strip inline HTML + entities from rule text (refs #595)
This commit is contained in:
@@ -15,6 +15,8 @@ so parsing the comment id is the reliable path to a comment's docket.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import html
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Iterator
|
||||
|
||||
@@ -123,23 +125,38 @@ def _attachment_text(store: Store, item_key: str) -> str:
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
_TAG_RE = re.compile(r"<[^>]+>")
|
||||
|
||||
|
||||
def _strip_html_wrapper(text: str) -> str:
|
||||
"""Drop the FR ``.txt`` attachment's HTML wrapper, if present.
|
||||
"""Drop the FR ``.txt`` attachment's HTML wrapper and inline markup.
|
||||
|
||||
FR TXT attachments are HTML-wrapped: ``<html><head>...<body><pre>``
|
||||
then the plain text, closed with ``</pre></body></html>``. Strip
|
||||
everything through the first ``<pre>`` and any trailing close tags;
|
||||
text without that wrapper passes through unchanged.
|
||||
|
||||
The FR text itself also carries inline HTML — e.g. Cloudflare's
|
||||
obfuscated ``<a href="/cdn-cgi/l/email-protection#...">`` mailto
|
||||
anchors and page-break anchors. Those are stripped with a plain
|
||||
``<[^>]+>`` regex (this is a controlled, known corpus — not
|
||||
attacker-supplied HTML — so no HTML-parsing dependency is needed),
|
||||
keeping the anchor's inner text. HTML entities (``&`` etc.) are
|
||||
then unescaped. Plain-text markers like ``[[Page NNNNN]]`` contain no
|
||||
angle brackets and pass through untouched.
|
||||
"""
|
||||
idx = text.find("<pre>")
|
||||
if idx == -1:
|
||||
return text
|
||||
body = text[idx + len("<pre>") :]
|
||||
stripped = body.rstrip()
|
||||
for suffix in ("</pre></body></html>", "</pre>"):
|
||||
if stripped.endswith(suffix):
|
||||
return stripped[: -len(suffix)]
|
||||
return body
|
||||
body = text
|
||||
else:
|
||||
body = text[idx + len("<pre>") :]
|
||||
stripped = body.rstrip()
|
||||
for suffix in ("</pre></body></html>", "</pre>"):
|
||||
if stripped.endswith(suffix):
|
||||
body = stripped[: -len(suffix)]
|
||||
break
|
||||
body = _TAG_RE.sub("", body)
|
||||
return html.unescape(body)
|
||||
|
||||
|
||||
def _rule_text(store: Store, item_key: str) -> str:
|
||||
|
||||
@@ -145,6 +145,27 @@ class TestRuleDocs:
|
||||
"item_key": rule_key,
|
||||
}
|
||||
|
||||
def test_inline_html_and_entities_stripped(self, store, rule_key, tmp_path):
|
||||
txt = tmp_path / "2025-13271.txt"
|
||||
txt.write_text(
|
||||
"<html><head><title>x</title></head><body><pre>\n"
|
||||
"[[Page 12345]]\n"
|
||||
'Contact us at <a href="/cdn-cgi/l/email-protection#x">'
|
||||
"someone</a> regarding CMS & PFS.\n"
|
||||
"</pre></body></html>"
|
||||
)
|
||||
store.attach_file(rule_key, txt)
|
||||
|
||||
docs = list(iter_rule_docs(store))
|
||||
|
||||
assert len(docs) == 1
|
||||
text = docs[0].text
|
||||
assert "<a href=" not in text
|
||||
assert "</a>" not in text
|
||||
assert "someone" in text
|
||||
assert "CMS & PFS" in text
|
||||
assert "[[Page 12345]]" in text
|
||||
|
||||
def test_pdf_only_falls_back_to_extract_attachment(self, store, rule_key, tmp_path):
|
||||
pdf = tmp_path / "rule.pdf"
|
||||
pdf.write_bytes(b"%PDF-1.4 fake")
|
||||
|
||||
Reference in New Issue
Block a user