Skip to content

Comments

Fix empty encoded body sending spurious chunked terminator#7215

Open
danielalanbates wants to merge 1 commit intopsf:mainfrom
danielalanbates:fix/issue-6122
Open

Fix empty encoded body sending spurious chunked terminator#7215
danielalanbates wants to merge 1 commit intopsf:mainfrom
danielalanbates:fix/issue-6122

Conversation

@danielalanbates
Copy link

Summary

When data encodes to an empty string (e.g. data={'foo': None} or data=[('a', None)]), prepare_body previously set body = '' rather than body = None. This caused prepare_content_length to skip setting Content-Length: 0 (since super_len('') returns 0, which is falsy), and the adapter then treated the request as chunked, sending a bare 0\r\n\r\n terminator that servers like nginx and Apache interpreted as a malformed second request — resulting in a 400 Bad Request.

Root cause: In models.py, self._encode_params(data) returned '' (empty string) for data with all-None values. The empty string is truthy enough to bypass the body is not None check in prepare_content_length, but super_len('') returns 0 which is falsy, so no Content-Length header was set. Without Content-Length or Transfer-Encoding, the adapter fell through to chunked encoding and sent b'0\r\n\r\n'.

Fix: Convert an empty encoded body to None (body = self._encode_params(data) or None) so that prepare_content_length correctly handles it:

  • For POST/PUT: sets Content-Length: 0
  • For GET/HEAD: omits Content-Length entirely (per RFC 7230 3.3.2)

This is the minimal fix suggested by @nateprewitt in the original discussion.

Changes

  • src/requests/models.py: One-line change on the _encode_params call
  • tests/test_lowlevel.py: Parametrized test covering both GET (no Content-Length) and POST (Content-Length: 0) with empty form data, verifying no chunked terminator is sent

Test plan

  • New parametrized test test_empty_urlencoded_form_body passes for both GET and POST
  • All existing test_lowlevel.py tests pass (14/14)
  • Verified manually: Request('POST', url, data={'foo': None}).prepare() now has Content-Length: 0 and body is None
  • Verified manually: normal data={'foo': 'bar'} still works correctly

Closes #6122

This PR was created with the assistance of Claude Opus 4.6 by Anthropic. Happy to make any adjustments! Reviewed and submitted by a human.

When `data` encodes to an empty string (e.g. `data={'foo': None}`),
`prepare_body` set `body = ''` rather than `body = None`. This caused
`prepare_content_length` to skip setting `Content-Length: 0` (since
`super_len('')` is 0, which is falsy), and the adapter then treated the
request as chunked, sending a bare `0\r\n\r\n` terminator that servers
like nginx and Apache interpreted as a malformed second request.

The fix converts an empty encoded body to `None` so that
`prepare_content_length` correctly handles it: setting
`Content-Length: 0` for POST/PUT and omitting it for GET/HEAD.

Fixes psf#6122

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Daniel Bates <danielalanbates@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant