遇到個 Java 和 Php 在獲取客戶端 cookie 方式不一樣致使跨系統的問題。因此寫了這篇博客梳理下相關知識。php
下面經過兩個簡單的實驗,來看Java和Php在獲取web請求中的cookie的不一樣之處,我下面貼出http請求的相關信息,和服務端輸出的結果。html
請求信息git
GET / HTTP/1.1 Host: localhost:7003 ... Cookie: test2=ab+cd; test1=ab%2Bcd
服務端web
@Controller @Slf4j public class MainController { @Autowired private HttpServletRequest request; @GetMapping("/") public @ResponseBody String index() { Cookie[] cookies = request.getCookies(); if (null != cookies) { for (Cookie cookie : cookies) { log.info(cookie.getName() + "=" + cookie.getValue()); } } return "index"; } }
控制檯輸出api
2019-05-16 18:03:32.770 INFO 10114 --- [nio-7003-exec-1] net.mengkang.demo.MainController : test2=ab+cd 2019-05-16 18:03:32.770 INFO 10114 --- [nio-7003-exec-1] net.mengkang.demo.MainController : test1=ab%2Bcd
GET / HTTP/1.1 Host: localhost:8084 ... Cookie: test2=ab+cd; test1=ab%2Bcd
服務端數組
var_exprot($_COOKIE);
array ( 'test2' => 'ab cd', 'test1' => 'ab+cd', )
發現Java
是不會對cookie
數據作任何處理,可是php
則會默認進行一次urldecode
操做,這致使了,兩邊系統裏面獲取同一cookie
時,結果不一致的 bug。瀏覽器
相似的問題 PHP 在解析外部變量時的一個 BUGcookie
主要查看兩處源碼app
main/php_variables.c ext/standard/url.c
SAPI_API SAPI_TREAT_DATA_FUNC(php_default_treat_data) { ... switch (arg) { case PARSE_GET: case PARSE_STRING: separator = PG(arg_separator).input; break; case PARSE_COOKIE: separator = ";\0"; //能夠在咱們瀏覽器裏看到請求的header裏面cookie的分隔符就是這個 break; } var = php_strtok_r(res, separator, &strtok_buf); while (var) { val = strchr(var, '='); if (arg == PARSE_COOKIE) { /* Remove leading spaces from cookie names, needed for multi-cookie header where ; can be followed by a space */ while (isspace(*var)) { var++; } if (var == val || *var == '\0') { goto next_cookie; } } if (++count > PG(max_input_vars)) { php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", PG(max_input_vars)); break; } if (val) { /* have a value */ size_t val_len; size_t new_val_len; *val++ = '\0'; php_url_decode(var, strlen(var)); val_len = php_url_decode(val, strlen(val)); val = estrndup(val, val_len); if (sapi_module.input_filter(arg, var, &val, val_len, &new_val_len)) { php_register_variable_safe(var, val, new_val_len, &array); } efree(val); } else { size_t val_len; size_t new_val_len; php_url_decode(var, strlen(var)); val_len = 0; val = estrndup("", val_len); if (sapi_module.input_filter(arg, var, &val, val_len, &new_val_len)) { php_register_variable_safe(var, val, new_val_len, &array); } efree(val); } next_cookie: var = php_strtok_r(NULL, separator, &strtok_buf); } if (free_buffer) { efree(res); } }
咱們看到cookie的值會被執行php_url_decode
操做,下面附帶其源碼,且加上一段測試代碼curl
#include <stdio.h> #include <ctype.h> #include <memory.h> static int php_htoi(char *s) { int value; int c; c = ((unsigned char *) s)[0]; if (isupper(c)) c = tolower(c); value = (c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10) * 16; c = ((unsigned char *) s)[1]; if (isupper(c)) c = tolower(c); value += c >= '0' && c <= '9' ? c - '0' : c - 'a' + 10; return (value); } size_t php_url_decode(char *str, size_t len) { char *dest = str; char *data = str; while (len--) { if (*data == '+') { *dest = ' '; } else if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) && isxdigit((int) *(data + 2))) { *dest = (char) php_htoi(data + 1); data += 2; len -= 2; } else { *dest = *data; } data++; dest++; } *dest = '\0'; return dest - str; } int main() { char a[6] = {"ab+cd"}; php_url_decode(a, strlen(a)); printf("%s\n", a); return 0; }
上面php_url_decode
用到了php_htoi
,這個是由於urlencode
是按照rfc1738
對字符串中除了 -_. 以外的全部非字母數字字符都將被替換成百分號(%)後跟兩位十六進制數。htoi
做用就是Converting Hexadecimal Digits Into Integers
。而後把計算出來的整型轉換爲char
,存回處理完以後的字符數組裏。
$_COOKIE
的數據在 php 這邊是通過urldecode
的二手數據,這個致使和JAVA那邊獲取的cookie值不同了就。
rawurlencode
與urlencode
的區別是什麼?
手冊上的解釋是:
urlencode
返回字符串,此字符串中除了 -_. 以外的全部非字母數字字符都將被替換成百分號(%)後跟兩位十六進制數,空格則編碼爲加號(+)。此編碼與 WWW 表單 POST 數據的編碼方式是同樣的,同時與 application/x-www-form-urlencoded 的媒體類型編碼方式同樣。因爲歷史緣由,此編碼在將空格編碼爲加號(+)方面與 » RFC3986 編碼(參見 rawurlencode())不一樣。
PHPAPI size_t php_raw_url_decode(char *str, size_t len) { char *dest = str; char *data = str; while (len--) { if (*data == '%' && len >= 2 && isxdigit((int) *(data + 1)) && isxdigit((int) *(data + 2))) { #ifndef CHARSET_EBCDIC *dest = (char) php_htoi(data + 1); #else *dest = os_toebcdic[(char) php_htoi(data + 1)]; #endif data += 2; len -= 2; } else { *dest = *data; } data++; dest++; } *dest = '\0'; return dest - str; }
經過源碼能夠看到就是對+
處理沒有了。
當咱們在 url 傳遞+
的時候,瀏覽器不會默認爲咱們執行urlencode
操做,可是 php 服務端取值的時候(仍是上面那段代碼)會執行urldecode
,致使url中的+
被去掉。這一點也很是好檢測。
var_dump($_GET);
curl http://localhost:8084/a.php\?a=\bbb+c array(1) { ["a"]=> string(5) "bbb c" }
當咱們的作表單提交post
請求的時候,默認表單的編碼規範就是application/x-www-form-urlencoded
,這樣瀏覽器會自動的對咱們的數據就行一次urlencode
編碼,以後 php 服務端收到$_POST
數據會再進行urldecode
。
<form action="a.php" method="post" > <input type="text" name="postData" value=""> <input type="submit"> </form>
當我在表單裏提交了一段ab+cd
的內容,請求數據以下
POST /a.php HTTP/1.1 ... Host: localhost:8084 Content-Type: application/x-www-form-urlencoded ... Cookie: test2=ab+cd; test1=ab%2Bcd postData=ab%2Bcd
服務端
# a.php var_dump($_POST); var_dump(file_get_contents("php://input"));
輸出結果
array(1) { ["postData"]=> string(5) "ab+cd" } string(16) "postData=ab%2Bcd"
另外一種狀況,若是咱們post的表單執行編碼爲multipart/form-data
,瀏覽器則不會對數據進行編碼,服務端也不會對數據就行解碼。
因此當咱們在配置 url 參數和 cookie 的時候,必定要注意url編碼的問題。
本文做者:周夢康
本文爲雲棲社區原創內容,未經容許不得轉載。