PHP接收GET中文参数乱码深入研究 [PHP]

post by 朦朧中的罪惡 / 2010-6-18 10:29 Friday

相信很多PHPer都会遇到这样的问题:在utf-8的页面下面,如果直接访问带有中文参数的地址如test.php?s=测试 这样的地址输出参数的值会乱码,在搜索引擎上查询了下相关资料,都只给出了一些解决方案,但是却没有人研究导致这个问题的原因,今天特写此文来深入这个问题产生的原因:

首先我们演示这个问题,测试代码和运行结果如下。

代码:

点击查看原图

测试结果:

点击查看原图

代码中声明了响应内容的编码为utf-8,显示的内容确实乱码。

在这里请注意var_dump出变量的长度只有4 ,很显然,两个中文字的长度在utf-8编码下肯定不止4个字节

然后我们再看一下Firefox的访问这个页面url

点击查看原图

FireFox会自动将中文url编码,所以我们可以看到测试变成了%B2%E2%CA%D4,很明显,这里一个字是两个字节,是gb2313、gbk等中文编码格式,而不是utf-8编码。

如果我们把页面的编码切换为gbk,中文参数就会显示正常,参见下图

点击查看原图

这时一个有趣的问题就诞生了:像emlog的中文标签这样的参数怎么就没有乱码呢?

多方测试后,我发现了一个小小的区别:

emlog中文参数的链接是在页面上生成的,而上面我们测试则用手直接在地址栏输入的,

如果我们直接输入例如http://be-evil.org/?tag=原创 这样的链接,程序同样会提示找不到标签

测试代码如下:

点击查看原图

测试结果,正常显示:

点击查看原图

请注意上图中红框标出的url编码,这次测试两个字是由6个字节组成,而不是先前的2个字节,因此表明中文参数已经正确的成为utf-8编码。

那么,是什么导致这个问题的发生呢?

答案是浏览器默认编码 在作怪,我们都用的是中文系统,浏览器默认的编码自然也会设置为本地化,例如我自己电脑上的IE的FireFox的默认编码都是gb系列的,请参看下图:

IE的默认设置:

点击查看原图

Firefox的默认设置:

点击查看原图

正因为这个设置,让浏览器在请求用户输入的url 时会默认把url中的中文以默认的编码格式发送而不是以页面的编码格式发送,这就是为什么页面中带有中文的链接正常而我们手动输入的链接会乱码的原因。同理,如果我们把浏览器的默认编码调整为utf-8,那么输入url中的中文则会按照utf-8编码。

除了上面的之外,还有以下情况会出现这种情况:

如果gbk编码的页面生成的地址链接到utf-8的页面,gbk页面的中文是按照gbk的格式编码传送给下个页面,那么utf-8编码接收后肯定会出现乱码。

IIS的url重写模块,重写后的中文编码也是gbk,如果你的页面是utf-8编码,那么重写参数将会失效。

像这些情况,我们就需要使用php内置的转码函数来处理编码问题了:

方案1:

$str = iconv("gb2312","utf-8",$str);

方案2:

mb_convert_encoding($str, "utf-8", "gb2312");

希望本文对那些因为编码问题而抓破头的PHPer们有所帮助 :)

标签: php 原创 疑难杂症

PHP实现html标签补全 [PHP]

post by 朦朧中的罪惡 / 2010-6-13 20:28 Sunday

我们有时候需要截断html的内容来输出,但是由于截断的位置并不确定,导致阶段后的html内容中的很多标签都没有正常闭合而导致页面变形,如何解决这个问题呢?当然是使用强大的正则表达式来匹配那些没有闭合的标签了,代码如下:

图片可以点击放大

点击查看原图

用了这个函数就可以将截断的html补全了,下面是测试代码:

点击查看原图

程序源码参见附件压缩包:

参考资料:

Close HTML Tags

标签: php 算法

PHP匹配GBK全中文字正则表达式(不包括GBK标点符号) [PHP]

post by 朦朧中的罪惡 / 2010-6-12 10:03 Saturday

今天朋友让我帮他弄个正则表达式,要求如下:

编码为gbk,要求,a-z,0-9,只匹配中文字不包括中文的标点符号如# ¥ % &等

查了下资料,网上匹配GBK中文的表达式都是使用16进制码通杀中文的那种

例如 "/^[".chr(0xa1)."-".chr(0xff)."]+$/" ,依然会把 这样的的字符匹配进去

测试代码如下:

点击查看原图

测试结果:

点击查看原图

去维基百科查阅相关资料 ,GBK的编码范围如下:

点击查看原图

根据资料继续查阅GBK码表

前面提供的表达式是从A1就开始了,而A1-A9正好就是符号段,难怪会匹配到

排除符号段后就可以得出中文字符的码段为0xB0 - 0xF7,0x81 - 0xA0, 0xAA - 0xFE

按照编码标准,中文字由两个字节组成,那么我们按照标准建立正则表达式

(([\xB0-\xF7][\xA1-\xFE])|([\x81-\xA0][\x40-\xFE])|([\xAA-\xFE][\x40-\xA0])|(\w))+

测试代码如下:

点击查看原图

运行结果,成功匹配中文字符,问题解决

点击查看原图

标签: php 原创

PHP解析XML异常问题解决 [PHP]

post by 朦朧中的罪惡 / 2010-6-10 10:38 Thursday

今天在调试emlog工具箱导入rss文件时php报错

内容为

Char 0x0 out of allowed range in Entity

Google搜索相关资料无果。郁闷了半天,后来突然想到Char 0x0,觉得这个应该是rss文本中含有非法的字符而导致xml解析失败.

查询了w3.org的xml字符范围 之后

上面定义了有效的uncode字符范围:

Char       ::=       #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]

虽然0x0可以存在于utf-8文件中,但是0x0很明显不在上面规定范围之内

那么我只需要把文档里面不符合上面标准的字符过滤掉应该就能解决这个问题

php函数如下

点击查看原图

在解析xml之前使用该函数来过滤掉非法字符,就能解决xml解析异常的问题了

参考资料

Invalid XML Characters: when valid UTF8 does not mean valid XML

标签: php 原创 疑难杂症

PHP识别url重写请求 [PHP]

post by 朦朧中的罪惡 / 2010-6-9 10:28 Wednesday

现在很多PHP程序都使用了url重写技术来优化url,但是有时候我们要在PHP程序中区分默认url的请求和url重写后的请求,这该如何处理呢?

我的解决方案是在常量$_SERVER里面找答案,在不同的服务器中$_SERVER都会有一个索引用来记录重写请求访问重写之前的路径

根据我的测试,部分主流服务器的索引如下

IIS7 + Rewrite Module -> $_SERVER['HTTP_X_ORIGINAL_URL']

IIS6 + ISAPI Rewite -> $_SERVER['HTTP_X_REWRITE_URL’]

Apache2 -> $_SERVER['REQUEST_URI’] 或 $_SERVER['REDIRECT_URL']

nginx -> $_SERVER['REQUEST_URI’]

有了这个参数,我们就可以在PHP中来判断请求url来自于重写地址的还是默认格式的地址了

标签: php 原创

mysql_fetch_object函数读取SELECT COUNT(*)数据的方法 [PHP]

post by 朦朧中的罪惡 / 2010-5-18 21:57 Tuesday

mysql_fetch_object 函数返回的对象形式的数据

点击查看原图

但是遇到在SQL语句中出现SELECT Count(*)和 SELECT  SUM等情况时,就不能使用上面的方法取得列的数据了,目前的解决方案是给Count(*)增加一个别名,请看下面的代码:

点击查看原图

起了别名之后,我们就可使用与别名同名的属性来访问列的数据了。

最后,这个解决方案除了mysql_fetch_object之外,还适用于mysql_fetch_assoc函数

标签: php 原创

几个很酷的但是你不一定知道的PHP小技巧 [PHP]

post by 朦朧中的罪惡 / 2010-4-24 22:34 Saturday

今天在PHPBulider上看到了一篇文章《Six Cool PHP Tricks You May Not Know》

作者介绍了6个很有意思的小技巧,在这里翻译并记录一下

1.快速判断字符串的长度

一般我们都是使用strlen函数来判断,但是这里有一个更快的方法,下面是实现代码

点击查看原图

2.像使用一个函数那样使用echo

我一直认为,如果想连接字符串时,只能按照常规出牌。但是实际上你可以像是用函数那样使用echo,将打印的字符串使用逗号隔开(这么做速度也更快),请参见下面的代码

点击查看原图

3.尽可能的使用单引号

用单引号代替双引号,你就让PHP节省了从字符串中解析变量的时间,不仅如此,这么写还会对程序员更加友好一些,因为这样可以更加容易从你的代码中找到变量。

同样,使用字符串作为数组索引的时候要这么做,这样可以防止PHP花更多的时间研究你到底要表达什么意思.

4.变量的变量

很多情况下我们可能要访问一些动态的变量(变量名不确定),你可以很容易在PHP使用变量的变量来实现。

参看以下代码

点击查看原图

剩下的2条

5.在表单字段中使用数组

6.PHP输出缓存

估计做PHP的应该都知道,我就不在这里陈述了...原文页面在这里

标签: php

PHP计算相关两个时区的时间差 [PHP]

post by 朦朧中的罪惡 / 2010-4-19 9:03 Monday

我们在开发通用PHP程序的时候可能遇到这样的情况:

服务器配置的时区和用户的时区不一样,而不同的用户也可能子在不同时区,不同的服务器默认的时区也不会不一样,那么就会经常涉及一个不同时区时间转换的问题,而获得不同时区的时差就是一个很头疼的问题。

PHP在5.1之后提供了一个DateTimeZone来帮助我们转换时区的时间,我们可以使用这个类来处理时间转换的问题

下面看代码(点击放大):

点击查看原图

参考资料 http://cn.php.net/manual/en/function.timezone-offset-get.php

详细代码可以参见附件 timezone.php

附件下载:
timezone.zip 659字节

标签: php 原创

PHP为fopen,file_get_contents等函数请求web地址时增加Http头的方法 [PHP]

post by 朦朧中的罪惡 / 2010-3-29 22:13 Monday

我们在使用fsockopen时可以方便的自定义自己请求的http头内容来访问某些对客户端请求头有特殊限制的网站,但是使用fopen,file_get_contents等函数请求web地址时怎么来灵活定义请求的http头呢?

解决方案:stream_context_create() 函数

fopen实现

<?php
$opts 
= array(
  
'http'=>array(
    
'method'=>"GET",
    
'header'=>"Accept-language: en\r\n" .
              
"Cookie: foo=bar\r\n"
  
)
);

$context stream_context_create($opts);

$fp fopen('http://www.example.com''r'false$context);
fpassthru($fp);
fclose($fp);
?>
 
file_get_contents实现
 
<?php
$opts 
= array(
  
'http'=>array(
    
'method'=>"GET",
    
'header'=>"Accept-language: en\r\n" .
              
"Cookie: foo=bar\r\n"
  
)
);

$context stream_context_create($opts);

file_get_contents('http://www.example.com', null$context);
?>

标签: php 原创

php使用fsockopen读取分段数据(transfer-encoding: chunked) [PHP]

post by 朦朧中的罪惡 / 2010-3-29 22:08 Monday

使用fsockopen读取数据时遇到了一个神奇的问题,具体情况如下:

读取地址:http://blog.maxthon.cn/?feed=rss2

读取代码:

<?php
$fp = fsockopen("blog.maxthon.cn", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET /?feed=rss2 HTTP/1.1\r\n";
    $out .= "Host: blog.maxthon.cn\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
?>

返回http内容:

Date: Mon, 29 Mar 2010 10:16:13 GMT
Server: Apache/2.2.8 (Unix) mod_ssl/2.2.8 OpenSSL/0.9.8b PHP/5.2.6
X-Powered-By: PHP/5.2.6
X-Pingback: http://blog.maxthon.cn/xmlrpc.php
Last-Modified: Wed, 03 Mar 2010 03:13:41 GMT
ETag: "8f16b619f32188bde3bc008a60c2cc11"
Keep-Alive: timeout=15, max=120
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/xml; charset=UTF-8

22de
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"

<description><![CDATA[2009年12月31日
1711
.......
1fe8
]]></description>
            <content:encoded><![CDATA[<p>2009年12月31日<br />
1711</p>

请注意上面那些标红的4个字符,它们每隔一段数据就会出现一次,但是用其他的方法如curl,file_get_contents等取回的数据则没有这些玩意。换成其他的网站来抓取,也只是少数的网站会出现这种情况,多方搜索无解后,我无意中看到了上面返回头中有这么一个声明:Transfer-Encoding: chunked,而常见的Content-lenght字段没有了。这个声明的大致的意思是传输编码为分段方式。

在Google上搜索该关键词,在维基百科上找到对这个声明的解释(由于没有中文版,我只能自己按照意思翻译):

Chunked Transfer Encoding is a mechanism that allows HTTP messages to be split in several parts. This can be applied to both HTTP requests (from client to server) and HTTP responses (from server to client)

分块传输编码是一种机制,允许将HTTP消息分成几个部分传输。同时适用于HTTP请求(从客户端到服务器)和 HTTP响应(从服务器到客户端)

For example, let us consider the way in which an HTTP server  may transmit data  to a client application (usually a web browser). Normally, data delivered in HTTP responses is sent in one piece, whose length is indicated by the Content-Length header field. The length of the data is important, because the client needs to know where the response ends and any following response starts. With chunked encoding, however, the data is broken up into a series of blocks of data and transmitted in one or more "chunks" so that a server may start sending data before it knows the final size of the content that it's sending. Often, the size of these blocks is the same, but this is not always the case.


例如,让我们考虑HTTP服务器可将数据传输到客户端应用程序(通常是一个网络浏览器)使用哪些方式。通常情况下,在HTTP响应数据是按照一整块发送给客户端的,数据的长度是由Content - Length头域表示。数据的长度很重要,因为客户需要知道在哪里响应结束和后面的响应何时启动。而使用Chunked编码方式,不管怎样,数据都会分割成一系列的数据块和一个或多个转发的“块”,因此服务器在知道内容的长度之前,就可以开始发送数据后。通常情况下,这些数据块的大小是一样的,但也并不是绝对的。

大概意思了解后,我们来看例子:

Chunked编码使用若干个Chunk串连而成,由一个标明长度为0的chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定下一段正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0的Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。具体的Chunk编码格式如下:

编过码的响应内容:

HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25
这是第一段数据

1A
然后这是第二段数据
0
 

解码的数据:

这是第一段内容,然后这是第二段数据

情况搞清楚了,那么我们怎么来解码这个编码后的数据呢?

在php官方手册fsockopen函数下面的评论中,已经有很多人提出了解决方法

方法1.

<?php
function unchunk($result) {
    return preg_replace_callback(
        '/(?:(?:\r\n|\n)|^)([0-9A-F]+)(?:\r\n|\n){1,2}(.*?)'.
        '((?:\r\n|\n)(?:[0-9A-F]+(?:\r\n|\n))|$)/si',
        create_function(
            '$matches',
            'return hexdec($matches[1]) == strlen($matches[2]) ? $matches[2] : $matches[0];'
        ),
        $result
    );
}

方法二.

function unchunkHttp11($data) {
    $fp = 0;
    $outData = "";
    while ($fp < strlen($data)) {
        $rawnum = substr($data, $fp, strpos(substr($data, $fp), "\r\n") + 2);
        $num = hexdec(trim($rawnum));
        $fp += strlen($rawnum);
        $chunk = substr($data, $fp, $num);
        $outData .= $chunk;
        $fp += strlen($chunk);
    }
    return $outData;
}


注意:这两个函数的参数都是返回的http原始数据(包括头)

 

 


 

标签: php 原创

1 2 3