如果我想用一个变量创建一个URL,我有两个选择来编码字符串。Urlencode()和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);
}

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年(这有点催促,但在我看来仍然有可能)。

其他回答

证明在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年(这有点催促,但在我看来仍然有可能)。

区别在于返回值,即:

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

空格编码为%20 vs. +

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

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

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

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

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

%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

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