因为 Java 和 Php 在获取客户端 cookie 方式不同引发的 bug
遇到個 Java 和 Php 在獲取客戶端 cookie 方式不同導(dǎo)致跨系統(tǒng)的問題。所以寫了這篇博客梳理下相關(guān)知識。
實驗
下面通過兩個簡單的實驗,來看Java和Php在獲取web請求中的cookie的不同之處,我下面貼出http請求的相關(guān)信息,和服務(wù)端輸出的結(jié)果。
Java
請求信息
GET / HTTP/1.1 Host: localhost:7003 ... Cookie: test2=ab+cd; test1=ab%2Bcd復(fù)制代碼服務(wù)端
@Controller @Slf4j public class MainController {@Autowiredprivate HttpServletRequest request;@GetMapping("/")public @ResponseBodyString index() {Cookie[] cookies = request.getCookies();if (null != cookies) {for (Cookie cookie : cookies) {log.info(cookie.getName() + "=" + cookie.getValue());}}return "index";} }復(fù)制代碼控制臺輸出
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復(fù)制代碼Php
GET / HTTP/1.1 Host: localhost:8084 ... Cookie: test2=ab+cd; test1=ab%2Bcd復(fù)制代碼服務(wù)端
var_exprot($_COOKIE); array ('test2' => 'ab cd','test1' => 'ab+cd', )復(fù)制代碼結(jié)果對比
發(fā)現(xiàn)Java是不會對cookie數(shù)據(jù)做任何處理,但是php則會默認(rèn)進(jìn)行一次urldecode操作,這導(dǎo)致了,兩邊系統(tǒng)里面獲取同一cookie時,結(jié)果不一致的 bug。
類似的問題 PHP 在解析外部變量時的一個 BUGPhp 源碼分析
主要查看兩處源碼
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"; //可以在我們?yōu)g覽器里看到請求的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);} }復(fù)制代碼我們看到cookie的值會被執(zhí)行php_url_decode操作,下面附帶其源碼,且加上一段測試代碼
#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;}復(fù)制代碼上面php_url_decode用到了php_htoi,這個是因為urlencode是按照rfc1738對字符串中除了 -_. 之外的所有非字母數(shù)字字符都將被替換成百分號(%)后跟兩位十六進(jìn)制數(shù)。htoi作用就是Converting Hexadecimal Digits Into Integers。然后把計算出來的整型轉(zhuǎn)換為char,存回處理完之后的字符數(shù)組里。
小結(jié)
$_COOKIE的數(shù)據(jù)在 php 這邊是經(jīng)過urldecode的二手?jǐn)?shù)據(jù),這個導(dǎo)致和JAVA那邊獲取的cookie值不一樣了就。
編碼擴展討論
rawurlencode與urlencode的區(qū)別是什么?
手冊上的解釋是:
通過源碼可以看到就是對+處理沒有了。
請求的編碼討論
GET
當(dāng)我們在 url 傳遞+的時候,瀏覽器不會默認(rèn)為我們執(zhí)行urlencode操作,但是 php 服務(wù)端取值的時候(還是上面那段代碼)會執(zhí)行urldecode,導(dǎo)致url中的+被去掉。這一點也非常好檢測。
var_dump($_GET); curl http://localhost:8084/a.php\?a=\bbb+c array(1) {["a"]=>string(5) "bbb c" }復(fù)制代碼POST
當(dāng)我們的做表單提交post請求的時候,默認(rèn)表單的編碼規(guī)范就是application/x-www-form-urlencoded,這樣瀏覽器會自動的對我們的數(shù)據(jù)就行一次urlencode編碼,之后 php 服務(wù)端收到$_POST數(shù)據(jù)會再進(jìn)行urldecode。
<form action="a.php" method="post" ><input type="text" name="postData" value=""><input type="submit"> </form>復(fù)制代碼當(dāng)我在表單里提交了一段ab+cd的內(nèi)容,請求數(shù)據(jù)如下
POST /a.php HTTP/1.1 ... Host: localhost:8084 Content-Type: application/x-www-form-urlencoded ... Cookie: test2=ab+cd; test1=ab%2BcdpostData=ab%2Bcd復(fù)制代碼服務(wù)端
# a.php var_dump($_POST); var_dump(file_get_contents("php://input"));復(fù)制代碼輸出結(jié)果
array(1) {["postData"]=>string(5) "ab+cd" } string(16) "postData=ab%2Bcd"復(fù)制代碼另一種情況,如果我們post的表單執(zhí)行編碼為multipart/form-data,瀏覽器則不會對數(shù)據(jù)進(jìn)行編碼,服務(wù)端也不會對數(shù)據(jù)就行解碼。
所以當(dāng)我們在配置 url 參數(shù)和 cookie 的時候,一定要注意url編碼的問題。
原文鏈接
本文為云棲社區(qū)原創(chuàng)內(nèi)容,未經(jīng)允許不得轉(zhuǎn)載。
轉(zhuǎn)載于:https://juejin.im/post/5cecdcffe51d455cd73ba01a
總結(jié)
以上是生活随笔為你收集整理的因为 Java 和 Php 在获取客户端 cookie 方式不同引发的 bug的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C# 移除数组中重复项
- 下一篇: 学习性代码和使用不存在的代码