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

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


当前回答

选择其中一种而不是另一种的一个实际原因是,如果您将在另一种环境中使用结果,例如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函数。

其他回答

证明在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">

选择其中一种而不是另一种的一个实际原因是,如果您将在另一种环境中使用结果,例如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函数。

简单的 * rawurlencode路径 - path是“?”之前的部分。 -空格必须编码为%20 * urlencode查询字符串 —查询字符串为?后的部分。 -空格更好地编码为“+” = 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