博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
因为 Java 和 Php 在获取客户端 cookie 方式不同引发的 bug
阅读量:6228 次
发布时间:2019-06-21

本文共 6204 字,大约阅读时间需要 20 分钟。

遇到个 Java 和 Php 在获取客户端 cookie 方式不同导致跨系统的问题。所以写了这篇博客梳理下相关知识。

实验

下面通过两个简单的实验,来看Java和Php在获取web请求中的cookie的不同之处,我下面贴出http请求的相关信息,和服务端输出的结果。

Java

请求信息

GET / HTTP/1.1Host: localhost:7003...Cookie: test2=ab+cd; test1=ab%2Bcd复制代码

服务端

@Controller@Slf4jpublic 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";    }}复制代码

控制台输出

2019-05-16 18:03:32.770  INFO 10114 --- [nio-7003-exec-1] net.mengkang.demo.MainController         : test2=ab+cd2019-05-16 18:03:32.770  INFO 10114 --- [nio-7003-exec-1] net.mengkang.demo.MainController         : test1=ab%2Bcd复制代码

Php

GET / HTTP/1.1Host: 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 源码分析

主要查看两处源码

main/php_variables.cext/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操作,下面附带其源码,且加上一段测试代码

#include 
#include
#include
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值不一样了就。

编码扩展讨论

rawurlencodeurlencode的区别是什么?

手册上的解释是:

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;}复制代码

通过源码可以看到就是对+处理没有了。

请求的编码讨论

GET

当我们在 url 传递+的时候,浏览器不会默认为我们执行urlencode操作,但是 php 服务端取值的时候(还是上面那段代码)会执行urldecode,导致url中的+被去掉。这一点也非常好检测。

var_dump($_GET);复制代码
curl http://localhost:8084/a.php\?a=\bbb+carray(1) {  ["a"]=>  string(5) "bbb c"}复制代码

POST

当我们的做表单提交post请求的时候,默认表单的编码规范就是application/x-www-form-urlencoded,这样浏览器会自动的对我们的数据就行一次urlencode编码,之后 php 服务端收到$_POST数据会再进行urldecode

复制代码

当我在表单里提交了一段ab+cd的内容,请求数据如下

POST /a.php HTTP/1.1...Host: localhost:8084Content-Type: application/x-www-form-urlencoded...Cookie: test2=ab+cd; test1=ab%2BcdpostData=ab%2Bcd复制代码

服务端

# a.phpvar_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编码的问题。

本文作者:周梦康

本文为云栖社区原创内容,未经允许不得转载。

转载于:https://juejin.im/post/5cecd8b0518825409b03d574

你可能感兴趣的文章
react学习笔记二----nodejs服务器搭建及异常处理
查看>>
PHP 操作 Beanstalkd 方法及参数注释
查看>>
Elasticsearch Java High Level REST Client(入门)
查看>>
开了香槟的Kubernetes并不打算放慢成功的脚步
查看>>
AngularJs与Angular 常用的指令写法的区别;
查看>>
如何在Angular6下使用ng-zorro-antd
查看>>
【React进阶系列】从零开始手把手教你实现一个Virtual DOM(一)
查看>>
区块链概念 That You Must Know 第一期.md
查看>>
webpack3.0 前端工程化
查看>>
ES2018 新特征之:异步迭代器 for-await-of
查看>>
在Vue中使用highCharts绘制3d饼图
查看>>
vue+webpack+nginx 部署在服务器非根目录下访问404问题
查看>>
观麦在 Webpack 的经验
查看>>
angular.js和vue.js中实现函数去抖(debounce)
查看>>
Laravel SMS 短信发送包
查看>>
OkHttp之ApplicationInterceptors与NetworkInterceptors
查看>>
scrapy入门教程——爬取豆瓣电影Top250!
查看>>
手摸手,带你优雅的使用 icon
查看>>
wiremock使用入门
查看>>
Yii2事件示例解析
查看>>