如果我想用一个变量创建一个URL,我有两个选择来编码字符串。Urlencode()和rawurlencode()。

到底有什么不同,哪个更可取?


urlencode:这与 »RFC 1738编码(见 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 vs 1738。rawurlencode在php 5.3之前根据RFC 1738对波浪字符(~)进行编码。然而,从PHP 5.3开始,rawurlencode遵循RFC 3986,它不需要编码波浪号字符。

Urlencode将空格编码为加号(不像rawurlencode中那样%20)(参见http://us2.php.net/manual/en/function.urlencode.php)

返回一个字符串,其中除-_之外的所有非字母数字字符。已替换为百分号(%)后面跟着两个十六进制数字和编码为加号(+)的空格。它的编码方式与WWW表单中发布的数据的编码方式相同,这与application/x-www-form-urlencoded media类型的编码方式相同。这与»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查询组件:

在查询组件,字符 ";", "/", "?", ":", "@", “&”、“=”、“+”、“,”和“$”保留。

正如您所看到的,+是查询字符串中的保留字符,因此需要按照RFC 3986进行编码(与rawurlencode一样)。


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 vs asd+asd

urlencode与RFC 1738的区别在于将空格编码为+而不是%20


区别在于返回值,即:

urlencode ():

返回一个字符串,其中all 非字母数字字符,-_除外。 已替换为百分比(%) 符号后面跟着两个十六进制数字和 空格编码为加号(+)。它 的编码方式与 从WWW表单发布的数据是 编码,这和 应用程序/ x-www-form-urlencoded 媒体类型。这与»不同 RFC 1738编码(参见rawurlencode()) 因为历史原因,空间 编码为加号(+)。

rawurlencode ():

返回一个字符串,其中all 非字母数字字符,-_除外。 已替换为百分比(%) 符号后面跟着两个十六进制数字。这 编码是否在»RFC中描述 保护文字字符 避免被解释为特殊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">

证明在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_encode和php_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字节不是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来解释它们。

ASCII的区别:

URLENCODE:

Calculates a start/end length of the input string, allocates memory Walks through a while-loop, increments until we reach the end of the string Grabs the present character If the character is equal to ASCII Char 0x20 (ie, a "space"), add a + sign to the output string. If it's not a space, and it's also not alphanumeric (isalnum(c)), and also isn't and _, -, or . character, then we , output a % sign to array position 0, do an array look up to the hexchars array for a lookup for os_toascii array (an array from Apache that translates char to hex code) for the key of c (the present character), we then bitwise shift right by 4, assign that value to the character 1, and to position 2 we assign the same lookup, except we preform a logical and to see if the value is 15 (0xF), and return a 1 in that case, or a 0 otherwise. At the end, you'll end up with something encoded. If it ends up it's not a space, it's alphanumeric or one of the _-. chars, it outputs exactly what it is.

RAWURLENCODE:

为字符串分配内存 基于函数调用中提供的长度迭代它(不像URLENCODE那样在函数中计算)。

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

Assigns the present character to a matching character position in str. It checks if the present character is alphanumeric, or one of the _-. chars, and if it isn't, we do almost the same assignment as with URLENCODE where it preforms lookups, however, we increment differently, using y++ rather than to[1], this is because the strings are being built in different ways, but reach the same goal at the end anyway. When the loop's done and the length's gone, It actually terminates the string, assigning the \0 byte. It returns the encoded string.

差异:

UrlEncode检查空格,分配一个+号,RawURLEncode没有。 UrlEncode不会将\0字节分配给字符串,而RawUrlEncode会(这可能是一个有争议的问题) 它们以不同的方式迭代,一个可能容易溢出畸形的字符串,我只是建议这个,我还没有真正研究过。

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

EBCDIC的差异:

URLENCODE:

Same iteration setup as with ASCII Still translating the "space" character to a + sign. Note-- I think this needs to be compiled in EBCDIC or you'll end up with a bug? Can someone edit and confirm this? It checks if the present char is a char before 0, with the exception of being a . or -, OR less than A but greater than char 9, OR greater than Z and less than a but not a _. OR greater than z (yeah, EBCDIC is kinda messed up to work with). If it matches any of those, do a similar lookup as found in the ASCII version (it just doesn't require a lookup in os_toascii).

RAWURLENCODE:

与ASCII相同的迭代设置 与EBCDIC版本的URL Encode中描述的检查相同,除了如果它大于z,它将从URL Encode中排除~。 与ASCII的RawUrlEncode相同的赋值 仍然在返回前将\0字节附加到字符串。

大总结

两者都使用相同的六边形查找表 URIEncode不会用\0结束字符串,raw会。 如果你在EBCDIC中工作,我建议使用RawUrlEncode,因为它管理UrlEncode没有的~(这是一个报告的问题)。值得注意的是,ASCII和EBCDIC 0x20都是空格。 它们的迭代方式不同,一个可能更快,另一个可能更容易利用内存或字符串。 URIEncode将空格转换为+,RawUrlEncode通过数组查找将空格转换为%20。

免责声明:我已经很多年没有碰过C了,我也已经很久很久没有看过EBCDIC了。如果我哪里说错了,请告诉我。

建议实现

基于所有这些,rawurlencode是大多数时候的选择。正如你在乔纳森·芬格兰的回答中看到的,在大多数情况下坚持下去。它处理URI组件的现代方案,其中urlencode使用老式方法,其中+表示“空格”。

如果您试图在旧格式和新格式之间进行转换,请确保您的代码不会出错,不会因为意外的双重编码而将已解码的+符号转换为空格,或者围绕空格/20%/+问题出现类似的“哎呀”情况。

如果您在一个不喜欢新格式的旧系统和旧软件上工作,请坚持使用urlencode,但是,我相信%20实际上是向后兼容的,因为在旧标准下%20可以工作,只是不受欢迎。试试吧,如果你想玩的话,让我们知道你是如何成功的。

基本上,你应该坚持生的,除非你的EBCDIC系统真的讨厌你。大多数程序员永远不会在2000年之后的任何系统上遇到EBCDIC,甚至可能是1990年(这有点催促,但在我看来仍然有可能)。


选择其中一种而不是另一种的一个实际原因是,如果您将在另一种环境中使用结果,例如JavaScript。

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

但如果你需要在JavaScript中使用decodeURI()函数“解码”这个,那么decodeURI(“test+1”)会给你“test+1”,而decodeURI(“test%201”)会给你“test 1”作为结果。

换句话说,在PHP中由urlencode编码到加号("+")的空格(" ")将不会被JavaScript中的decodeURI正确解码。

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


我认为空格必须被编码为:

%20当在URL路径组件中使用 +在URL查询字符串组件或表单数据中使用(参见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

web服务器将查找目录latest+songs,而不是最新的歌曲 查询字符串参数q将包含lady gaga


1. 到底有什么不同

唯一的区别是对待空格的方式:

基于遗留实现的Urlencode -将空格转换为+

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

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

2. 首选哪种?

我真的很想知道选择一个而不是另一个的一些原因……我希望能够选择一个,并永远使用它,而不是大惊小怪。

有道理,在做这些决定时,我有一个简单的策略,我将与你分享,希望它能有所帮助。

我记得是HTTP/1.1规范RFC 2616,它要求“宽容应用程序”

客户端应该容忍状态行和服务器的解析 当解析Request-Line时。

当面对这样的问题时,最好的策略总是尽可能多地消费和生产符合标准的产品。

所以我的建议是使用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似乎就是这么想的,尽管我从来没有遇到过有人拒绝这两种格式中的任何一种,但我想不出更好的策略来作为您的实际策略,不是吗?

nJoy !


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


空格编码为%20 vs. +

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

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

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

我特别见过某些接受编码文本查询的API端点期望看到空格%20,因此,如果使用加号则失败。显然,这在不同的API实现之间是不同的,您的里程可能会有所不同。


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