java php cookies_因为 Java 和 Php 在获取客户端 cookie 方式不同引发的 bug

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

实验

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

Java

请求信息

GET / HTTP/1.1

Host: localhost:7003

...

Cookie: test2=ab+cd; test1=ab%2Bcd

服务端

@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";

}

}

控制台输出

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

Php

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 源码分析

主要查看两处源码

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

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

编码扩展讨论

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;

}

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

请求的编码讨论

GET

当我们在 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

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

当我在表单里提交了一段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编码的问题。

原文链接

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值