Skip to content

Commit 9f52b44

Browse files
gh-150751: Ignore optional whitespace around Content-Length in http.client (GH-159156)
gh-150752 started validating the Content-Length value against the RFC 9112 grammar (1*DIGIT), but the compat32 header parser keeps trailing SP/HTAB in the field value, so "Content-Length: 5 " is now treated as a missing header. HTTPResponse then assumes that the connection will close and read() blocks until the server closes it, which never happens on a keep-alive connection. RFC 9112, section 5.1 says that optional whitespace around a field value is excluded by parsers. Strip SP/HTAB before the grammar check; values such as "+5", "5_0" and "5 0" are still rejected.
1 parent 2514548 commit 9f52b44

3 files changed

Lines changed: 26 additions & 1 deletion

File tree

‎Lib/http/client.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,10 @@ def begin(self, *, _max_headers=None):
397397
# NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
398398
self.length = None
399399
length = self.headers.get("content-length")
400+
if length is not None:
401+
# RFC 9112, section 5.1: optional whitespace (SP / HTAB) around
402+
# the field value is not part of the value.
403+
length = length.strip(' \t')
400404
if length and not self.chunked and _is_legal_content_length(length):
401405
self.length = int(length)
402406
else:

‎Lib/test/test_httplib.py‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1351,7 +1351,7 @@ def test_negative_content_length(self):
13511351
def test_malformed_content_length(self):
13521352
# RFC 9112: Content-Length = 1*DIGIT. Values that int() accepts but
13531353
# the grammar forbids must not be used to frame the body.
1354-
for value in ('+5', '5_0'):
1354+
for value in ('+5', '5_0', '5 0'):
13551355
with self.subTest(value=value):
13561356
sock = FakeSocket(
13571357
'HTTP/1.1 200 OK\r\nContent-Length: %s\r\n\r\nHello\r\n' % value)
@@ -1361,6 +1361,22 @@ def test_malformed_content_length(self):
13611361
self.assertEqual(resp.read(), b'Hello\r\n')
13621362
resp.close()
13631363

1364+
def test_content_length_with_whitespace(self):
1365+
# RFC 9112, section 5.1: optional whitespace (SP / HTAB) around
1366+
# the field value is not part of the value, so it must not
1367+
# prevent the Content-Length from being used to frame the body.
1368+
for value in ('7 ', '7\t', '7 \t ', ' 7 '):
1369+
with self.subTest(value=value):
1370+
sock = FakeSocket(
1371+
'HTTP/1.1 200 OK\r\nContent-Length: %s\r\n\r\n'
1372+
'Hello\r\nextra' % value)
1373+
resp = client.HTTPResponse(sock, method="GET")
1374+
resp.begin()
1375+
self.assertEqual(resp.length, 7)
1376+
self.assertEqual(resp.read(), b'Hello\r\n')
1377+
self.assertTrue(resp.isclosed())
1378+
resp.close()
1379+
13641380
def test_malformed_chunk_size(self):
13651381
# RFC 9112: chunk-size = 1*HEXDIG. Reject sizes that int(_, 16) accepts
13661382
# but the grammar forbids (a sign, an "0x" prefix, underscores or
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
:mod:`http.client` now ignores optional whitespace around the
2+
``Content-Length`` header value when validating it, as required by
3+
:rfc:`9112`. Previously a value with trailing whitespace (for example
4+
``Content-Length: 5`` followed by a space) was treated as missing and the
5+
response body was read until the connection was closed.

0 commit comments

Comments
 (0)