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
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import html
|
||||||
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
|
|
||||||
@@ -123,23 +125,38 @@ def _attachment_text(store: Store, item_key: str) -> str:
|
|||||||
return "\n\n".join(parts)
|
return "\n\n".join(parts)
|
||||||
|
|
||||||
|
|
||||||
|
_TAG_RE = re.compile(r"<[^>]+>")
|
||||||
|
|
||||||
|
|
||||||
def _strip_html_wrapper(text: str) -> str:
|
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>``
|
FR TXT attachments are HTML-wrapped: ``<html><head>...<body><pre>``
|
||||||
then the plain text, closed with ``</pre></body></html>``. Strip
|
then the plain text, closed with ``</pre></body></html>``. Strip
|
||||||
everything through the first ``<pre>`` and any trailing close tags;
|
everything through the first ``<pre>`` and any trailing close tags;
|
||||||
text without that wrapper passes through unchanged.
|
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>")
|
idx = text.find("<pre>")
|
||||||
if idx == -1:
|
if idx == -1:
|
||||||
return text
|
body = text
|
||||||
body = text[idx + len("<pre>") :]
|
else:
|
||||||
stripped = body.rstrip()
|
body = text[idx + len("<pre>") :]
|
||||||
for suffix in ("</pre></body></html>", "</pre>"):
|
stripped = body.rstrip()
|
||||||
if stripped.endswith(suffix):
|
for suffix in ("</pre></body></html>", "</pre>"):
|
||||||
return stripped[: -len(suffix)]
|
if stripped.endswith(suffix):
|
||||||
return body
|
body = stripped[: -len(suffix)]
|
||||||
|
break
|
||||||
|
body = _TAG_RE.sub("", body)
|
||||||
|
return html.unescape(body)
|
||||||
|
|
||||||
|
|
||||||
def _rule_text(store: Store, item_key: str) -> str:
|
def _rule_text(store: Store, item_key: str) -> str:
|
||||||
|
|||||||
@@ -145,6 +145,27 @@ class TestRuleDocs:
|
|||||||
"item_key": rule_key,
|
"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):
|
def test_pdf_only_falls_back_to_extract_attachment(self, store, rule_key, tmp_path):
|
||||||
pdf = tmp_path / "rule.pdf"
|
pdf = tmp_path / "rule.pdf"
|
||||||
pdf.write_bytes(b"%PDF-1.4 fake")
|
pdf.write_bytes(b"%PDF-1.4 fake")
|
||||||
|
|||||||
Reference in New Issue
Block a user