From 185d6b9a20b7d0e77f4c60435d17e5072bd4d704 Mon Sep 17 00:00:00 2001 From: greymoth Date: Sat, 27 Jun 2026 04:53:32 +0900 Subject: [PATCH] Fix request_to_curl() corrupting dict cookies with bytes keys/values (#7675) * Fix request_to_curl() corrupting dict cookies with bytes keys/values PR #7603 made the list-cookie branch of request_to_curl() bytes-safe via _cookie_value_to_unicode(), but left the sibling dict-cookie branch using raw f-string interpolation. A dict cookie with bytes keys/values (a supported and common form, e.g. Request(url, cookies={b"k": b"v"})) was rendered as --cookie 'b'k'=b'v'' instead of --cookie 'k=v', producing a broken curl command. Route the dict branch through the same _cookie_value_to_unicode() helper, mirroring the list branch. Add a regression test. Co-Authored-By: Claude Opus 4.8 (1M context) * fix: apply _cookie_value_to_unicode to list-branch cookie key/value --------- Co-authored-by: Claude Opus 4.8 (1M context) --- scrapy/utils/request.py | 7 +++++-- tests/test_utils_request.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/scrapy/utils/request.py b/scrapy/utils/request.py index 398403d90..4a85526c0 100644 --- a/scrapy/utils/request.py +++ b/scrapy/utils/request.py @@ -204,13 +204,16 @@ def request_to_curl(request: Request) -> str: cookies = "" if request.cookies: if isinstance(request.cookies, dict): - cookie = "; ".join(f"{k}={v}" for k, v in request.cookies.items()) + cookie = "; ".join( + f"{_cookie_value_to_unicode(k)}={_cookie_value_to_unicode(v)}" + for k, v in request.cookies.items() + ) cookies = f"--cookie '{cookie}'" elif isinstance(request.cookies, list): cookie = "; ".join( f"{_cookie_value_to_unicode(c['name'])}={_cookie_value_to_unicode(c['value'])}" if "name" in c and "value" in c - else f"{next(iter(c.keys()))}={next(iter(c.values()))}" + else f"{_cookie_value_to_unicode(next(iter(c.keys())))}={_cookie_value_to_unicode(next(iter(c.values())))}" for c in request.cookies ) cookies = f"--cookie '{cookie}'" diff --git a/tests/test_utils_request.py b/tests/test_utils_request.py index e4967d4e7..63926e9d2 100644 --- a/tests/test_utils_request.py +++ b/tests/test_utils_request.py @@ -401,6 +401,19 @@ class TestRequestToCurl: ) self._test_request(request_object, expected_curl_command) + def test_cookies_dict_bytes(self): + request_object = Request( + "https://www.httpbin.org/post", + method="POST", + cookies={b"foo": b"bar"}, + body=json.dumps({"foo": "bar"}), + ) + expected_curl_command = ( + "curl -X POST https://www.httpbin.org/post" + " --data-raw '{\"foo\": \"bar\"}' --cookie 'foo=bar'" + ) + self._test_request(request_object, expected_curl_command) + def test_cookies_list(self): request_object = Request( "https://www.httpbin.org/post", @@ -455,3 +468,16 @@ class TestRequestToCurl: " --data-raw '{\"foo\": \"bar\"}' --cookie 'foo=1'" ) self._test_request(request_object, expected_curl_command) + + def test_cookies_list_bytes_nonstandard_key(self): + request_object = Request( + "https://www.httpbin.org/post", + method="POST", + cookies=[{b"foo": b"bar"}], + body=json.dumps({"foo": "bar"}), + ) + expected_curl_command = ( + "curl -X POST https://www.httpbin.org/post" + " --data-raw '{\"foo\": \"bar\"}' --cookie 'foo=bar'" + ) + self._test_request(request_object, expected_curl_command)