33import logging
44import sys
55import time
6- import typing
76from http .cookies import (
87 CookieError ,
98 Morsel ,
109 SimpleCookie ,
1110 _unquote as simplecookie_unquote ,
1211)
13- from unittest .mock import patch
1412
1513import 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)
11461154def test_parse_set_cookie_headers_ctl_chars_from_octal (
@@ -1150,18 +1158,17 @@ def test_parse_set_cookie_headers_ctl_chars_from_octal(
11501158
11511159 CPython builds with the CVE-2026-3644 patch reject control characters in
11521160 cookies. When octal unquoting produces a control character, the parser
1153- should fall back to the raw coded_value instead of raising CookieError.
1161+ skips the cookie entirely instead of raising CookieError.
11541162 """
11551163 result = parse_set_cookie_headers ([header ])
11561164
1157- assert len (result ) == 1
1158- name , morsel = result [0 ]
1159-
1160- assert name == expected_name
1161- assert morsel .coded_value == expected_coded
1162- # Depending on CPython build, morsel.value will either be the decoded string
1163- # (pre CVE-2026-3644 patch) or the raw coded_value (post patch).
1164- # We just ensure it doesn't crash and the coded_value is preserved.
1165+ # On CPython with CVE-2026-3644 patch the cookie is rejected (result is empty);
1166+ # on older builds it may be accepted with the decoded value.
1167+ # Either way, no crash.
1168+ if result :
1169+ name , morsel = result [0 ]
1170+ assert name == expected_name
1171+ assert morsel .coded_value == expected_coded
11651172
11661173
11671174def test_parse_set_cookie_headers_literal_ctl_chars () -> None :
@@ -1183,8 +1190,7 @@ def test_parse_set_cookie_headers_literal_ctl_chars_preserves_others() -> None:
11831190 result = parse_set_cookie_headers (['bad="a\x07 b"; good=value' , "another=cookie" ])
11841191 # "good" is an attribute of "bad" (same header), so it's not a separate cookie.
11851192 # "another" is in a separate header and must always be preserved.
1186- names = [name for name , _ in result ]
1187- assert "another" in names
1193+ assert any (name == "another" for name , _ in result )
11881194
11891195
11901196# Tests for parse_cookie_header (RFC 6265 compliant Cookie header parser)
@@ -1660,8 +1666,7 @@ def test_parse_cookie_header_literal_ctl_chars() -> None:
16601666 result = parse_cookie_header ('name="a\x07 b"; good=cookie' )
16611667 # On CPython with CVE-2026-3644 patch the bad cookie is skipped;
16621668 # on older builds it may be accepted. Either way, no crash.
1663- names = [name for name , _ in result ]
1664- assert "good" in names
1669+ assert any (name == "good" for name , _ in result )
16651670
16661671
16671672@pytest .mark .parametrize (
@@ -1855,27 +1860,26 @@ def test_unquote_compatibility_with_simplecookie(test_value: str) -> None:
18551860
18561861
18571862@pytest .fixture
1858- def mock_strict_morsel () -> typing .Iterator [None ]:
1863+ def mock_strict_morsel (
1864+ monkeypatch : pytest .MonkeyPatch ,
1865+ ) -> None :
18591866 original_setstate = Morsel .__setstate__ # type: ignore[attr-defined]
18601867
18611868 def _mock_setstate (self : Morsel [str ], state : dict [str , str ]) -> None :
18621869 if any (ord (c ) < 32 for c in state .get ("value" , "" )):
18631870 raise CookieError ()
18641871 original_setstate (self , state )
18651872
1866- with patch (
1873+ monkeypatch . setattr (
18671874 "aiohttp._cookie_helpers.Morsel.__setstate__" ,
1868- autospec = True ,
1869- side_effect = _mock_setstate ,
1870- ):
1871- yield
1872-
1875+ _mock_setstate ,
1876+ )
18731877
1874- def test_cookie_helpers_cve_fallback (mock_strict_morsel : None ) -> None :
1875- m : Morsel [str ] = Morsel ()
1876- assert helpers ._safe_set_morsel_state (m , "k" , "v\n " , "v\\ 012" ) is True
1877- assert m .value == "v\\ 012"
18781878
1879+ @pytest .mark .usefixtures ("mock_strict_morsel" )
1880+ def test_cookie_helpers_cve_fallback () -> None :
1881+ # With strict morsel: any CTL char in value → CookieError → rejected
1882+ assert helpers ._safe_set_morsel_state (Morsel (), "k" , "v\n " , "v\\ 012" ) is False
18791883 assert helpers ._safe_set_morsel_state (Morsel (), "k" , "v\n " , "v\n " ) is False
18801884
18811885 cookie : Morsel [str ] = Morsel ()
@@ -1886,3 +1890,4 @@ def test_cookie_helpers_cve_fallback(mock_strict_morsel: None) -> None:
18861890 assert parse_cookie_header ("f=b\x07 r" ) == []
18871891 assert parse_cookie_header ('f="b\x07 r";' ) == []
18881892 assert parse_set_cookie_headers (['f="b\x07 r";' ]) == []
1893+ assert parse_set_cookie_headers ([r'name="\012newline\012"' ]) == []
0 commit comments