Processing math: 100%

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

简介: ## Java 请求信息 ```bash GET / HTTP/1.1 Host: localhost:7003 Connection: keep-alive Pragma: no-cache Cache-Control: no-cache Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.

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

实验

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

Java

请求信息

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

服务端

@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";
    }
}
AI 代码解读

控制台输出

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
AI 代码解读

Php

GET / HTTP/1.1
Host: localhost:8084
...
Cookie: test2=ab+cd; test1=ab%2Bcd
AI 代码解读

服务端

var_exprot($_COOKIE);
AI 代码解读
array (
  'test2' => 'ab cd',
  'test1' => 'ab+cd',
)
AI 代码解读

结果对比

发现Java是不会对cookie数据做任何处理,但是php则会默认进行一次urldecode操作,这导致了,两边系统里面获取同一cookie时,结果不一致的 bug。

类似的问题 PHP 在解析外部变量时的一个 BUG

Php 源码分析

主要查看两处源码

main/php_variables.c
ext/standard/url.c
AI 代码解读
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);
    }
}
AI 代码解读

我们看到cookie的值会被执行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;

}
AI 代码解读

上面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;
}
AI 代码解读

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

请求的编码讨论

GET

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

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

POST

当我们的做表单提交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>
AI 代码解读

当我在表单里提交了一段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
AI 代码解读

服务端

# a.php
var_dump($_POST);
var_dump(file_get_contents("php://input"));
AI 代码解读

输出结果

array(1) {
  ["postData"]=>
  string(5) "ab+cd"
}
string(16) "postData=ab%2Bcd"
AI 代码解读

另一种情况,如果我们post的表单执行编码为multipart/form-data,浏览器则不会对数据进行编码,服务端也不会对数据就行解码。

所以当我们在配置 url 参数和 cookie 的时候,一定要注意url编码的问题。

目录
打赏
0
0
0
0
936
分享
相关文章
|
1月前
|
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
【02】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-2月12日优雅草简化Centos stream8安装zabbix7教程-本搭建教程非docker搭建教程-优雅草solution
81 20
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
【01】客户端服务端C语言-go语言-web端PHP语言整合内容发布-优雅草网络设备监控系统-硬件设备实时监控系统运营版发布-本产品基于企业级开源项目Zabbix深度二开-分步骤实现预计10篇合集-自营版
34 0
【Bug合集】——Java大小写引起传参失败,获取值为null的解决方案
类中成员变量命名问题引起传送json字符串,但是变量为null的情况做出解释,@Data注解(Spring自动生成的get和set方法)和@JsonProperty
php学习笔记-php会话控制,cookie,session的使用,cookie自动登录和session 图书上传信息添加和修改例子-day07
本文介绍了PHP会话控制及Web常用的预定义变量,包括`REQUEST_SERVER`、`COOKIE_SESSION`的用法和示例。涵盖了cookie的创建、使用、删除以及session的工作原理和使用,并通过图书上传的例子演示了session在实际应用中的使用。
php学习笔记-php会话控制,cookie,session的使用,cookie自动登录和session 图书上传信息添加和修改例子-day07
ly~
|
6月前
|
php跟java有什么区别
PHP 和 Java 是两种常用编程语言,各有特色。PHP 语法简洁灵活,适用于快速开发中小型网站,尤其在 Web 脚本和数据库交互中表现出色。Java 则语法严谨,强类型特性使其在企业级应用、移动开发及大数据处理中更受欢迎,具备高稳定性和安全性。通过优化,PHP 性能可提升,而 Java 在大规模应用中表现更佳。总体而言,PHP 开发效率高但维护性稍差,Java 则更注重代码质量和安全性。
ly~
175 5
如何优雅地根治Java中Null值引起的Bug问题
【8月更文挑战第18天】在Java开发中,null 值是一个既常见又危险的存在。它常常是导致程序崩溃、难以调试的“罪魁祸首”。然而,通过一系列优雅的策略和实践,我们可以有效地减少甚至根除由 null 值引发的Bug。本文将从多个方面探讨如何做到这一点。
126 4
PHP——调用java文件中的方法
PHP——调用java文件中的方法
75 0
PHP——调用java文件中的方法
PHP中的异常处理:Java常见的编码方式
在PHP开发中,掌握异常处理至关重要,它有助于预见并管理运行时错误,避免用户体验受损或数据丢失。本文介绍PHP异常处理的基本概念与实践,包括try-catch语句的使用,以及如何通过抛出和捕获异常来增强代码的健壮性和可靠性。通过示例展示如何避免常见错误,如除数为零的情况,并探讨多catch块和finally语句的高级用法,帮助开发者提升程序稳定性与可维护性。[总字符数:238]
48 0
消息队列 MQ使用问题之如何获取PHP客户端代码
消息队列(MQ)是一种用于异步通信和解耦的应用程序间消息传递的服务,广泛应用于分布式系统中。针对不同的MQ产品,如阿里云的RocketMQ、RabbitMQ等,它们在实现上述场景时可能会有不同的特性和优势,比如RocketMQ强调高吞吐量、低延迟和高可用性,适合大规模分布式系统;而RabbitMQ则以其灵活的路由规则和丰富的协议支持受到青睐。下面是一些常见的消息队列MQ产品的使用场景合集,这些场景涵盖了多种行业和业务需求。

热门文章

最新文章