1. 程式人生 > >php setcookie對cookie值進行urlencode的問題及解決

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也不會有什麼變化。如果一定需要包含特殊字元,請注意避開協議保留字。