From 818d92764bd7035a0450d7b4e52ec9fc6d07abb6 Mon Sep 17 00:00:00 2001 From: cobyfrombrooklyn-bot Date: Fri, 20 Feb 2026 18:15:09 -0500 Subject: [PATCH] Fix empty body with None values sending malformed chunked request When data={'foo': None}, the body encodes to an empty string but Content-Length was not set (because 0 is falsy). This caused the adapter to fall back to Transfer-Encoding: chunked, sending a terminating chunk that servers misinterpreted as a second request. Fix: Change the Content-Length check from 'if length:' to 'if length is not None and Transfer-Encoding not in headers', so that Content-Length: 0 is correctly set for empty bodies while preserving chunked encoding for streams. Fixes #6122 --- src/requests/models.py | 10 +++++++--- tests/test_requests.py | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/requests/models.py b/src/requests/models.py index 2d043f59cf..14e87407f1 100644 --- a/src/requests/models.py +++ b/src/requests/models.py @@ -575,9 +575,13 @@ def prepare_content_length(self, body): """Prepare Content-Length header based on request method and body""" if body is not None: length = super_len(body) - if length: - # If length exists, set it. Otherwise, we fallback - # to Transfer-Encoding: chunked. + if length is not None and "Transfer-Encoding" not in self.headers: + # Set Content-Length for any known length, including 0. + # A length of 0 is valid (e.g. data={'foo': None} encodes + # to an empty body) and must be sent to avoid falling back + # to Transfer-Encoding: chunked, which can cause servers + # to misinterpret the terminating chunk as a new request. + # Skip if Transfer-Encoding is already set (e.g. for streams). self.headers["Content-Length"] = builtin_str(length) elif ( self.method not in ("GET", "HEAD") diff --git a/tests/test_requests.py b/tests/test_requests.py index 257d9d7ab1..12379a9282 100644 --- a/tests/test_requests.py +++ b/tests/test_requests.py @@ -2229,6 +2229,24 @@ def test_chunked_upload_does_not_set_content_length_header(self, httpbin): assert "Transfer-Encoding" in prepared_request.headers assert "Content-Length" not in prepared_request.headers + def test_empty_body_from_none_values_sets_content_length_zero(self, httpbin): + """Ensure that a request body consisting only of None values sets + Content-Length: 0 instead of falling back to Transfer-Encoding: chunked. + + When data={'foo': None}, the body encodes to an empty string. + Without Content-Length: 0, the adapter falls back to chunked encoding + and sends a terminating chunk ('0\\r\\n\\r\\n') that servers may + misinterpret as a second, malformed request. + + See: https://github.com/psf/requests/issues/6122 + """ + url = httpbin("post") + r = requests.Request("POST", url, data={"foo": None}) + prepared_request = r.prepare() + assert prepared_request.body == "" + assert prepared_request.headers["Content-Length"] == "0" + assert "Transfer-Encoding" not in prepared_request.headers + def test_custom_redirect_mixin(self, httpbin): """Tests a custom mixin to overwrite ``get_redirect_target``.