4037b582757f86eed1c5559eca9ccc7af85c1496 braney Fri Aug 21 10:00:49 2026 -0700 lib: add htmlSanitize, an allowlist filter for HTML written elsewhere, refs #38126 htmlSanitize() takes a piece of HTML and returns a copy holding only the elements, attributes and style properties on its lists. An element on the keep list survives with its allowed attributes. A short list of elements that carry nothing for a reader, script and style and form among them, is dropped along with its contents. Every other element loses its tag and keeps its text, so a whole document that somebody saved and pasted in comes out as the article it was meant to be. The lists come from a survey of all 5390 description pages reachable from the public hub list, so they are sized to what hubs actually write. The style attribute is filtered a property at a time, and href and src are checked for a scheme we do not print, after decoding entities and padding. An iframe is kept only when it plays a video from one of a few hosts, and then with a sandbox attribute. htmlSanitizeReport() returns the same copy plus a list of one-line messages naming what came out, for hubCheck to show a hub author. The tokenizer is hand written and forgiving. It never aborts and always returns something, because the HTML it will be handed is often broken. lib/htmlPage.c cannot be reused for this: its parser aborts on bad input. diff --git src/lib/tests/expected/htmlSanitizeTest src/lib/tests/expected/htmlSanitizeTest new file mode 100644 index 00000000000..90d323314cd --- /dev/null +++ src/lib/tests/expected/htmlSanitizeTest @@ -0,0 +1,53 @@ +in : <html><head><title>T</title><meta charset="utf-8"></head><body class="x"><h2>Head</h2><p>Text</p></body></html> +out: <h2>Head</h2><p>Text</p> + +in : <p>before</p><script>alert(1)</script><style>body{visibility:hidden}</style><p>after</p> +out: <p>before</p><p>after</p> + (removed the script element and everything inside it) + (removed the style element and everything inside it) + +in : <p>before</p><script>if (a < b) alert(1) +out: <p>before</p> + (removed the script element and everything inside it) + +in : <div id="top" class="warn" title="t" onclick="alert(1)">text</div> +out: <div id="top" title="t">text</div> + (removed the attribute onclick) + +in : <a href="javascript:alert(1)">one</a> <a href="java	script:alert(1)">two</a> +out: <a>one</a> <a>two</a> + (removed a link that used the javascript: scheme) + +in : <a href="https://genome.ucsc.edu">u</a> <a href="#anchor" target="_blank">a</a> +out: <a href="https://genome.ucsc.edu">u</a> <a href="#anchor" target="_blank" rel="noopener noreferrer">a</a> + +in : <img src="pic.png" alt="a" onerror="alert(1)" width="20"> +out: <img src="pic.png" alt="a" width="20"> + (removed the attribute onerror) + +in : <iframe width="560" src="https://www.youtube.com/embed/abc"></iframe><iframe src="https://example.com/x">fallback</iframe> +out: <iframe width="560" src="https://www.youtube.com/embed/abc" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation"></iframe> + (removed an iframe, we only allow one that plays a video from a site we know) + +in : <p style="color:red;behavior:url(#default#VML);text-align:center;position:fixed">p</p> +out: <p style="color:red;text-align:center;">p</p> + +in : <o:p>word</o:p><vertebrates>more</vertebrates> +out: wordmore + +in : <div><b>bold<p>para +out: <div><b>bold<p>para</p></b></div> + +in : <a href=https://example.com/x\">link text</a> and more text +out: <a href="https://example.com/x\"">link text</a> and more text + +in : <form action="/x"><input name="password"><button>Log in</button></form><p>after</p> +out: <p>after</p> + (removed the form element and everything inside it) + +in : <!DOCTYPE html><!-- <p>hidden</p> --><p>shown</p> +out: <p>shown</p> + +in : <table border="1"><tr><td colspan="2" bgcolor="#eee">cell</td></tr></table> +out: <table border="1"><tr><td colspan="2" bgcolor="#eee">cell</td></tr></table> +