Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/requests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
18 changes: 18 additions & 0 deletions tests/test_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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``.

Expand Down