关于 HTML 实体编码解码
HTML 实体编解码工具会把那些否则会被当成标记的字符转义掉,也能把一串实体还原成它们代表的字符。编码使用浏览器自带的文本序列化逻辑,结果就是浏览器认为在 HTML 文本节点中安全的形态;解码使用浏览器的解析器,因此具名、十进制和十六进制引用都会按页面里的规则解析。
转义不是字符串的属性,而是「它要去哪里」的属性:HTML 文本节点、带引号的属性、脚本、URL 和 SQL 语句各需要不同的处理。本工具精确覆盖第一种场景,对第二种只覆盖了一部分——把输出粘进属性之前,这一点值得先知道。反方向同样重要:从订阅源、CMS 字段或抓取页面里取出的文本,常常多编码了一层。
如何使用这个开发者工具
- 选择「编码」把字符转成实体,或选择「解码」把实体转回字符。
- 粘贴你要处理的片段、订阅源字段或代码段。
- 执行转换后逐字符对照两个面板,重点看 & 符号和各种引号。
- 把结果复制到它本来要去的上下文中;如果这个值会被插入属性或脚本,请交给真正的模板引擎处理,而不是这个工具。
- 解码时先跑一遍再看结果:输出里仍然带实体,说明这个值被编码了不止一次。
实例:把一段代码转义成可展示的文本
一段要在文章里原样显示的 HTML 片段,里面同时有标签、小于号、& 符号和中文,正好覆盖了编码器会动和不会动的两类字符。
<p class="tip">价格 < 100 元 & 包邮</p><p class="tip">价格 < 100 元 & 包邮</p>注意两点:属性里的双引号原样保留了,因为在 HTML 文本节点中双引号无法结束任何东西;而中文字符也没有被转义——只要页面声明了 UTF-8,它们根本不需要实体。真正被处理的只有会改变解析结果的 < 、> 和 &。
这个编码器会改什么,以及为什么重要
前三行在任何 HTML 上下文中都是必须的,其余几行解释那些容易让人意外的取舍。
| 字符 | 编码为 | 必须转义的场景 | 说明 |
|---|---|---|---|
| & | & | 文本与属性 | 必须第一个处理,否则会把其他实体再转义一次 |
| < | < | 文本与属性 | 它会开启一个标签 |
| > | > | 文本 | 并非严格必须,但已是惯例 |
| ' | ' | 单引号属性 | 由本工具额外补上,浏览器序列化器不管它 |
| " | 原样保留 | 双引号属性 | 输出要进属性时,请自行转义 |
| U+00A0 | | 哪里都不必须,但很有用 | 让粘贴进来的隐形字符现形 |
| é、中、🚀 | 原样保留 | 无 | UTF-8 页面不需要为它们写实体 |
常见问题排查
编码后的值把 HTML 属性搞坏了。
成因 文本节点序列化不转义双引号,所以值里只要有一个双引号,双引号属性就会提前结束。本工具转义单引号,但不转义双引号。
解决办法 要么把属性改用单引号包裹(输出已经处理过单引号),要么在插入前把双引号替换成对应的具名引用。更好的做法是把属性值的转义交给模板引擎,它知道自己正在往哪个上下文里写。
用户在页面上看到的是 & 或 <b> 而不是预期的字符。
成因 这个值被转义了两次:一次手工,一次由框架在渲染时完成。第一次留下的每个 & 符号,在第二次里又被转义了一遍。
解决办法 只在值进入 HTML 的那个边界上转义一次。这里的解码按设计只做一趟,所以反复解到输出不再变化为止:多解一趟,就意味着多了一层不请自来的转义。
往返一圈之后文本变了意思,例如 1 < 2 变成了 1 < 2。
成因 HTML 保留了一份历史遗留的具名引用清单,即使不写分号解析器也会解析,其中就包括 lt、gt、amp、copy、reg。& 后面跟上这些名字,不管你是否有意,都会被解码。
解决办法 实体一律以分号结尾;并且在值进入 HTML 之前,把每一个字面 & 都转义成实体。这正是「& 必须第一个处理」不是可选项的原因。
解码得到的字符串,在看起来该相等的比较里不相等。
成因 不间断空格实体解码后是 U+00A0,它和普通空格长得一模一样,码点却不同,于是相等判断、关键词匹配和去空白的行为全都不一样。
解决办法 解码之后统一做一次空白规范化,把 U+00A0 替换成普通空格——前提是这个值本来就该是纯文本。
适合用这个工具的场景
- 把代码片段或标签名处理成可以在文章、文档页里原样显示的文本。
- 解码从 RSS 订阅源、API 字段或 CMS 导出内容里取出的标题和摘要。
- 线上渲染出问题时,判断某个值到底被转义了一次还是两次。
- 让隐形字符现形,尤其是从文档里粘过来的不间断空格。
应该换用别的工具的场景
- 让不可信输入变安全。这里只覆盖 HTML 文本上下文;属性、脚本块、样式块和 URL 各有各的转义规则,而清除危险标记是消毒器的活。
- 为 URL 做转义。百分号编码是完全不同的体系,请用 URL 编解码工具。
- 为 XML 做转义。XML 只定义了五个具名实体, 在那里根本不合法;请用 XML 格式化工具,并改写成数字引用。
隐私与数据处理
两个方向都在当前浏览器标签页内、使用从未插入页面的游离 DOM 节点完成,因此不会执行、不会发请求、也不会上传任何内容。解码包含 script 标签的片段在这里是安全的,字符串被当作文本而非标记来解析。但这份安全只属于本页面,并不代表你把同样的值粘进某个应用之后会发生什么。
相关开发者工具
常见问题
做了实体编码就能防住 XSS 吗?
单靠它不行。它只能阻止 HTML 文本上下文里的标记被解析,同一个值放进属性、内联脚本或 URL 时需要不同处理。真正的防线是按上下文选择输出编码,富文本再叠加一层消毒器。
为什么双引号没有被编码?
因为编码器是按 HTML 文本节点序列化的,而在文本节点里双引号没有特殊含义。它只有在双引号属性中才要紧,而那不是本工具针对的上下文。
该用具名实体还是数字引用?
对解析器来说两者等价。大家都认识的那几个用具名更易读;数字引用无歧义且对任何码点都可用,本工具写单引号时用的就是数字引用。
中文和带重音的字符需要转成实体吗?
只要页面声明了 UTF-8 就不需要。给这类字符写实体是单字节编码年代的遗留做法,只会让标记更大、更难读。
在这里解码含 script 标签的字符串危险吗?
不危险。解码是在一个游离的 textarea 中进行的,它的内容按纯文本处理,不会创建任何元素,也不会插入本页面。当然,解码出来的字符串放到别处仍然可能是危险的。
为什么有些文档里有 ' 而这里的输出没有?
它在 XML 和 HTML5 中有定义,但 HTML 4 没有,所以老工具会避开它。表示撇号的数字引用在任何地方都成立,因此是更稳妥的选择。