如果我想使用变量创建 URL,我有两种选择来对字符串进行编码。urlencode()
和rawurlencode()
。
究竟有什么区别,哪个是首选?
如果我想使用变量创建 URL,我有两种选择来对字符串进行编码。urlencode()
和rawurlencode()
。
究竟有什么区别,哪个是首选?
这将取决于你的目的。如果与其他系统的互操作性很重要,那么似乎 rawurlencode 是要走的路。一个例外是遗留系统,它期望查询字符串遵循编码为 + 而不是 %20 的空格的表单编码样式(在这种情况下,您需要 urlencode)。
rawurlencode遵循 PHP 5.3.0 之前的 RFC 1738 和之后的 RFC 3986(参见http://us2.php.net/manual/en/function.rawurlencode.php)
返回一个字符串,其中除 -_.~ 之外的所有非字母数字字符都已替换为百分号 (%) 符号后跟两个十六进制数字。这是 » RFC 3986 中描述的编码,用于保护文字字符不被解释为特殊的 URL 分隔符,以及保护 URL 不被具有字符转换的传输媒体(如某些电子邮件系统)破坏。
关于 RFC 3986 与 1738 的注意事项。php 5.3 之前的 rawurlencode~
根据 RFC 1738 对波浪字符 ( ) 进行编码。然而,从 PHP 5.3 开始,rawurlencode 遵循不需要编码波浪字符的 RFC 3986。
urlencode将空格编码为加号(不像%20
rawurlencode 中所做的那样)(参见http://us2.php.net/manual/en/function.urlencode.php)
返回一个字符串,其中包含除 -_ 之外的所有非字母数字字符。已替换为百分号 (%) 后跟两个十六进制数字和编码为加号 (+) 的空格。它的编码方式与 WWW 表单中发布的数据的编码方式相同,即与 application/x-www-form-urlencoded 媒体类型中的方式相同。这与 » RFC 3986 编码(参见 rawurlencode())的不同之处在于,由于历史原因,空格被编码为加号 (+)。
这对应于RFC 1866中 application/x-www-form-urlencoded 的定义。
补充阅读:
您可能还想在http://bytes.com/groups/php/5624-urlencode-vs-rawurlencode查看讨论。
此外,RFC 2396值得一看。RFC 2396 定义了有效的 URI 语法。我们感兴趣的主要部分来自 3.4 查询组件:
在查询组件中,字符是保留的。
";", "/", "?", ":", "@",
"&", "=", "+", ",", and "$"
如您所见,+
是查询字符串中的保留字符,因此需要按照 RFC 3986 进行编码(如 rawurlencode 中所示)。
证明在 PHP 的源代码中。
我会带你快速了解如何在未来任何时候自己找出这类事情。请耐心等待,您可以浏览很多 C 源代码(我会解释)。如果你想复习一些 C,一个很好的起点是我们的 SO wiki。
下载源代码(或使用http://lxr.php.net/在线浏览),grep 函数名的所有文件,你会发现如下内容:
PHP 5.3.6(在撰写本文时最新版本)在文件url.c中的本机 C 代码中描述了这两个函数。
RawUrlEncode()
PHP_FUNCTION(rawurlencode)
{
char *in_str, *out_str;
int in_str_len, out_str_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in_str,
&in_str_len) == FAILURE) {
return;
}
out_str = php_raw_url_encode(in_str, in_str_len, &out_str_len);
RETURN_STRINGL(out_str, out_str_len, 0);
}
网址编码()
PHP_FUNCTION(urlencode)
{
char *in_str, *out_str;
int in_str_len, out_str_len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &in_str,
&in_str_len) == FAILURE) {
return;
}
out_str = php_url_encode(in_str, in_str_len, &out_str_len);
RETURN_STRINGL(out_str, out_str_len, 0);
}
好的,那么这里有什么不同?
它们本质上都是分别调用两个不同的内部函数:php_raw_url_encode和php_url_encode
所以去寻找那些功能!
PHPAPI char *php_raw_url_encode(char const *s, int len, int *new_length)
{
register int x, y;
unsigned char *str;
str = (unsigned char *) safe_emalloc(3, len, 1);
for (x = 0, y = 0; len--; x++, y++) {
str[y] = (unsigned char) s[x];
#ifndef CHARSET_EBCDIC
if ((str[y] < '0' && str[y] != '-' && str[y] != '.') ||
(str[y] < 'A' && str[y] > '9') ||
(str[y] > 'Z' && str[y] < 'a' && str[y] != '_') ||
(str[y] > 'z' && str[y] != '~')) {
str[y++] = '%';
str[y++] = hexchars[(unsigned char) s[x] >> 4];
str[y] = hexchars[(unsigned char) s[x] & 15];
#else /*CHARSET_EBCDIC*/
if (!isalnum(str[y]) && strchr("_-.~", str[y]) != NULL) {
str[y++] = '%';
str[y++] = hexchars[os_toascii[(unsigned char) s[x]] >> 4];
str[y] = hexchars[os_toascii[(unsigned char) s[x]] & 15];
#endif /*CHARSET_EBCDIC*/
}
}
str[y] = '\0';
if (new_length) {
*new_length = y;
}
return ((char *) str);
}
PHPAPI char *php_url_encode(char const *s, int len, int *new_length)
{
register unsigned char c;
unsigned char *to, *start;
unsigned char const *from, *end;
from = (unsigned char *)s;
end = (unsigned char *)s + len;
start = to = (unsigned char *) safe_emalloc(3, len, 1);
while (from < end) {
c = *from++;
if (c == ' ') {
*to++ = '+';
#ifndef CHARSET_EBCDIC
} else if ((c < '0' && c != '-' && c != '.') ||
(c < 'A' && c > '9') ||
(c > 'Z' && c < 'a' && c != '_') ||
(c > 'z')) {
to[0] = '%';
to[1] = hexchars[c >> 4];
to[2] = hexchars[c & 15];
to += 3;
#else /*CHARSET_EBCDIC*/
} else if (!isalnum(c) && strchr("_-.", c) == NULL) {
/* Allow only alphanumeric chars and '_', '-', '.'; escape the rest */
to[0] = '%';
to[1] = hexchars[os_toascii[c] >> 4];
to[2] = hexchars[os_toascii[c] & 15];
to += 3;
#endif /*CHARSET_EBCDIC*/
} else {
*to++ = c;
}
}
*to = 0;
if (new_length) {
*new_length = to - start;
}
return (char *) start;
}
在我继续之前快速了解一点,EBCDIC 是另一种字符集,类似于 ASCII,但完全是竞争对手。PHP 试图同时处理这两种情况。但基本上,这意味着字节 EBCDIC 0x4c 字节不是L
ASCII,它实际上是一个<
. 我相信你看到这里的混乱。
如果 Web 服务器已经定义了 EBCDIC,那么这两个函数都会管理它。
此外,它们都使用一个字符数组(认为字符串类型)hexchars
查找来获取一些值,该数组被描述为:
/* rfc1738:
...The characters ";",
"/", "?", ":", "@", "=" and "&" are the characters which may be
reserved for special meaning within a scheme...
...Thus, only alphanumerics, the special characters "$-_.+!*'(),", and
reserved characters used for their reserved purposes may be used
unencoded within a URL...
For added safety, we only leave -_. unencoded.
*/
static unsigned char hexchars[] = "0123456789ABCDEF";
除此之外,功能真的不同,我将用 ASCII 和 EBCDIC 来解释它们。
网址编码:
+
则在输出字符串中添加一个符号。isalnum(c)
,也不_
是字母数字(一个来自Apache的数组,用于将char 转换为十六进制代码)作为(当前字符)的键,然后我们按位右移 4,将该值分配给字符 1,并为位置 2 分配相同的查找,除了我们执行a 逻辑 and 查看值是否为 15 (0xF),在这种情况下返回 1,否则返回 0。最后,你会得到一些编码的东西。-
.
%
hexchars
os_toascii
c
_-.
字符之一,它会准确输出它的内容。RAWURLENCODE:
注意:许多程序员可能从来没有见过这样的 for 循环迭代,它有点 hackish 并且不是大多数 for 循环使用的标准约定,请注意,它分配x
and ,在到达 0 时y
检查退出,并递增and 。我知道,这不是您所期望的,但它是有效的代码。len
x
y
str
。_-.
字符之一,如果不是,我们执行与 URLENCODE 执行查找的几乎相同的分配,但是,我们使用y++
而不是递增不同to[1]
,这是因为字符串以不同的方式构建,但最终还是达到了相同的目标。\0
字节。差异:
\0
为字符串分配字节,RawUrlEncode 会(这可能是一个有争议的问题)它们基本上以不同的方式迭代,在 ASCII 20 的情况下分配一个 + 号。
网址编码:
0
,除了是 a.
或-
,或小于A
但大于 char 9
,或大于Z
和小于a
但不是 a _
。或大于z
(是的,EBCDIC 有点搞砸了)。如果它与其中任何一个匹配,请执行与 ASCII 版本中类似的查找(它只是不需要在 os_toascii 中查找)。RAWURLENCODE:
z
排除。~
\0
到字符串。~
UrlEncode 没有的(这是一个报告的问题)。值得注意的是 ASCII 和 EBCDIC 0x20 都是空格。+
,RawUrlEncode%20
通过数组查找将空格放入。免责声明:我已经很多年没有接触过 C 语言了,也很长时间没有看过 EBCDIC 了。如果我在某个地方错了,请告诉我。
基于所有这些,rawurlencode 是大多数时候要走的路。正如您在 Jonathan Fingland 的回答中看到的那样,在大多数情况下坚持下去。它处理 URI 组件的现代方案,其中 urlencode 以老式方式处理事情,其中 + 表示“空间”。
如果您尝试在旧格式和新格式之间进行转换,请确保您的代码不会出错并通过意外双编码或类似的“哎呀”场景将解码 + 符号转换为空格空间/20%/+ 问题。
如果您正在使用不喜欢新格式的旧软件在旧系统上工作,请坚持使用 urlencode,但是,我相信 %20 实际上是向后兼容的,因为在旧标准下 %20 有效,只是没有首选。如果您愿意四处玩耍,请试一试,让我们知道它是如何为您服务的。
基本上,您应该坚持使用 raw,除非您的 EBCDIC 系统真的讨厌您。大多数程序员永远不会在 2000 年之后制造的任何系统上遇到 EBCDIC,甚至可能是 1990 年(这是在推动,但在我看来仍然可能)。
echo rawurlencode('http://www.google.com/index.html?id=asd asd');
产量
http%3A%2F%2Fwww.google.com%2Findex.html%3Fid%3Dasd%20asd
尽管
echo urlencode('http://www.google.com/index.html?id=asd asd');
产量
http%3A%2F%2Fwww.google.com%2Findex.html%3Fid%3Dasd+asd
区别在于asd%20asd
vsasd+asd
urlencode 与 RFC 1738 的不同之处在于将空格编码+
为%20
选择其中一个的一个实际原因是,如果您要在另一个环境中使用结果,例如 JavaScript。
在 PHP中urlencode('test 1')
返回'test+1'
,而作为结果rawurlencode('test 1')
返回。'test%201'
但是,如果您需要使用decodeURI()函数在 JavaScript 中“解码”它,那么decodeURI("test+1")
它会给您"test+1"
同时decodeURI("test%201")
给您"test 1"
结果。
换句话说,在 PHP 中由urlencode 编码为加号(“+”)的空格(“”)将不会被JavaScript 中的decodeURI正确解码。
在这种情况下,应该使用rawurlencode PHP 函数。
我相信空格必须编码为:
%20
在 URL 路径组件中使用时+
在 URL 查询字符串组件或表单数据中使用时(参见17.13.4 表单内容类型)以下示例显示了rawurlencode
and的正确用法urlencode
:
echo "http://example.com"
. "/category/" . rawurlencode("latest songs")
. "/search?q=" . urlencode("lady gaga");
输出:
http://example.com/category/latest%20songs/search?q=lady+gaga
如果反过来编码路径和查询字符串组件会发生什么?对于以下示例:
http://example.com/category/latest+songs/search?q=lady%20gaga
latest+songs
而不是latest songs
q
将包含lady gaga
唯一的区别在于空间的处理方式:
urlencode - 基于遗留实现将空格转换为 +
rawurlencode - 基于RFC 1738将空格转换为 %20
差异的原因是因为 + 在 url 中是保留且有效(未编码)的。
我真的很想看到选择其中一个而不是另一个的一些原因……我希望能够选择一个并永远使用它,而不必大惊小怪。
公平地说,我在做出这些决定时遵循了一个简单的策略,我将与您分享,希望它可能会有所帮助。
我认为是 HTTP/1.1 规范RFC 2616要求“容忍应用程序”
客户端在解析状态行时应该是宽容的,而服务器在解析请求行时应该是宽容的。
面对此类问题时,最好的策略始终是尽可能多地消费并生产符合标准的产品。
因此,我的建议是使用rawurlencode
生成符合标准的 RFC 1738 编码字符串,并用于urldecode
向后兼容并容纳您可能遇到的任何内容。
现在你可以相信我的话,但让我们证明它应该......
php > $url = <<<'EOD'
<<< > "Which, % of Alice's tasks saw $s @ earnings?"
<<< > EOD;
php > echo $url, PHP_EOL;
"Which, % of Alice's tasks saw $s @ earnings?"
php > echo urlencode($url), PHP_EOL;
%22Which%2C+%25+of+Alice%27s+tasks+saw+%24s+%40+earnings%3F%22
php > echo rawurlencode($url), PHP_EOL;
%22Which%2C%20%25%20of%20Alice%27s%20tasks%20saw%20%24s%20%40%20earnings%3F%22
php > echo rawurldecode(urlencode($url)), PHP_EOL;
"Which,+%+of+Alice's+tasks+saw+$s+@+earnings?"
php > // oops that's not right???
php > echo urldecode(rawurlencode($url)), PHP_EOL;
"Which, % of Alice's tasks saw $s @ earnings?"
php > // now that's more like it
看起来 PHP 正是考虑到这一点,即使我从未遇到任何人拒绝这两种格式中的任何一种,但我想不出更好的策略来作为您的实际策略,你可以吗?
开心!
不同之处在于返回值,即:
返回一个字符串,其中包含除 -_ 之外的所有非字母数字字符。已替换为百分号 (%) 后跟两个十六进制数字和编码为加号 (+) 的空格。它的编码方式与 WWW 表单中发布的数据的编码方式相同,即与 application/x-www-form-urlencoded 媒体类型中的方式相同。这与 » RFC 1738 编码(参见 rawurlencode())的不同之处在于,由于历史原因,空格被编码为加号 (+)。
返回一个字符串,其中包含除 -_ 之外的所有非字母数字字符。已替换为百分号 (%) 符号后跟两个十六进制数字。这是 » RFC 1738 中描述的编码,用于保护文字字符不被解释为特殊的 URL 分隔符,并保护 URL 不被具有字符转换的传输媒体(如某些电子邮件系统)破坏。
两者非常相似,但后者(rawurlencode)将用'%'和两个十六进制数字替换空格,这适用于编码密码等,其中'+'不是例如:
echo '<a href="ftp://user:', rawurlencode('foo @+%/'),
'@ftp.example.com/x.txt">';
//Outputs <a href="ftp://user:foo%20%40%2B%25%2F@ftp.example.com/x.txt">
urlencode:这与 » RFC 1738 编码(参见 rawurlencode())的不同之处在于,由于历史原因,空格被编码为加号 (+)。
%20
vs.+
我rawurlencode()
在大多数情况下看到使用的最大原因是urlencode
将文本空间编码为+
(加号),其中rawurlencode
将它们编码为常见的%20
:
echo urlencode("red shirt");
// red+shirt
echo rawurlencode("red shirt");
// red%20shirt
我特别看到某些接受编码文本查询的 API 端点期望看到%20
空格,因此,如果使用加号代替,则会失败。显然,这在 API 实现之间会有所不同,并且您的里程可能会有所不同。
我相信 urlencode 用于查询参数,而 rawurlencode 用于路径段。这主要是由于%20
路径段与+
查询参数。请参阅有关空格的答案:何时将空格编码为加号(+)或%20?
但是%20
现在也可以在查询参数中使用,这就是 rawurlencode 总是更安全的原因。然而,加号往往用于用户的编辑体验和查询参数的可读性很重要的地方。
请注意,这意味着rawurldecode
不会解码+
为空格(http://au2.php.net/manual/en/function.rawurldecode.php)。这就是为什么 $_GET 总是自动传递的原因urldecode
,这意味着+
和%20
都被解码为空格。
如果您希望输入和输出之间的编码和解码保持一致,并且您选择始终使用+
而不是%20
查询参数,那么urlencode
查询参数(键和值)就可以了。
结论是:
路径段 - 始终使用 rawurlencode/rawurldecode
查询参数 - 解码总是使用 urldecode(自动完成),对于编码,rawurlencode 或 urlencode 都可以,只需选择一个保持一致,尤其是在比较 URL 时。
简单 * rawurlencode 路径 - 路径是“?”之前的部分 - 空格必须编码为 %20 * urlencode 查询字符串 - 查询字符串是“?”之后的部分 -spaces 更好地编码为“+” = rawurlencode 通常更兼容