fix(network): preserve encoded cookie signatures (#6527)

This commit is contained in:
jxxghp
2026-09-01 07:29:12 +08:00
parent 70a0019ae6
commit b2e3056ca0
2 changed files with 37 additions and 25 deletions
+5 -24
View File
@@ -8,7 +8,6 @@ import weakref
from contextlib import AsyncExitStack, asynccontextmanager, contextmanager
from pathlib import Path
from typing import Any, Dict, Literal, Optional, Tuple, Union, overload
from urllib.parse import quote, unquote
import chardet
import httpx
@@ -266,29 +265,12 @@ async def aclose_shared_async_transports() -> None:
_pending_eviction_tasks.difference_update(pending_evictions)
def _url_decode_if_latin(original: str) -> str:
"""
解码URL编码的字符串,只解码文本,二进制数据保持不变
:param original: URL编码字符串
:return: 解码后的字符串或原始二进制数据
"""
try:
# 先解码
decoded = unquote(original, encoding="latin-1")
# 再完整编码
fully_encoded = quote(decoded, safe="")
# 验证
decoded_again = unquote(fully_encoded, encoding="latin-1")
if decoded_again == decoded:
return decoded
except Exception:
pass
return original
def cookie_parse(cookies_str: str, array: bool = False) -> Union[list, dict]:
"""
解析cookie,转化为字典或者数组
将 Cookie 请求头字符串解析为字典或浏览器 Cookie 数组
Cookie 值属于不透明数据,百分号编码可能是签名的一部分,必须保持浏览器原值。
:param cookies_str: cookie字符串
:param array: 是否转化为数组
:return: 字典或者数组
@@ -301,8 +283,7 @@ def cookie_parse(cookies_str: str, array: bool = False) -> Union[list, dict]:
for cookie in cookies:
cstr = cookie.split("=", 1) # 只分割第一个=,因为value可能包含=
if len(cstr) > 1:
# URL解码Cookie值(但保留Cookie名不解码)
cookie_dict[cstr[0].strip()] = _url_decode_if_latin(cstr[1].strip())
cookie_dict[cstr[0].strip()] = cstr[1].strip()
if array:
return [{"name": k, "value": v} for k, v in cookie_dict.items()]
return cookie_dict
+32 -1
View File
@@ -1,7 +1,7 @@
import requests
from app.adapters.network import http as http_module
from app.adapters.network.http import AsyncRequestUtils, RequestUtils
from app.adapters.network.http import AsyncRequestUtils, RequestUtils, cookie_parse
class _FakeSession:
@@ -195,3 +195,34 @@ def test_request_utils_normalizes_per_request_cookie_string(monkeypatch) -> None
assert result is response
assert prepared_requests[0].headers["Cookie"] == "sid=1; token=two"
def test_cookie_parse_preserves_percent_encoded_signature_values(monkeypatch) -> None:
"""Cookie 签名中的百分号编码必须按浏览器原值发送,不能被 URL 解码破坏。"""
raw_cookie = "session=abc%2Fdef%2Bghi%3D%3D; userid=123"
parsed_cookie = cookie_parse(raw_cookie)
browser_cookies = cookie_parse(raw_cookie, array=True)
response = _make_response()
session = requests.Session()
prepared_requests = []
def fake_send(request, **_kwargs):
prepared_requests.append(request)
return response
monkeypatch.setattr(session, "send", fake_send)
result = RequestUtils(session=session, cookies=raw_cookie).get_res(
"https://example.com/data"
)
assert result is response
assert parsed_cookie == {
"session": "abc%2Fdef%2Bghi%3D%3D",
"userid": "123",
}
assert browser_cookies == [
{"name": "session", "value": "abc%2Fdef%2Bghi%3D%3D"},
{"name": "userid", "value": "123"},
]
assert prepared_requests[0].headers["Cookie"] == raw_cookie