PHP实现URL编码与解码时,参考的是RFC 1738与RFC 3986:
RFC 1738: http://www.faqs.org/rfcs/rfc1738.html
RFC 3986: http://www.faqs.org/rfcs/rfc3986.html
其中rawurlencode()方法在PHP5.3.0之前,会依据RFC 1738,将波浪线~视为不安全字符,编码成%7E。
在PHP5.3.0之后则符合RFC 3986标准,将~号作为无限制字符,根据URL所处部位,可以不进行编码(通常也如此)。在PHP5.3.4之后,~则完全不编码。
rawurlencode主要的依据还是RFC 3986,其中不进行编码的字符包括:
数字,大小写字母,点号,下划线,减号,波浪号 (即: 0-9A-Za-z._-~)
其它都采用8位(一个字节)的%XX形式来编码
而urlencode()方法,则采用非标准的技术,其中不进行编码的字符包括:
数字,大小写字母,点号,下划线,减号 (即: 0-9A-Za-z._-)
空格编码成+号
其它都采用8位(一个字节)的%XX形式来编码
可以看到,与urlencode()与rawurlencode()不同在于对波浪线、空格的处理。相同点在于对数字、大小写字母、减号 - ,点号 . ,下划线 _ 都不进行编码。
PHP文档中对urlencode()及rawurlencode()的说明:
urlencode ( string $str
) : string — 编码 URL 字符串
返回值
返回字符串,此字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数,空格则编码为加号(+)。
此编码与 WWW 表单 POST 数据的编码方式是一样的,
同时与 application/x-www-form-urlencoded 的媒体类型编码方式一样。
由于历史原因,此编码在将空格编码为加号(+)方面与 RFC3986 编码(参见 rawurlencode())不同。
rawurlencode ( string $str
) : string — 按照 RFC 3986 对 URL 进行编码
返回值
返回字符串,此字符串中除了 -_. 之外的所有非字母数字字符都将被替换成百分号(%)后跟两位十六进制数。
这是在 » RFC 3986 中描述的编码,是为了保护原义字符以免其被解释为特殊的 URL 定界符,同时保护 URL 格式以免其被传输媒体(像一些邮件系统)使用字符转换时弄乱。
实例:
echo urlencode("中文(zh_CN) : 2020.11~2020.12--%20+Days[!$*',%#?]") . nl2br(PHP_EOL);
在PHP文件编码为GBK时,显示结果:
%D6%D0%CE%C4%28zh_CN%29+%3A+2020.11%7E2020.12--%2520%2BDays%5B%21%24%2A%27%2C%25%23%3F%5D
urlencode()在GBK编码下,可以看到:
中文两字被编码成 %D6%D0%CE%C4 这是它的GBK编码,每个字节(8位)前都加了%
空格被编码成 +
~被编码成 %7E
数字、字母、. _ - 号都不被编码
其它的特殊字符都编码为%XX形式
在PHP文件编码为UTF-8时,显示结果:
%E4%B8%AD%E6%96%87%28zh_CN%29+%3A+2020.11%7E2020.12--%2520%2BDays%5B%21%24%2A%27%2C%25%23%3F%5D
urlencode()的在UTF-8编码下,可以看到:
中文两字被编码成 %E4%B8%AD%E6%96%87 这是它的UTF-8编码,每个字节(8位)前都加了%
其它的与GBK编码结果一致。
echo rawurlencode("中文(zh_CN) : 2020.11~2020.12--%20+Days[!$*',%#?]") . nl2br(PHP_EOL);
在PHP文件编码为GBK时,显示结果:
%D6%D0%CE%C4%28zh_CN%29%20%3A%202020.11~2020.12--%2520%2BDays%5B%21%24%2A%27%2C%25%23%3F%5D
rawurlencode()在GBK编码下,可以看到:
中文两字被编码成 %D6%D0%CE%C4 这是它的GBK编码,每个字节(8位)前都加了%
空格被编码成 %20
数字、字母、. _ - ~ 号都不被编码
其它的特殊字符都编码为%XX形式
在PHP文件编码为UTF-8时,显示结果:
%E4%B8%AD%E6%96%87%28zh_CN%29%20%3A%202020.11~2020.12--%2520%2BDays%5B%21%24%2A%27%2C%25%23%3F%5D
rawurlencode()的在UTF-8编码下,可以看到:
中文两字被编码成 %E4%B8%AD%E6%96%87 这是它的UTF-8编码,每个字节(8位)前都加了%
其它的与GBK编码结果一致。
通过以上的例子,我们可以发现,urlencode()与rawurlencode()在PHP下自己也可以很方便实现,其原理就是:
不管文件编码是GBK还是UTF-8, 除了数字、大小写字母、._-号,以及特殊处理空格及波浪线,其它字符(单字节的ASCII码特殊字符,双字节的GBK或不定长字节的UTF-8)都是各字节的十六进制(两位)大写形式前加%(即%XX)
自定义实现urlencode()及rawurlencode()----兼容RFC 3986:
function my_urlencode($str,$raw=false,$ebcdic=true){ $url = ''; for($i=0;$i<strlen($str);$i++){ $ascii = ord(substr($str,$i,1)); if($ascii==0x2D || $ascii==0x2E || $ascii==0x5F || ($ascii>=0x30 && $ascii<=0x39) || ($ascii>=0x41 && $ascii<=0x5A) || ($ascii>=0x61 && $ascii<=0x7A) ){//_.-以及数字、字母不转化 $url .= chr($ascii); }else{ if($ascii==0x20 && !$raw){//空格在非raw情况下,转化为+ $url .= '+'; }else if($ascii==0x7e && $raw && $ebcdic){//波浪线在raw情况下不转化 $url .= chr($ascii); }else{ $url .= '%' . str_pad(strtoupper(dechex($ascii)),2,"0",STR_PAD_LEFT); } } } return $url; }
echo my_urlencode("中文+_zh .%- 2020~") . nl2br(PHP_EOL);
PHP文件编码为GBK时下显示:%D6%D0%CE%C4%2B_zh+.%25-+2020%7E
PHP文件编码为UTF-8时下显示:%E4%B8%AD%E6%96%87%2B_zh+.%25-+2020%7E
echo my_urlencode("中文+_zh .%- 2020~",true) . nl2br(PHP_EOL);
PHP文件编码为GBK时下显示:%D6%D0%CE%C4%2B_zh%20.%25-%202020~
PHP文件编码为UTF-8时下显示:%E4%B8%AD%E6%96%87%2B_zh%20.%25-%202020~
echo my_urlencode("中文+_zh .%- 2020~",true,false) . nl2br(PHP_EOL);
PHP文件编码为GBK时下显示:%D6%D0%CE%C4%2B_zh%20.%25-%202020%7E
PHP文件编码为UTF-8时下显示:%E4%B8%AD%E6%96%87%2B_zh%20.%25-%202020%7E
自定义实现urldecode()及rawurldecode()
function my_urldecode($str,$raw=false){ !$raw && $str = str_replace('+','%20',$str);//非raw的话需解码+号为空格 $url = ''; for($i=0;$i<strlen($str);$i++){ $c = substr($str,$i,1); if($c=='%'){ $url .= hex2bin(substr($str,$i+1,2)); $i = $i+2; if($i>=strlen($str)) break; }else{//_.-~以及数字、字母 $url .= $c; } } return $url; }
echo my_urldecode('%D6%D0%20%7E+~') . nl2br(PHP_EOL); echo my_urldecode('%D6%D0%20%7E+~',true) . nl2br(PHP_EOL);
PHP文件为GBK下显示:
中 ~ ~
中 ~+~
另外还可以对不是符合URL编码的字符串进行解码而不出错的情况,修改下:
function my_urldecode_ext($str,$raw=false){ !$raw && $str = str_replace('+','%20',$str);//非raw的话需解码+号为空格 $url = ''; $len = strlen($str); for($i=0;$i<$len;$i++){ $c = substr($str,$i,1); if($c=='%'){ if($i+1<=$len){ $c2 = substr($str,$i+1,1); if($i+2<=$len){ $c3 = substr($str,$i+2,1); if((ord($c2)>=0x30 && ord($c2)<=0x39) || (ord($c2)>=0x41 && ord($c2)<=0x46) || (ord($c2)>=0x61 && ord($c2)<=0x66)){ if((ord($c3)>=0x30 && ord($c3)<=0x39) || (ord($c3)>=0x41 && ord($c3)<=0x46) || (ord($c3)>=0x61 && ord($c3)<=0x66)){ $url .= hex2bin($c2.$c3); $i = $i+2; }else{ $url .= hex2bin("0".$c2); $i = $i+1; } }else{ $url .= '%'; } }else{ if((ord($c2)>=0x30 && ord($c2)<=0x39) || (ord($c2)>=0x41 && ord($c2)<=0x46) || (ord($c2)>=0x61 && ord($c2)<=0x66)){ $url .= hex2bin("0".$c2); $i = $i+1; }else{ $url .= '%'; } } }else{ $url .= '%'; } if($i>=strlen($str)) break; }else{//_.-~以及数字、字母 $url .= $c; } } return $url; }
echo my_urldecode_ext('%D6%D0%20%7E+~%x%%%7%E') . nl2br(PHP_EOL); echo my_urldecode_ext('%D6%D0%20%7E+~%x%%%7%E',true) . nl2br(PHP_EOL);
PHP文件为GBK下显示:
中 ~ ~%x%%
中 ~+~%x%%
其中%x、%%、%7、%E 由于不符合URL编码后的规则,被解码为:%x、%%、chr(0x7)、chr(0xE),后两者为为非显示的控制字符。