php setcookie对cookie值进行urlencode的问题及解决

1. 问题

有如下代码
setcookie.php

class Cookie{
    protected $_key = "person";
    protected $_val = "name:ball,sex:male";

    public function set(){
        $duration = 0;
        $path = "/";
        setcookie($this->_key, $this->_val, $duration, $path);
    }   

    public function get(){
        echo $_COOKIE[$this->_key];
    }   
}

我们先调用set(), 再调用get()。页面上输出

name:ball,sex:male

按说这是符合预期的。
但是使用chrome的debug工具查看cookie,发现person的值为

name%3Aball%2Csex%3Amale

在console中执行document.cookie,结果为

"person=name%3Aball%2Csex%3Amale"

也就是说,虽然php侧能设置并正常的取到cookie值,但是从浏览器或js侧看来,这个cookie是被编了码的。不方便js使用,也不方便人工排查问题时查看cookie。

2.解决

查手册,发现setcookie的确是对cookie值进行了urlencode。怎么绕开呢?我们想到setcookie的本质就是在response header中加入Set-Cookie响应头,于是决定尝试直接用header方法。set()代码调整如下:

public function set(){
    $str = sprintf("Set-Cookie:%s=%s;path=/", $this->_key, $this->_val);
    header($str);
}  

这时再从chrome侧查看cookie中的person值如下,并没有进行编码。

name:ball,sex:male

3.风险

2中的方法虽然解决了cookie值被编码的问题,但是会不会带来风险呢?
答案是会的。比如,如果cookie中带了分号(http协议中,Set-Cookie用来分隔键值对的关键字),就会产生bug。

为了详细说明问题,我们先看下2中例子的response header(主要截取Set-Cookie部分)

Server: nginx/1.4.1
Set-Cookie: person=name:ball,sex:male;path=/
Transfer-Encoding: chunked

下面修改代码

将
protected $_val = "name:ball,sex:male";
改为
protected $_val = "name:ball;sex:male";

response header变为

Server: nginx/1.4.1
Set-Cookie: person=name:ball;sex:male;path=/
Transfer-Encoding: chunked

ball后的分号将person值打断,后面的sex:male;被协议解析为无法识别的键值对,因而忽略。

get()方法的输出及浏览器中看的person值也变为

name:ball

4.建议

cookie值尽量简单,不含特殊符号,这样即使setcookie进行了urlencode也不会有什么变化。如果一定需要包含特殊字符,请注意避开协议保留字。

你可能感兴趣的:(php)