URL / URI 的设计中规定允许的字符,如果出现了其他字符就需要转义,这套规则被称之为百分号编码(Percent-encoding)。
WWW 的设计中就使用了这套规则,比如 URL 地址、和表单提交(application/x-www-form-urlencoded
)等场景,所以这个编码规则也被称之为 URL 编码(URL encoding)。
编程时,编码解码操作一般就叫 urlencode,urldecode。
编码的方式非常简单,就是把字节用 16 进制的方式表示,然后每个字节前面加一个百分号。
应用场景
- URL / URI
- MIME
application/x-www-form-urlencoded
合法字符
参考:URI 基础语法
ASCII 中 66 个非保留字符和 18 个保留字符。
非保留字符(66)
- 字母和数字:
A-Z + a-z + 0-9
, - 再加上四个标点:
-
,_
,.
,~
。
注意,非保留字符也可以按照规则编码,比如字母 A 按 ASCII / UTF-8 可以编码成 %41
。但是协议反对这种做法。
Update @ 2022-11-22:
近期遇到一个问题:邮件中的链接,如果 path 部分包含 ~
,会被客户端解析成 %7E
,按理来说这是非保留字符,不需要转义的。
谷歌浏览器在访问相关地址的时候会重新反转义成 ~
,但是苹果的 Safari 就不会,导致没有做兼容处理的服务器端无法处理苹果浏览器访问的 %7E
路径而报错。
根据有限的资料,这可能和 WWW 生态中,部分中间服务的安全策略有关,带波浪线的可能有安全风险。这个问题待查明!
至少,以后要小心链接中使用波浪线。
Update @ 2022-11-24 (PHP: urlencode() vs. rawurlencode()):
PHP 的 urlencode
, rawurlencode
的重要区别就是空格和波浪线的编码方式:
- urlencode 编码空格为加号(
+
),rawurlencode 编码空格为%20
- urlencode 编码波浪线为加号(
%7E
),rawurlencode 不编码波浪线
PHP 5.3.4 changelog 中有提到:
Fixed bug #53248 (rawurlencode RFC 3986 EBCDIC support misses tilde char).
看来,之前都是对波浪线进行编码,但是 RFC 3986 中,波浪线是非保留字符。
PHP 中的 rawurlencode 的设计是按照 URI RFC 实现,所以做这个调整,取消了对波浪线的编码。
再往前倒,RFC 1738 Uniform Resource Locators (URL) 中,波浪线不属于非保留字符。
safe = "$" | "-" | "_" | "." | "+"
extra = "!" | "*" | "'" | "(" | ")" | ","
unreserved = alpha | digit | safe | extra
保留字符(18)
! # $ & ' ( ) * + , / : ; = ? @ [ ]
保留字符是指 URI 语法中有特俗含义的一组字符。比如 http://user:pass@host:port/path?query#fragment
,其密码部分包含 /
保留字符又分成两组,: / ? # [ ] @
(7) 为通用保留字符,! $ & ' ( ) * + , ; =
(11) 为实现相关保留字符。
比如,URL 中,&
, +
都是有特俗含义的,就是保留字符。
PS:RPC 中似乎是把前一组叫做 gen-delims,后一组叫做 sub-delims。
URL / x-www-form-urlencode
The encoding used by default is based on an early version of the general URI percent-encoding rules, with a number of modifications such as newline normalization and replacing spaces with
+
instead of%20
.
在 Web 开发领域中 URL 编码其实和 URI 相关 RPC 的 Percent-Encoding 章节描述的规则可能有一些不一样。
比如:空格一般不转义成 %20
,而是 +
。
print(repr(string.printable))
for index, i in enumerate(string.printable, 1):
a, b = parse.quote(i), parse.quote_plus(i)
if a != b:
print('%2d. %r %5s %5s' % (index, i, a, b))
# 77. '/' / %2F
# 95. ' ' %20 +
参考资料与拓展阅读
- 阮一峰,关于URL编码
- 维基百科(en),Percent-encoding
- 维基百科(en),Uniform Resource Identifier
- 维基百科,Quoted-printable
- 2018/04/20, Quoted-printable