说起来,这么多年,我一直没有彻底搞清楚过。比如,哪些字符需要转义,中文要不要转义,URL 呢?
对于第一个问题:
我在评论功能的代码里面已经解释得比较清楚了(之所以需要这部分代码而没有使用 innerText/innerHTML 的方式让浏览器帮我正确地转义,是因为目前部分内容还是通过手动拼接 HTML 的方式构成的,需要找时间换成 <template/> 以及 ShadowDOM):
- // 把可能的 HTML 特殊字符转义以作为纯文本嵌入到页面中。
- // 单、双引号均没必要转换,任何时候都不会引起歧义。
- const h2t = (h) => {
- const map = {'&': '&', '<': '<', '>': '>'};
- return h.replace(/[&<>]/g, c => map[c]);
- };
- // 转义成属性值。
- // 两种情况:手写和非手写。
- // 手写的时候知道什么时候需要把值用单、双引号包起来,跟本函数无关。
- // 如果是构造 HTML,则(我)总是放在单、双引号中,所以 < > 其实没必要转义,
- // 而如果可能不放在引号中,则需要转义。' " 则总是需要转义。
- // 试了一下在火狐中执行 temp0.setAttribute('title', 'a > b'),不管是查看或者编辑,都没被转义。
- // https://mina86.com/2021/no-you-dont-need-to-escape-that/
- const h2a = (h) => {
- const map = {'&': '&', "'": ''', '"': '"'};
- return h.replace(/[&'"]/g, c => map[c]);
- };
-
对于后面两个问题,一开始觉得按照第一步做完就已经足够了,原因:转义的目的只是为了不导致 HTML 解析的时候出现歧义,并没有其它目的。比如:防止 Attribute.Value 被中的引号/大于/小于符号对标签的错误解析。上面👆代码中的链接非常清楚地解释了什么时候会引起歧义。
但是今天用 Go 的 "html/template" 渲染一段单测的时候,竟然挂了,就是下面这个:
- template.Must(template.New(`t`).Parse(`<span src="{{.}}"></span>`)).Execute(os.Stdout, `/118/我的一个道姑朋友.mp3`)
- template.Must(template.New(`t`).Parse(`<span xxx="{{.}}"></span>`)).Execute(os.Stdout, `/118/我的一个道姑朋友.mp3`)
-
结果如下:
- <span src="/118/%e6%88%91%e7%9a%84%e4%b8%80%e4%b8%aa%e9%81%93%e5%a7%91%e6%9c%8b%e5%8f%8b.mp3"></span>
- <span xxx="/118/我的一个道姑朋友.mp3"></span>
-
这让我有点儿意外,因为以前觉得: URL 在 Attribute 里面根本不会引起歧义,根本没必要转义,所以我就算是手写 HTML 的时候,也不会特意去转义(实际上,不转义大部分时候也不会有问题)。所以我在写单测的时候期待结果也不是转义的结果,然后就挂了。
- This package understands HTML, CSS, JavaScript, and URIs. It adds sanitizing
- functions to each simple action pipeline, so given the excerpt
-
- <a href="/search?q={{.}}">{{.}}</a>
-
- At parse time each {{.}} is overwritten to add escaping functions as necessary.
- In this case it becomes
-
- <a href="/search?q={{. | urlescaper | attrescaper}}">{{. | htmlescaper}}</a>
-
解答了我的疑惑:确实会先 URL 转义,然后 Attribute 转义。 但,只针对特定的 Attribute Name。然后,我找到了这份列表:
- template (stable) → pwd
- /opt/homebrew/Cellar/go/1.22.2/libexec/src/html/template
- template (stable) → cat attr.go | grep contentTypeURL
- "action": contentTypeURL,
- "archive": contentTypeURL,
- "background": contentTypeURL,
- "cite": contentTypeURL,
- "classid": contentTypeURL,
- "codebase": contentTypeURL,
- "data": contentTypeURL,
- "formaction": contentTypeURL,
- "href": contentTypeURL,
- "icon": contentTypeURL,
- "longdesc": contentTypeURL,
- "manifest": contentTypeURL,
- "poster": contentTypeURL,
- "profile": contentTypeURL,
- "src": contentTypeURL,
- "usemap": contentTypeURL,
- "xmlns": contentTypeURL,
-
而 Go 的这份列表,引用自 W3C:
里面清晰地定义了,对于 URL,哪些字符需要转义、如何转义。 也可以参考维斯百科,也解释得很清楚,更简单。