说起来,这么多年,我一直没有彻底搞清楚过。比如,哪些字符需要转义,中文要不要转义,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,哪些字符需要转义、如何转义。 也可以参考维斯百科,也解释得很清楚,更简单。