URL 编码与解码

三种百分号编码方式并排对照,让你一眼看出该用哪一种。

三条规则,选错那条就是 bug

百分号编码把一个字符替换成 % 后面跟上它的字节值的十六进制。够简单了。真正惹麻烦的是,关于哪些字符需要被替换,存在三种不同的意见,而它们的分歧恰恰落在那些要紧的字符上。

规则转义 / ? & = #空格变成适用于
encodeURIComponent%20要放进 URL 里的一个值
encodeURI%20整理一整条 URL
表单编码+HTML 表单实际发送的格式

本工具一次把三种都显示出来,原因正在于此。把它们并排读一遍,比抽象地去判断你到底想要哪一种要快。

它能避免的那个错误

假设一个搜索值是 coffee & cake。按 component 规则编码后它变成 coffee%20%26%20cake,URL 是这样:

/search?q=coffee%20%26%20cake

一个参数,值完好无损。而用 encodeURI 编码,那个 & 号会原样活下来:

/search?q=coffee%20&%20cake

现在服务器看到的是两个参数——q 的值是「coffee 」,另外还有一个名叫「 cake」的空参数。没有任何报错。搜索只是悄悄返回了错误的结果,而这正是那种能通过评审的 bug,因为 URL 看起来完全正常。

那个不是加号的 +

另一个可靠的混淆来源。在表单编码的数据里,+ 表示一个空格,而真正的加号必须写成 %2B。所以用表单规则解码 C%2B%2B 会得到 C++,但解码 C++ 会得到 C——外加两个空格,语言名字没了。

这一条在电话号码上咬得最狠。+44 20 7946 0958 通过表单提交、又用错误的规则解码,就变成了 44 20 7946 0958,丢掉了国家代码前缀,而且任何地方都不会报错。

别手工编码,该怎么做

在 JavaScript 里,用 URLSearchParams 来构造查询字符串,而不是拼接。它会为每个值套用正确的规则,也能处理重复的键:

const url = new URL('https://example.com/search');
url.searchParams.set('q', 'coffee & cake');
url.searchParams.set('page', '2');
// https://example.com/search?q=coffee+%26+cake&page=2

其他每种语言都有对应的东西,而它们每一个都比逐字符去判断更可靠。本页是给这些时候用的:你在读别人发给你的一条 URL,或者在调试一条已经出了问题的 URL。

读懂一条编码过的 URL

有几个序列值得一眼认出来,因为它们在日志里不断出现:

序列字符为什么重要
%20空格迄今最常见的一个
%2F/路径里出现被编码的斜杠,往往是一次目录穿越尝试
%3A:常见于跳转参数里内嵌的编码 URL
%25%双重编码会表现为 %2520
%00空字符几乎从来都不是正当的

尤其 %2520 值得记住:它是被编码了第二次的 %20。它通常意味着一个值穿过了两层,而每一层都编码了它一次;它是某条跳转链或某个代理干了错事的签名。

相关

如果要编码字节而不是转义文本,Base64才是那件不同的活儿。如果解码出来的值原来是 JSON,格式化器会替你读它;如果它是一个令牌,那就交给 JWT 解码器

编码相关问题

encodeURI 和 encodeURIComponent 有什么区别?

encodeURIComponent 会转义那些在 URL 中具有结构含义的字符——/ ? & = # : 等等——因为它假定你要编码的是一个将被放进 URL 里的值。encodeURI 则不动这些字符,因为它假定你交给它的是一整条需要整理的 URL。用 encodeURI 去编码查询参数是常见的错误:值里面的 & 会原样保留下来,悄悄把你的参数一分为二。

为什么空格有时是 %20,有时又是 +?

在各自的语境里两者都对。%20 是空格的通用百分号编码,在 URL 的任何位置都能用。而 + 这个约定来自 application/x-www-form-urlencoded,也就是 HTML 表单提交所用的格式,在那里 + 表示空格,真正的加号必须写成 %2B。所以路径里的空格是 %20,提交的表单数据里的空格是 +。用错规则去解码表单数据,会把用户文本里的每一个加号都变成空格。

我遇到了 URI malformed 错误,是什么原因?

某个百分号后面没有跟着两位十六进制数字。通常是文本里有一个从未被编码的字面 % ——像「50% off」这样的折扣码就会触发,因为 %20 会被当作转义序列读取,而 %off 并不合法。字面的百分号必须写成 %25。另一个原因是重复解码:对只编码过一次的内容解码两次,迟早会撞上一个本该原样保留的 %。

我该编码整条 URL 还是只编码里面的值?

基本上永远只编码值。按结构把 URL 拼起来,在插入每个参数时对它单独编码——在 JavaScript 里,URLSearchParams 会替你做这件事,而且比手工编码可靠得多。只有当别人递给你的 URL 本身就已经写坏了、你要打补丁时,编码整条 URL 才说得通。

对 URL 编码就意味着安全了吗?

不是。百分号编码关乎语法,而非安全——它保证一个值被放进 URL 后能原样存活,同时不改变这条 URL 的结构。它管不了这条 URL 指向哪里,也不是防注入的手段:一个已经为 URL 正确编码过的值,如果之后被插进 HTML 或 SQL 而没有做那两者各自该做的转义,依然是危险的。

最后审校于 。发现有内容过时了? 告诉我们.