如果我想使用变量创建一个URL,我有两种选择来编码字符串。 urlencode()
和 rawurlencode()
。
它们的区别是什么,哪个更受推荐?
如果我想使用变量创建一个URL,我有两种选择来编码字符串。 urlencode()
和 rawurlencode()
。
它们的区别是什么,哪个更受推荐?
urlencode将空格编码为加号(而不是像rawurlencode中的%20
那样)(参见https://www.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 开始。
下载源代码(或使用 https://heap.space/ 在线浏览),在所有文件中搜索函数名称,你会发现类似于这样的内容:
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);
}
UrlEncode()
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字节不是ASCII中的L,而实际上是一个<。我相信你看到了这里的困惑。
如果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来解释它们。
URLENCODE:
+
符号。isalnum(c)
),并且还不是_
、-
或.
字符,则我们会将一个%
符号输出到数组位置0,对hexchars
数组进行查找以进行os_toascii
数组(从Apache that translates char to hex code)的键值_-.
字符之一,则输出与它完全相同的内容。RAWURLENCODE:
注意:许多程序员可能从未见过for循环以这种方式迭代,这有点hackish并且不是大多数for循环使用的标准约定,请注意,它分配x
和y
,检查len
是否达到0的退出条件,并增加x
和y
。我知道,这不是你期望的,但它是有效的代码。
str
中的匹配字符位置。_-.
字符之一,如果不是,则执行与URLENCODE几乎相同的分配操作,其中它执行查找,但我们使用y++
而不是to [1]
进行不同的递增,这是因为字符串以不同的方式构建,但最终都达到相同的目标。\0
字节。差异:
\0
字节,而RawUrlEncode会(这可能是无关紧要的)它们基本上迭代方式不同,在ASCII 20的情况下会分配一个+号。
URLENCODE:
0
的字符,除了.
或-
之外,或小于char 9
但大于char的例外。 A
,或大于Z
且小于a
但不是_
。或大于z
(是的,EBCDIC处理起来有点混乱)。如果它匹配其中任何一个,则执行类似于ASCII版本中找到的查找操作(它只不过不需要在os_toascii中进行查找)。RAWURLENCODE:
z
,则从URL编码中排除 ~
。 \ 0
字节附加到字符串。 ~
(这是已报告的问题)。 值得注意的是,ASCII和EBCDIC 0x20都是空格。 +
,RawUrlEncode通过数组查找将空格变成%20
。免责声明:我多年没有接触过C语言,也很长时间没有看过EBCDIC。 如果我有错误,请让我知道。
基于所有这些,大多数情况下都应该使用rawurlencode。 如Jonathan Fingland的答案中所示,在大多数情况下坚持使用它。 它处理了URI组件的现代方案,而urlencode则以旧方式处理,其中+表示“空格”。
如果您正在尝试在旧格式和新格式之间进行转换,请确保您的代码不会出错,并将解码后的+符号转换为空格,例如通过意外双重编码或类似的与此空格/20%/+问题相关的“糟糕”场景。
如果您正在使用不偏好新格式的旧软件的较旧系统,请坚持使用urlencode,但是我认为%20实际上是向后兼容的,因为在旧标准下%20有效,只是不被偏爱。 如果您想尝试玩耍,请试试,告诉我们它对您有何作用。
基本上,除非您的EBCDIC系统真的不喜欢您,否则应坚持使用原始工具。 大多数程序员在2000年之后甚至1990年之后制造的任何系统上都不会遇到EBCDIC(这是推测,但在我看来仍然很可能)。
*to = 0;
。可以将其解释为将值零分配给to
指向的位置。此时,to
指向应该有空字节的位置。另外,0
和'\0'
是相等的,只是表达相同事物的不同方式。 - mdfst13echo 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
与asd+asd
urlencode与RFC 1738的不同之处在于将空格编码为+
而不是%20
选择urlencode
或rawurlencode
的一个实际原因是如果你要在另一个环境中使用结果,例如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函数。
json_encode
和 JSON.parse
来实现这个目的。 - Fabrício Matté我认为在URL路径组件内,空格必须编码为%20
。
+
(请参见17.13.4表单内容类型)。以下示例展示了正确使用rawurlencode
和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
q
将包含 lady gaga
。否则它会包含什么?无论在 PHP 5.2+ 中使用 rawurlencode
还是 urlencode
,查询参数 q
的值似乎都与传递给 $_GET
数组的值相同。但是,urlencode
编码为 application/x-www-form-urlencoded
格式,这是 GET 请求的默认格式,所以我采用了您的方法。+1 - Fabrício Matté+
和%20
时,它们都被解码为空格。 - Salman A唯一的区别在于空格的处理方式:
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 正是考虑到这一点,尽管我从未遇到拒绝其中任何一种格式的人,但我想不出更好的默认策略可供采用,你呢?
愉快!
%20
vs. +
在大多数情况下,我看到使用rawurlencode()
的最大原因是因为urlencode
将文本中的空格编码为+
(加号),而rawurlencode
将其编码为常见的%20
:
echo urlencode("red shirt");
// red+shirt
echo rawurlencode("red shirt");
// red%20shirt
我特别注意到某些API端点接受编码文本查询,并期望使用%20
表示空格,如果使用加号代替,可能会导致失败。显然,这将因API实现而异,因此您的结果可能会有所不同。
区别在于返回值,即:
返回一个字符串,其中所有非字母数字字符(除了-_.)都被替换为百分号(%)后跟两个十六进制数字。这是在 RFC 1738 中描述的编码,用于保护文字字符不被解释为特殊的 URL 分隔符,并保护 URL 免受某些电子邮件系统等传输媒体的字符转换破坏。返回一个字符串,在该字符串中所有非字母数字字符(除了-_.)都被替换为一个百分号(%)后跟着两个十六进制数字,空格则被编码为加号(+)。它的编码方式与WWW表单提交的编码方式相同,即application/x-www-form-urlencoded媒体类型的编码方式。这与RFC 1738编码(请参见rawurlencode())不同之处在于,由于历史原因,空格被编码为加号(+)。
这两种方法非常相似,但后者(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">
简单来说 * 对路径进行rawurlencode编码 - 路径是问号之前的部分 - 空格必须编码为%20 * 对查询字符串进行urlencode编码 - 查询字符串是问号之后的部分 - 空格最好编码为"+" = rawurlencode通常更兼容
rawurlencode
。将空格编码为%20
的系统很少会出现问题,而那些将空格编码为+
的系统则更加普遍,可能会导致错误。 - Anomie