Skip to content

Commit 861e965

Browse files
Address PR review: improve tests, Sphinx roles, remove pyupgrade pin
- Use :cve:`2026-3644` and :external+python:exc: roles in changelog - Add pytest IDs to CTL chars from octal parametrize - Parametrize literal CTL char test (was two separate tests) - Use any() instead of list + in for semantic clarity - Replace unittest.mock.patch with monkeypatch fixture (no new dep) - Use @pytest.mark.usefixtures for void fixture injection - Remove pyupgrade language_version: python3.13 pin
1 parent cece051 commit 861e965

File tree

3 files changed

+24
-18
lines changed

3 files changed

+24
-18
lines changed

.pre-commit-config.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ repos:
103103
hooks:
104104
- id: pyupgrade
105105
args: ['--py37-plus']
106-
language_version: python3.13
107106
- repo: https://github.com/PyCQA/flake8
108107
rev: '7.3.0'
109108
hooks:

CHANGES/12395.bugfix.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Fixed a crash (``CookieError``) in the cookie parser when receiving cookies
2-
containing ASCII control characters on CPython builds with the CVE-2026-3644
1+
Fixed a crash (:external+python:exc:`~http.cookies.CookieError`) in the cookie parser when receiving cookies
2+
containing ASCII control characters on CPython builds with the :cve:`2026-3644`
33
patch. The parser now gracefully falls back to storing the raw, still-escaped
44
``coded_value`` when the decoded value contains control characters, and skips
55
cookies whose raw header contains literal control characters that cannot be

tests/test_cookie_helpers.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
import logging
44
import sys
55
import time
6-
import typing
76
from http.cookies import (
87
CookieError,
98
Morsel,
109
SimpleCookie,
1110
_unquote as simplecookie_unquote,
1211
)
13-
from unittest.mock import patch
1412

1513
import pytest
1614

@@ -1139,8 +1137,18 @@ def test_parse_set_cookie_headers_uses_unquote_with_octal(
11391137
@pytest.mark.parametrize(
11401138
("header", "expected_name", "expected_coded"),
11411139
[
1142-
(r'name="\012newline\012"', "name", r'"\012newline\012"'),
1143-
(r'tab="\011separated\011values"', "tab", r'"\011separated\011values"'),
1140+
pytest.param(
1141+
r'name="\012newline\012"',
1142+
"name",
1143+
r'"\012newline\012"',
1144+
id="newline-octal-012",
1145+
),
1146+
pytest.param(
1147+
r'tab="\011separated\011values"',
1148+
"tab",
1149+
r'"\011separated\011values"',
1150+
id="tab-octal-011",
1151+
),
11441152
],
11451153
)
11461154
def test_parse_set_cookie_headers_ctl_chars_from_octal(
@@ -1183,8 +1191,7 @@ def test_parse_set_cookie_headers_literal_ctl_chars_preserves_others() -> None:
11831191
result = parse_set_cookie_headers(['bad="a\x07b"; good=value', "another=cookie"])
11841192
# "good" is an attribute of "bad" (same header), so it's not a separate cookie.
11851193
# "another" is in a separate header and must always be preserved.
1186-
names = [name for name, _ in result]
1187-
assert "another" in names
1194+
assert any(name == "another" for name, _ in result)
11881195

11891196

11901197
# Tests for parse_cookie_header (RFC 6265 compliant Cookie header parser)
@@ -1660,8 +1667,7 @@ def test_parse_cookie_header_literal_ctl_chars() -> None:
16601667
result = parse_cookie_header('name="a\x07b"; good=cookie')
16611668
# On CPython with CVE-2026-3644 patch the bad cookie is skipped;
16621669
# on older builds it may be accepted. Either way, no crash.
1663-
names = [name for name, _ in result]
1664-
assert "good" in names
1670+
assert any(name == "good" for name, _ in result)
16651671

16661672

16671673
@pytest.mark.parametrize(
@@ -1855,23 +1861,24 @@ def test_unquote_compatibility_with_simplecookie(test_value: str) -> None:
18551861

18561862

18571863
@pytest.fixture
1858-
def mock_strict_morsel() -> typing.Iterator[None]:
1864+
def mock_strict_morsel(
1865+
monkeypatch: pytest.MonkeyPatch,
1866+
) -> None:
18591867
original_setstate = Morsel.__setstate__ # type: ignore[attr-defined]
18601868

18611869
def _mock_setstate(self: Morsel[str], state: dict[str, str]) -> None:
18621870
if any(ord(c) < 32 for c in state.get("value", "")):
18631871
raise CookieError()
18641872
original_setstate(self, state)
18651873

1866-
with patch(
1874+
monkeypatch.setattr(
18671875
"aiohttp._cookie_helpers.Morsel.__setstate__",
1868-
autospec=True,
1869-
side_effect=_mock_setstate,
1870-
):
1871-
yield
1876+
_mock_setstate,
1877+
)
18721878

18731879

1874-
def test_cookie_helpers_cve_fallback(mock_strict_morsel: None) -> None:
1880+
@pytest.mark.usefixtures("mock_strict_morsel")
1881+
def test_cookie_helpers_cve_fallback() -> None:
18751882
m: Morsel[str] = Morsel()
18761883
assert helpers._safe_set_morsel_state(m, "k", "v\n", "v\\012") is True
18771884
assert m.value == "v\\012"

0 commit comments

Comments
 (0)