证明在 PHP 的源代码中。
我将带您快速了解如何在将来随时自行找出此类问题。请耐心等待,您可以浏览很多 C 源代码(我会解释)。 If you want to brush up on some C, a good place to start is our 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 is another character set,类似于 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:
- 计算输入字符串的开始/结束长度,分配内存
- 遍历一个while循环,递增直到我们到达字符串的末尾
- 抓取当前角色
- 如果字符等于 ASCII Char 0x20(即“空格”),则在输出字符串中添加
+ 符号。
- 如果不是空格,也不是字母数字(
isalnum(c)),也不是_,-,或.字符,那么我们,输出一个%符号到数组位置 0,查找hexchars 数组以查找os_toascii 数组(从Apache that translates 字符到十六进制代码的数组)查找c 的键(当前字符),然后我们按位右移 4,将该值分配给字符 1,并将相同的查找分配给位置 2,除了我们执行逻辑并查看该值是否为 15 (0xF),并在这种情况下返回 1,或者否则为 0。最后,你会得到一些编码的东西。
- 如果它最终不是空格,而是字母数字或
_-. 字符之一,它会准确输出它的内容。
RAWURLENCODE:
- 为字符串分配内存
- 根据函数调用中提供的长度对其进行迭代(不像 URLENCODE 那样在函数中计算)。
注意:许多程序员可能从未见过这样迭代的 for 循环,这有点 hackish 并且不是大多数 for 循环使用的标准约定,请注意,它分配了 x 和 @ 987654345@,检查len 是否达到0,并增加x 和y。我知道,这不是您所期望的,但它是有效的代码。
- 将当前字符分配给
str 中的匹配字符位置。
- 它检查当前字符是字母数字还是
_-. 字符之一,如果不是,我们执行与执行查找的URLENCODE 几乎相同的分配,但是,我们使用@ 递增不同987654351@ 而不是to[1],这是因为字符串的构建方式不同,但最终还是达到了相同的目标。
- 当循环完成并且长度消失时,它实际上终止了字符串,并分配了
\0 字节。
- 它返回编码后的字符串。
区别:
- UrlEncode 检查空格,分配一个 + 号,RawURLEncode 没有。
- UrlEncode 不会将
\0 字节分配给字符串,RawUrlEncode 会(这可能是一个争论点)
- 它们的迭代方式不同,其中一个可能容易溢出格式错误的字符串,我只是建议这一点,而我没有实际调查过。
它们基本上迭代不同,一个在 ASCII 20 的情况下分配一个 + 号。
EBCDIC 的区别:
URLENCODE:
- 与 ASCII 相同的迭代设置
- 仍在将“空格”字符转换为 + 符号。注意——我认为这需要在 EBCDIC 中编译,否则最终会出现错误?有人可以编辑并确认吗?
- 它检查当前字符是否是
0之前的字符,除了.或-,OR小于A但大于字符@987654359 @, OR 大于 Z 且小于 a 但不是 _。 OR 大于 z(是的,EBCDIC 有点搞砸了)。如果它与其中任何一个匹配,请执行与 ASCII 版本中相似的查找(它只是不需要在 os_toascii 中查找)。
RAWURLENCODE:
- 与 ASCII 相同的迭代设置
- 与 EBCDIC 版本的 URL 编码中描述的检查相同,但如果它大于
z,则会从 URL 编码中排除 ~。
- 与 ASCII RawUrlEncode 相同的分配
- 在返回之前仍将
\0 字节附加到字符串。
大总结
- 两者都使用相同的 hexchars 查找表
- URIEncode 不会以 \0 终止字符串,而 raw 会。
- 如果您在 EBCDIC 中工作,我建议您使用 RawUrlEncode,因为它可以管理 UrlEncode 没有的
~ (this is a reported issue)。值得注意的是 ASCII 和 EBCDIC 0x20 都是空格。
- 它们的迭代方式不同,一种可能更快,一种可能容易受到基于内存或字符串的攻击。
- URIEncode 通过数组查找在
+ 中创建一个空格,RawUrlEncode 在%20 中创建一个空格。
免责声明:我已经很多年没有接触过 C 语言了,也很长时间没有看过 EBCDIC 了。如果我在某个地方错了,请告诉我。
建议的实现
基于所有这些,rawurlencode 是大多数时候要走的路。正如您在 Jonathan Fingland 的回答中看到的那样,在大多数情况下坚持下去。它处理 URI 组件的现代方案,其中 urlencode 以老式方式处理事情,其中 + 表示“空格”。
如果您尝试在旧格式和新格式之间进行转换,请确保您的代码不会出错,并通过意外双重编码或类似的“哎呀”将解码后的 + 符号转换为空格围绕这个空间/20%/+ 问题的场景。
如果您正在使用不喜欢新格式的旧软件在旧系统上工作,请坚持使用 urlencode,但是,我相信 %20 实际上会向后兼容,因为在旧标准下 %20 有效,只是不是首选。如果您愿意玩,请试一试,让我们知道它对您的效果。
基本上,您应该坚持使用 raw,除非您的 EBCDIC 系统真的讨厌您。大多数程序员永远不会在 2000 年之后制造的任何系统上遇到 EBCDIC,甚至可能是 1990 年(这是在推动,但在我看来仍然可能)。