开发
HTML 实体编解码
真正要紧的只有五个字符:与号、尖括号、双引号和单引号。在属性值或文本节点里漏掉任何一个,标签结构就会断——而且往往是静默地断,这比直接报错更麻烦。
怎么用
- 把文本或 HTML 片段粘贴进来。
- 点「编码」转义这五个特殊字符。
- 点「解码」把实体还原成普通字符。
- 复制结果粘回模板或 CMS 字段。
转义不是过滤
转义是把字符变成「显示出来」而不是「被解释执行」。过滤是删掉你不想要的内容。两者解决不同的问题:把 <script> 转义会让它变成可见文本,这对评论框是对的,对需要接受标记的字段就是错的。混淆这两件事正是大多数 XSS 漏洞的根源——因为一个只挡掉某种写法的过滤器,会放过另外十几种写法。
该转义什么,取决于上下文
在 HTML 文本节点里,要紧的是 < 和 &。在带引号的属性里,引号字符也要紧。在页面内的 JavaScript 字符串里,闭合标签无所谓,反斜杠要紧。在 URL 里,则要改用百分号编码。不存在一个「万能的转义函数」在任何上下文里都正确——这也是模板引擎要替你跟踪上下文,而不是只暴露一个通用辅助函数的原因。
双重转义,以及怎么认出来
页面在本该是 & 的地方显示 &,说明文本被转义了两次:存的时候一次,渲染的时候又一次。解一次得到 &,这是对的;解两次会把本来合法的 < 变成真正的 <——转义就是这样被「解回去」的。
一般原则是:在输出时转义,不要在输入时转义。存原文、在进入 HTML 的那一刻转义,数据在其他地方都还能用;存转义后的文本,则每个使用方都得猜它是不是已经被编码过。
常见问题
会转义哪些字符?
与号、小于号、大于号、双引号和单引号。其余字符(包括中文和带重音的字母)保持原样,因为 UTF-8 本身就能处理。
单引号为什么用数字形式?
数字形式 ' 浏览器和校验器都认。具名实体 ' 在 XHTML 和 HTML5 里合法,但 HTML 4 不支持,输出可能进老模板时数字形式更稳妥。
解码不可信内容安全吗?
解码结果只是放进文本域,不会执行脚本也不会加载图片——因为解析出来的是一个游离元素,从未插入页面。
存数据库前要先转义吗?
不要。存原始文本,渲染时再转义。入库时转义会导致双重转义,输出变成没人看得懂的乱码。
该用命名实体还是数字实体?
有命名实体且被广泛支持的就用命名(<、 );ASCII 之外的字符用数字,因为绝大多数字符根本没有命名实体。
和普通空格一样吗?
不一样。它是不间断空格:会阻止在该处换行,也不会被 HTML 的空白折叠规则压掉。拿它当缩进用,是导致文字选中错乱的常见原因。