同时显示四种编码,各自标明用途
组件编码用于单个值,完整 URL 编码用于拼好的链接,表单编码用于浏览器提交的任何内容,RFC 3986 严格编码用于签名。它们在空格、加号、斜杠、感叹号、单引号、波浪号和星号的处理上各不相同,而每一处分歧都曾破坏过某人的查询字符串。把四种放在一起看,比记住哪个函数做什么要快。
正确的百分号编码不止一种,选错了就会悄无声息地破坏查询字符串。一次看全四种,安全解码并指出错误的原因和位置,还能把任何链接拆分为协议、主机、路径、查询和片段。
34 个字符(38 个 UTF-8 字节)使用组件编码后变为 70 个字符。
编码一个值和编码一个 URL 不是一回事,所以下面给出四个答案,而不是一个。
34 / 100,000 个字符
不上传任何内容,也不访问任何链接。
用于 URL 的单个部分:一个查询值、一个路径段、一个片段。它会转义分隔符 / ? : @ & = + $ 和 #,所以包含其中任何一个的值都无法跳出自己的位置。
用于已经拼好的 URL。它不改动保留分隔符,结构因此得以保持,这也是它无法保护含有和号的值的原因。
此处与组件编码不同HTML 表单和 URLSearchParams 生成的就是这种编码。空格会变成加号而不是 %20,而正是这一处差异,会在两种约定相遇时悄无声息地破坏数据。
此处与组件编码不同只有字母、数字和 - . _ ~ 这四个符号保持不变。适用于 OAuth 1.0 签名、AWS Signature Version 4,以及任何签名必须逐字节匹配的场景。
此处与组件编码相同一个百分号转义承载的是一个字节,而不是一个字符。ASCII 以外的字符都会变成两个、三个或四个转义,所以一个带重音的单词长度可能变成三倍,一个表情符号会变成十二个字符。
| 字符 | 码位 | UTF-8 字节 | 编码后 |
|---|---|---|---|
| C | U+0043 | 43 | C |
| a | U+0061 | 61 | a |
| f | U+0066 | 66 | f |
| é | U+00E9 | C3 A9 | %C3%A9 |
| SP | U+0020 | 20 | %20 |
| c | U+0063 | 63 | c |
| r | U+0072 | 72 | r |
| è | U+00E8 | C3 A8 | %C3%A8 |
| m | U+006D | 6D | m |
| e | U+0065 | 65 | e |
| SP | U+0020 | 20 | %20 |
| & | U+0026 | 26 | %26 |
| SP | U+0020 | 20 | %20 |
| c | U+0063 | 63 | c |
| r | U+0072 | 72 | r |
| o | U+006F | 6F | o |
| i | U+0069 | 69 | i |
| s | U+0073 | 73 | s |
| s | U+0073 | 73 | s |
| a | U+0061 | 61 | a |
| n | U+006E | 6E | n |
| t | U+0074 | 74 | t |
| s | U+0073 | 73 | s |
| SP | U+0020 | 20 | %20 |
| / | U+002F | 2F | %2F |
| SP | U+0020 | 20 | %20 |
| é | U+00E9 | C3 A9 | %C3%A9 |
| t | U+0074 | 74 | t |
| é | U+00E9 | C3 A9 | %C3%A9 |
| SP | U+0020 | 20 | %20 |
| 2 | U+0032 | 32 | 2 |
| 0 | U+0030 | 30 | 0 |
| 2 | U+0032 | 32 | 2 |
| 6 | U+0036 | 36 | 6 |
RFC 3986 把 ASCII 分为非保留字符和保留字符:前者永远不需要转义,后者承担结构作用,因此出现在值中时必须转义。字母、数字以及连字符、句点、下划线和波浪号这四个符号属于非保留字符,本页的所有编码器都不会改动它们,所以不在表中列出。
| 字符 | 字节 | 角色 | 组件 | 完整 URL | 表单 | RFC 3986 |
|---|---|---|---|---|---|---|
| SP | 20 | 始终转义空格 | %20 | %20 | + | %20 |
| ! | 21 | 子分隔符感叹号 | ! | ! | %21 | %21 |
| " | 22 | 始终转义双引号 | %22 | %22 | %22 | %22 |
| # | 23 | 通用分隔符井号,片段的开头 | %23 | # | %23 | %23 |
| $ | 24 | 子分隔符美元符号 | %24 | $ | %24 | %24 |
| % | 25 | 始终转义百分号,转义的开头 | %25 | %25 | %25 | %25 |
| & | 26 | 子分隔符和号,分隔参数 | %26 | & | %26 | %26 |
| ' | 27 | 子分隔符单引号 | ' | ' | %27 | %27 |
| ( | 28 | 子分隔符左圆括号 | ( | ( | %28 | %28 |
| ) | 29 | 子分隔符右圆括号 | ) | ) | %29 | %29 |
| * | 2A | 子分隔符星号 | * | * | * | %2A |
| + | 2B | 子分隔符加号 | %2B | + | %2B | %2B |
| , | 2C | 子分隔符逗号 | %2C | , | %2C | %2C |
| - | 2D | 非保留连字符 | - | - | - | - |
| . | 2E | 非保留句点 | . | . | . | . |
| / | 2F | 通用分隔符斜杠,分隔路径段 | %2F | / | %2F | %2F |
| : | 3A | 通用分隔符冒号,跟在协议后面 | %3A | : | %3A | %3A |
| ; | 3B | 子分隔符分号 | %3B | ; | %3B | %3B |
| < | 3C | 始终转义小于号 | %3C | %3C | %3C | %3C |
| = | 3D | 子分隔符等号,分隔键和值 | %3D | = | %3D | %3D |
| > | 3E | 始终转义大于号 | %3E | %3E | %3E | %3E |
| ? | 3F | 通用分隔符问号,查询的开头 | %3F | ? | %3F | %3F |
| @ | 40 | 通用分隔符@ 符号,凭据的结尾 | %40 | @ | %40 | %40 |
| [ | 5B | 通用分隔符左方括号,包裹 IPv6 主机 | %5B | %5B | %5B | %5B |
| \ | 5C | 始终转义反斜杠 | %5C | %5C | %5C | %5C |
| ] | 5D | 通用分隔符右方括号,包裹 IPv6 主机 | %5D | %5D | %5D | %5D |
| ^ | 5E | 始终转义脱字符 | %5E | %5E | %5E | %5E |
| _ | 5F | 非保留下划线 | _ | _ | _ | _ |
| ` | 60 | 始终转义反引号 | %60 | %60 | %60 | %60 |
| { | 7B | 始终转义左花括号 | %7B | %7B | %7B | %7B |
| | | 7C | 始终转义竖线 | %7C | %7C | %7C | %7C |
| } | 7D | 始终转义右花括号 | %7D | %7D | %7D | %7D |
| ~ | 7E | 非保留波浪号 | ~ | ~ | %7E | ~ |
百分号编码把一个字符替换为百分号加上它每个 UTF-8 字节的十六进制值。难点从来不在机制本身,而在于该替换哪些字符,而这个问题没有唯一答案。两个路径段之间的斜杠是结构,必须保持原样;同样的斜杠出现在文件名里就是数据,必须变成 %2F。按照 RFC 3986,空格是 %20;而在浏览器实际发送的表单编码中,空格是加号。感叹号经过 encodeURIComponent 后保持不变,却会被严格的 RFC 3986 编码器转义,这正是用错编码器计算出的 OAuth 签名无法通过验证的原因。本页拒绝替你做选择:它把四套规则同时应用到同一输入上,为每一种标注它适用的场景,让你复制合适的那一个。
编码一个值和编码整个 URL 不是一回事,这两者又都不同于拆解一个链接。选择编码、解码、解析 URL 或查询工作台,工作区会随之变化。每项任务各自保留文本,所以来回切换不会丢失任何内容。
编码模式同时显示同一文本按四种规则编码的结果,每种都标明何时适用,并可单独复制。解码模式显示组件解码结果、把加号视为空格的表单解码结果,以及报告解开了几层的重复解码。解析模式把链接拆分为协议、凭据、主机(可读形式和 punycode 形式)、端口、路径段、查询参数和片段。
每个输出都有自己的复制按钮,不必在一大段文字里翻找。在解析模式和查询工作台中,你可以编辑某个值、添加参数、删除参数或调整顺序,链接会根据表格重新生成,所有内容都会重新正确编码。
组件编码用于单个值,完整 URL 编码用于拼好的链接,表单编码用于浏览器提交的任何内容,RFC 3986 严格编码用于签名。它们在空格、加号、斜杠、感叹号、单引号、波浪号和星号的处理上各不相同,而每一处分歧都曾破坏过某人的查询字符串。把四种放在一起看,比记住哪个函数做什么要快。
浏览器内置函数只会抛出一个毫无信息量的 URIError。这个解码器自己逐字遍历字符串,报告字符位置、出错的序列和原因:百分号后面没有十六进制数字、一对字符不是十六进制、某个字节只能出现在字符中间、超长编码、半个代理对,或者码位超出了 Unicode 的上限。它还会在出错的确切位置下方画一个脱字符(^)。
%2520 是经过两次编码的空格,也是重定向参数失效最常见的原因。重复解码会一直进行到文本不再变化,报告剥掉了几层,并列出每一个中间字符串,让你看清多出来的那一遍是在哪里加上的。
用西里尔字母、希腊字母或带变音符号的德语书写的域名会被解析为 punycode,两种形式并排显示。这项检查能识破同形字链接:可读的主机名看起来很眼熟,而 DNS 实际收到的 ASCII 形式却并非如此。punycode 在本页中解码,因为浏览器本身没有提供从 ASCII 形式转换回来的方法。
tag=a&tag=b 是两个值,而不是一个,悄悄只保留最后一个的解析器会丢失数据。参数按原始顺序逐行列出,你可以编辑、添加、删除和重新排序,然后复制重新生成的字符串,空格写成加号还是 %20,取决于接收方系统的要求。
一个带重音的字母会变成两组百分号三元组,一个中日韩字符变成三组,一个表情符号变成四组,所以编码后的字符串可能比你输入的长好几倍。字节视图逐字符展示这一点,参考表列出每个 ASCII 标点符号在 RFC 3986 中的角色,以及四种编码器各自如何书写它。两者都由生成上方输出的同一段代码生成。
encodeURIComponent 编码 URL 的一个部分,并假定这部分是数据,所以会转义赋予 URL 结构的分隔符:斜杠、问号、井号、和号、等号、冒号、@ 符号、加号和美元符号。encodeURI 编码一个已经拼好的 URL,并假定这些分隔符是结构,所以不去动它们。实用的规则是:你几乎总是需要组件版本,因为你几乎总是在用片段拼出一个 URL,而不是修补一个已经存在的 URL。只有当别人交给你一个完整链接,里面有空格或带重音的字符需要清理时,才用完整 URL 版本。
因为两个标准写于不同的时期,而且都沿用了下来。RFC 3986 描述的是一般意义上的 URL,它在任何位置都把空格编码为 %20。更早的 HTML 表单提交格式 application/x-www-form-urlencoded 把空格编码为加号,这正是浏览器提交表单时至今仍在发送的格式,也是 URLSearchParams 生成的格式。几乎所有服务器都能在查询字符串中正确读取这两种写法,因为查询字符串解析器知道这两种约定。麻烦出在用一种方式编码的值被另一种方式解码时:用普通的百分号解码器去解码表单编码的值,Anna Marie 这样的名字就会变成 Anna+Marie。所以本页会同时显示两种解码结果,而不是替你选一种。
它表示一个被百分号编码了两次的空格。空格先变成 %20。如果这个 %20 再经过一次编码器,百分号本身会被编码为 %25,于是就得到 %2520。在链接的任何位置看到 %25 后面再跟两个十六进制数字,就是双重编码的特征。它通常发生在用一个已经编码过的 URL 再编码来构造重定向参数时,或者一个值经过两个框架、而每个框架都好心地编码了一次时。把它粘贴到这里的解码模式,层数会准确告诉你需要撤销几遍。
RFC 3986 定义了一个永远不需要转义的非保留字符集:A 到 Z、a 到 z、0 到 9,以及连字符、句点、下划线和波浪号这四个符号。其余所有字符都属于保留字符集,又分为通用分隔符(冒号、斜杠、问号、井号、方括号、@ 符号)和子分隔符(感叹号、美元符号、和号、单引号、圆括号、星号、加号、逗号、分号、等号)。保留字符在发挥结构作用的位置是合法的,作为数据出现时则必须转义。此外,空格以及双引号、小于号、大于号、百分号、反斜杠、脱字符、反引号和花括号这些字符根本不允许原样出现。本页的参考表逐一列出了它们,以及四种编码器各自如何处理。
因为百分号编码处理的是字节,而不是字符,而现代 URL 以 UTF-8 传输文本。在 UTF-8 中,带尖音符的字母 e 占两个字节 C3 和 A9,所以编码为 %C3%A9。一个中日韩字符占三个字节,变成三组三元组;一个表情符号占四个字节,变成四组三元组,也就是一个可见符号对应十二个字符。这就是编码后的字符串可能比你输入的文本长好几倍的原因,也是按字符计算的长度限制在编码前后表现不同的原因。本页的字节视图会逐字符展示这种膨胀。
不能,这正是制造失效链接最多的错误。把一个本来正确的 URL 再送进编码器,每个结构字符都会变成转义序列:斜杠变成 %2F,问号变成 %3F,已有转义序列中的百分号会变成 %25。出来的东西已经不是 URL 了,只是一个碰巧长得像 URL 的字符串。如果链接本来就能用,就别动它。如果链接里有未编码的空格或带重音的字符,完整 URL 编码才是合适的工具,因为它能修正这些字符而不碰分隔符。在编码模式中试试“已编码”预设,就能看到重新编码到底会对一个正常链接做什么。
域名系统只能承载有限的一组 ASCII 字符,所以用阿拉伯文、西里尔字母、希腊字母、中文书写的域名,或者仅仅带一个德语变音符号的域名,都必须先转换成 ASCII 才能被查询。这种转换就是 punycode,它生成以 xn-- 开头的标签。浏览器会静默完成这一转换,这意味着你看到的主机名和实际被解析的主机名是两个不同的字符串。同时显示两者是一项安全检查,而不是猎奇:同形字攻击之所以奏效,正是因为由外形相似的字符组成的名称读起来像某个熟悉的品牌,解析出来却完全是另一回事。本页在浏览器中把 punycode 解码回 Unicode,因为浏览器没有内置的逆向转换方法。
凡是在 URL 中出现过的凭据,都应视为已经泄露。URL 中的用户信息部分,也就是 @ 符号之前的所有内容,在旧协议中会作为请求行的一部分明文发送,会被写入服务器访问日志和代理日志,会保存在浏览器历史记录中,过去还曾通过发往你访问的下一个网站的 Referer 请求头泄露。如今有些浏览器会去掉它或发出警告,但已经写入日志的副本不会消失。如果带凭据的链接被分享过、被粘贴到工单里或从邮件中点开过,请更换密码,而不要指望这个链接一直保密。本页只要发现凭据就会标记出来,并遮盖密码而不是显示它。
只要输入不是有效的百分号编码 UTF-8 字符串,它就会抛出 URIError,这包括百分号后面什么都没有、像 %ZZ 这样不是十六进制的字符对、像单独一个 %C3 这样被截断的多字节字符、超长编码,以及被转义的半个代理对。这个错误既不说明位置也不说明原因,所以在生产环境中通常会变成一个空白页面或 500 错误。在你自己的代码里,解决办法是包裹这个调用并显式处理失败,而不是任由异常抛出去。调试时的解决办法是把值粘贴到这里:本页的解码器会手动遍历字符串,告诉你是哪个字符出了问题以及原因,而不是拒绝说明。
不会。所有编码器、解码器、URL 解析器、punycode 解码器和查询工作台都以普通 JavaScript 在这个标签页中运行,不会访问任何链接。这一点在这里比在大多数工具上更重要,因为人们拿来解码的 URL 恰恰是那些包含会话令牌、签名下载链接、密码重置参数和内部主机名的链接。输入上限为 100,000 个字符,远超任何可用的 URL,两次访问之间不会保存任何内容。
探索不断扩充的工具合集,涵盖计算、文档、写作和日常工作。