Documentation
¶
Overview ¶
Command comments renders an untrusted comment: it removes what a comment has no business containing, and turns bare URLs into links.
$ comments <<< 'see https://example.com/x <script>alert(1)</script> and <b>bold</b>' see <a href="https://example.com/x" rel="nofollow noopener" target="_blank">https://example.com/x</a> and <b>bold</b> removed 1 elements and 0 attributes, linkified 1 URLs, refused 0 hrefs
Linkifying is the interesting half, because it is the one place a program has to build markup out of text an attacker chose. The link is markup and the text inside it is not, so the insertion is half of each - which is what lolhtml.EscapeText and lolhtml.EscapeAttribute are for, and why they are exported.
Why the text has to be accumulated ¶
A text node arrives in chunks with no guaranteed boundaries, and a URL can straddle two of them: "https://exa" and "mple.com/x". A per-chunk linkifier finds nothing in either. So the chunks are accumulated to IsLastInTextNode and the whole node is rewritten at its last chunk, which is the discipline the library's documentation gives for anything that matches a pattern in text.
The cost is that the node has to be written back whether or not anything changed, because accumulating means removing the chunks it arrived in. That is why an unchanged comment still comes out of this program byte-identical only if its text has no chunk boundaries the tokenizer chose differently - which is why the test for it compares the parse rather than the bytes.
What is not linkified ¶
Text already inside an <a>. There is no selector for "not inside a link", so the depth of open anchors is counted, and a URL inside one is left alone - otherwise the output has a link inside a link, which a parser resolves by ending the first one and moving the content out.
What is removed ¶
Everything not on a short allow-list: b, i, em, strong, code, br, p, a and blockquote. A comment does not need a table and must not have a script. Attributes go the same way, except href on an anchor, which is kept only when its decoded scheme is http, https or mailto - the decoded form, because a browser decodes "javascript:" before it acts and a check on the raw text does not.