urlencode与rawurlencode?


380

如果要使用变量创建URL,则有两种选择来编码字符串。urlencode()rawurlencode()

到底有什么区别,哪个是首选?


1
我真的很想知道一些选择另一个的原因(例如,一个或另一个可能遇到的问题),我(而且我希望其他人)希望能够选择一个,并将其永久使用。最少大惊小怪,所以我开始悬赏这个问题。
卡扎伊2011年

29
@Tchalvak:如果您只想选择一个,请选择rawurlencode。当给定编码为的空格时,您很少会遇到%20阻塞的系统,而在编码为的空格处阻塞的系统+则更常见。
Anomie

Answers:


326

这将取决于您的目的。如果与其他系统的互操作性很重要,那么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将空格编码为加号(不像%20rawurlencode那样)(请参见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)进行编码。


27
那么哪个优先?
加里·威洛比2009年

79
rawurlencode。在这种情况下,请遵循标准。urlencode仅保留用于传统用途
Jonathan Fingland 09年

2
非常感谢,这就是我的想法,在开始更新大量代码之前,我只想发表第二意见。
加里·威洛比2009年

3
我认为这不是rawurlencode,不是将空格编码为加号而是%20s
BigName 2010年

2
@Pindatjuh:您引用的部分是一个例外,传统系统希望查询字符串遵循以+而不是%20(在这种情况下您需要urlencode)编码的空格的表单编码样式,这意味着rawurlencode在大多数情况下是正确的,某些系统希望空格被编码为+(加号)。对于此类系统,urlencode是更好的选择。
Jonathan Fingland 2011年

213

证明在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);
}

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_encodephp_url_encode

所以去寻找那些功能!

让我们看一下php_raw_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);
}

当然,php_url_encode:

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字节不是LASCII中的,实际上是一个<。我确定您在这里看到了困惑。

如果Web服务器已定义,则这两个功能都将管理EBCDIC。

同样,它们都使用一个chars(认为是字符串类型)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对其进行解释。

ASCII的差异:

URLENCODE:

  • 计算输入字符串的开始/结束长度,分配内存
  • 遍历while循环,递增直到我们到达字符串的末尾
  • 抓住现在的角色
  • 如果字符等于ASCII字符0x20(即“空格”),请+在输出字符串中添加一个符号。
  • 如果它不是空格,也不是字母数字(isalnum(c)),也不是和_-.字符,那么我们将一个%符号输出到数组位置0,对hexchars数组进行os_toascii数组查找以查找数组(来自Apache的数组,该数组 char c(当前字符)的字符转换为 char(十六进制代码),然后我们按位右移4,将该值分配给字符1,并给位置2分配相同的查找,除了我们预先执行逻辑并查看该值是否为15(0xF),在这种情况下返回1,否则返回0。最后,您将得到一些编码的东西。
  • 如果结尾不是空格,则是字母数字或一个_-.字符,它将输出确切的内容。

RAWURLENCODE:

  • 为字符串分配内存
  • 根据函数调用中提供的长度对其进行迭代(不像URLENCODE那样在函数中计算)。

注意:许多程序员可能从未见过for循环会以这种方式进行迭代,这有点怪异,不是大多数for循环使用的标准约定,请注意,它会分配xand y,并在len到达0时检查退出,并同时增加xand y。我知道,这不是您所期望的,但这是有效的代码。

  • 将当前字符分配给中的匹配字符位置str
  • 它检查当前字符是否为字母数字或_-.字符之一,如果不是,则执行与执行预查询的URLENCODE几乎相同的赋值,但是,使用y++而不是进行不同的递增to[1],这是因为字符串以不同的方式构建,但最终仍会达到相同的目标。
  • 当循环完成且长度消失时,它实际上终止了字符串,并分配了\0字节。
  • 它返回编码的字符串。

差异:

  • UrlEncode检查空间,分配+号,而RawURLEncode不分配空间。
  • UrlEncode不会\0为字符串分配字节,而RawUrlEncode会分配字节(这可能是有争议的点)
  • 它们的迭代方式不同,可能容易出现格式错误的字符串,我只是在暗示这一点,而我并未进行任何调查。

它们的迭代方式基本上是不同的,在ASCII 20的情况下分配一个+号。

EBCDIC中的差异:

URLENCODE:

  • 与ASCII相同的迭代设置
  • 仍将“空格”字符转换为+ 号。注意-我认为这需要在EBCDIC中进行编译,否则您最终会遇到bug?有人可以编辑并确认吗?
  • 它检查是否本char是前一个字符0,用作为一个例外.-OR小于A但大于炭9OR大于Z和小于a但不是_大于z(是的,EBCDIC有点混乱地可以使用)。如果与其中任何一个匹配,请执行与ASCII版本中类似的查找(只是不需要在os_toascii中进行查找)。

RAWURLENCODE:

  • 与ASCII相同的迭代设置
  • 与EBCDIC版本的URL编码中所述的检查相同,不同之处在于,如果大于z,它将~从URL编码中排除。
  • 与ASCII RawUrlEncode分配相同
  • \0返回之前仍将字节附加到字符串。

总结

  • 两者都使用相同的hexchars查找表
  • URIEncode不会以\ 0终止字符串,raw不会。
  • 如果您使用的是EBCDIC,我建议您使用RawUrlEncode,因为它管理的~不是UrlEncode(这是一个已报告的问题)。值得注意的是ASCII和EBCDIC 0x20都是空格。
  • 它们的迭代方式有所不同,一种可能更快,另一种可能容易遭受基于内存或基于字符串的攻击。
  • URIEncode在其中添加一个空格+,RawUrlEncode在%20数组查找中添加一个空格。

免责声明:我已经好多年没有接触C了,而且很长一段时间都没有研究EBCDIC。如果我在某个地方错了,请告诉我。

建议的实施

基于所有这些,rawurlencode是大多数情况下使用的方法。正如您在乔纳森·芬德兰(Jonathan Fingland)的答案中所见,在大多数情况下,请坚持使用。它处理URI组件的现代方案,其中urlencode按照旧的方式进行操作,其中+表示“空格”。

如果您要在旧格式和新格式之间进行转换,请确保您的代码不会出错,并通过意外的双重编码或类似的“糟糕”方案将已解码+符号的内容转换为空格空间/ 20%/ +问题。

如果您使用的旧系统使用的旧软件不喜欢新格式,请坚持使用urlencode,但是,我相信%20实际上是向后兼容的,因为在旧的%20标准下有效,只是不行首选。如果您愿意玩这个游戏,请试一试,让我们知道它如何为您效劳。

基本上,您应该坚持使用raw,除非您的EBCDIC系统真的讨厌您。大多数程序员永远都不会在2000年甚至1990年以后生产的任何系统上使用EBCDIC(这正在推动,但我认为仍然很可能)。


我从不需要担心双重编码,毕竟我应该知道编码的内容,因为我正在做我想的编码。由于我使用兼容模式解码接收到的所有内容,该模式知道如何对空间+进行处理,所以我从来没有遇到过您尝试在此处警告的问题。如果我们不知道某些功能,我可以理解一下源代码,但是在这里我们确切地学到了什么,仅仅通过执行两个函数就不知道了。我知道我有偏见,但我不禁认为这太过分了。功夫上的荣誉!=)
尼克

2
+1,对于这一部分:“我相信%20实际上是向后兼容的,因为按照旧标准%20起作用,只是
Gras Double

3
好的答案,但也许有点矫kill过正?
rinogo

38
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%20asdvsasd+asd

从RFC 1738进行urlencode相差编码空格作为+代替%20


28

选择一个结果的一个实际原因是,如果要在另一个环境中使用结果,例如JavaScript。

在PHP中urlencode('test 1')返回,'test+1'rawurlencode('test 1')返回'test%201'结果。

但是,如果您需要使用解码URI()函数在JavaScript中对此进行“解码”,那么decodeURI("test+1")您会得到,而结果"test+1"decodeURI("test%201")会得到"test 1"

换句话说,由urlencode编码为PHP中加号(“ +”)的空格(“”)将无法由JavaScript中的encodeURI正确解码。

在这种情况下,应使用rawurlencode PHP函数。


6
到目前为止,这是我所见过的最好的答案。通过一个实际示例,它提供了使用建议。另外,它很简洁。
dotancohen 2012年

这是一个很好的例子,虽然我更喜欢json_encodeJSON.parse用于这一目的。
法布里西奥磨砂

21

我认为空格必须编码为:

  • %20 在URL路径组件内使用时
  • +在URL查询字符串组件或表单数据中使用时(请参见17.13.4表单内容类型

以下示例显示了rawurlencodeand 的正确用法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

2
“查询字符串参数q将包含lady gaga”,否则还将包含什么?无论使用还是在PHP 5.2+中,查询参数q似乎都将相同的值传递给$_GET数组。虽然,以GET请求的默认格式编码,所以我将采用您的方法。+1rawurlencodeurlencodeurlencodeapplication/x-www-form-urlencoded
法布里西奥磨砂

2
我想澄清一下+%20当在查询字符串中使用时,和都被解码为空格。
Salman A

5

区别在于返回值,即:

urlencode()

返回一个字符串,其中所有非字母数字字符(-_除外)。已由百分号(%)替换,后跟两个十六进制数字和空格,并编码为加号(+)。它的编码方式与WWW表单中发布的数据的编码方式相同,即与application / x-www-form-urlencoded媒体类型中的方式相同。这与»RFC 1738编码(请参阅rawurlencode())的不同之处在于,出于历史原因,空格被编码为加号(+)。

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">

2
OP询问如何知道使用哪个以及何时使用。如果他不知道不同返回值的重要性,那么知道每个空格的作用并不能帮助OP做出决定。
dotancohen 2012年

5

1.到底有什么区别和

唯一的区别在于对待空间的方式:

urlencode-基于旧版实现,将空格转换为+

rawurlencode-基于RFC 1738的空格转换为%20

造成这种差异的原因是因为+在URL中是保留的并且有效(未编码)。

2.哪个是首选?

我真的很想知道一些选择一个而不是另一个的理由...我希望能够选择一个并且永远用得最少。

公平地说,我在制定这些决定时会遵循一个简单的策略,我将与您分享一些希望对您有帮助的策略。

我认为这是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确实有这个想法,即使我从没碰过任何拒绝两种格式中的任何一种的人,但我想不出一种更好的策略来采用事实上的策略,对吗?

欢乐!



2

空格编码为%20vs.+

rawurlencode()在大多数情况下,我看到使用的最大原因是因为urlencode将文本空间编码为+(加号),而rawurlencode将其编码为常用的%20

echo urlencode("red shirt");
// red+shirt

echo rawurlencode("red shirt");
// red%20shirt

我已经特别看到某些接受编码的文本查询的API端点期望看到%20一个空格,结果,如果改用加号则失败。显然,这在API实现之间会有所不同,并且您的工作量可能会有所不同。


1

我相信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时。


0

简单* rawurlencode路径-路径是“?”之前的部分 -必须将空格编码为%20 * urlencode查询字符串-查询字符串是“?”之后的部分 -空格最好编码为“ +” = rawurlencode通常更兼容

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.