urlencode vs rawurlencode?

2022-08-30 05:54:58

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

究竟有什么区别,哪个是首选的?


答案 1

这将取决于您的目的。如果与其他系统的互操作性很重要,那么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将空格编码为加号(不像在rawulencode中所做的那样)(请参阅 http://us2.php.net/manual/en/function.urlencode.php%20)

返回一个字符串,其中除 -_ 之外的所有非字母数字字符。已替换为百分比 (%) 符号,后跟两个十六进制数字和编码为加号 (+) 的空格。它的编码方式与来自 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 中)。+


答案 2

证明在PHP的源代码中。

我将带您完成一个快速的过程,了解如何在将来的任何时间自己找到这种事情。请原谅我,有很多C源代码你可以略过(我解释一下)。如果你想复习一些C,一个很好的起点是我们的SO wiki

下载源代码(或使用 http://lxr.php.net/ 在线浏览),删除函数名称的所有文件,你会发现这样的东西:

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字节不是ASCII中的字节,它实际上是一个.我相信你在这里看到了混乱。L<

如果 Web 服务器已定义 EBCDIC,则这两个函数都管理 EBCDIC。

此外,它们都使用字符数组(think字符串类型)查找来获取一些值,该数组描述如下: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 Char 0x20(即“空格”),则在输出字符串中添加一个符号。+
  • 如果它不是一个空格,它也不是字母数字(),也不是和,,或字符,那么我们,输出一个符号到数组位置0,做一个数组查找数组(来自Apache的数组,将char转换为十六进制代码)的键(现在字符),然后我们按位向右移4, 将该值分配给字符 1,对于位置 2,我们分配相同的查找,除了我们预先形成一个逻辑并查看该值是否为 15(0xF),在这种情况下返回 1,否则返回 0。最后,你最终会得到一些编码的东西。isalnum(c)_-.%hexcharsos_toasciic
  • 如果它最终不是一个空格,它是字母数字或其中一个字符,它输出的正是它是什么。_-.

RAWURLENCODE:

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

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

  • 将当前字符指定给 中的匹配字符位置。str
  • 它检查当前字符是字母数字还是其中一个字符,如果不是,我们执行与URLENCODE几乎相同的赋值,其中它预先形成查找,但是,我们以不同的方式递增,使用而不是,这是因为字符串以不同的方式构建,但无论如何在最后达到相同的目标。_-.y++to[1]
  • 当循环完成并且长度消失时,它实际上终止字符串,分配字节。\0
  • 它返回编码的字符串。

差异:

  • UrlEncode检查空间,分配一个+号,RawURLEncode不。
  • UrlEncode不会为字符串分配字节,RawUrlEncode会(这可能是一个有争议的问题)\0
  • 它们的迭代方式不同,其中一个可能容易溢出格式错误的字符串,我只是建议这一点,我实际上还没有调查过。

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

EBCDIC的差异:

URLENCODE:

  • 与 ASCII 相同的迭代设置
  • 仍在将“空格”字符转换为 + 号。注意 - 我认为这需要在EBCDIC中编译,否则你最终会遇到一个错误?有人可以编辑并确认这一点吗?
  • 它检查现在的 char 是否是之前的 char,除了是 a 或 , OR 小于但大于 char , OR 大于和小于 但不是 a。或者大于(是的,EBCDIC有点混乱)。如果它与其中任何一个匹配,请执行与ASCII版本中类似的查找(它只是不需要在os_toascii中查找)。0.-A9Za_z

RAWURLENCODE:

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

概述

  • 两者都使用相同的十六进制查找表
  • URIEncode 不会终止带有 \0 的字符串,而原始代码会终止。
  • 如果你在EBCDIC工作,我建议使用RawUrlEncode,因为它管理着UrlEncode没有的(这是一个报告的问题)。值得注意的是,ASCII和EBCDIC 0x20都是空间。~
  • 它们的迭代方式不同,一个可能更快,一个可能容易发生基于内存或字符串的攻击。
  • URIEncode 通过数组查找将空格设置为 ,RawUrlEncode 将空格设置为 。+%20

免責聲明:我已经很多年没有接触过C,而且我已经很久没有看过EBCDIC了。如果我在某个地方错了,请告诉我。

建议的实现

基于所有这些,rawurlencode是大多数时候要走的路。正如你在乔纳森·芬格兰(Jonathan Fingland)的答案中看到的那样,在大多数情况下坚持下去。它涉及URI组件的现代方案,其中urlencode以旧学校的方式做事,其中+表示“空间”。

如果你试图在旧格式和新格式之间进行转换,请确保你的代码不会因为意外的双重编码而将解码的+号的东西变成空格,或者围绕这个space/20%/+问题出现类似的“哎呀”场景。

如果您使用的是较旧的系统,而较旧的软件不喜欢新格式,请坚持使用urlencode,但是,我相信%20实际上会向后兼容,因为在旧标准下,%20工作,只是不是首选。如果您愿意玩,请试一试,让我们知道它是如何为您解决的。

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


推荐