Skip to content

Commit a24a88d

Browse files
committed
gh-131724: Add a new max_headers param to HTTP/HTTPSConnection
1 parent 180b3eb commit a24a88d

File tree

2 files changed

+68
-15
lines changed

2 files changed

+68
-15
lines changed

Lib/http/client.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -209,22 +209,24 @@ def getallmatchingheaders(self, name):
209209
lst.append(line)
210210
return lst
211211

212-
def _read_headers(fp):
212+
def _read_headers(fp, max_headers):
213213
"""Reads potential header lines into a list from a file pointer.
214214
215215
Length of line is limited by _MAXLINE, and number of
216-
headers is limited by _MAXHEADERS.
216+
headers is limited by max_headers.
217217
"""
218218
headers = []
219+
if max_headers is None:
220+
max_headers = _MAXHEADERS
219221
while True:
220222
line = fp.readline(_MAXLINE + 1)
221223
if len(line) > _MAXLINE:
222224
raise LineTooLong("header line")
223-
headers.append(line)
224-
if len(headers) > _MAXHEADERS:
225-
raise HTTPException("got more than %d headers" % _MAXHEADERS)
226225
if line in (b'\r\n', b'\n', b''):
227226
break
227+
headers.append(line)
228+
if len(headers) > max_headers:
229+
raise HTTPException("got more than %d headers" % max_headers)
228230
return headers
229231

230232
def _parse_header_lines(header_lines, _class=HTTPMessage):
@@ -241,10 +243,10 @@ def _parse_header_lines(header_lines, _class=HTTPMessage):
241243
hstring = b''.join(header_lines).decode('iso-8859-1')
242244
return email.parser.Parser(_class=_class).parsestr(hstring)
243245

244-
def parse_headers(fp, _class=HTTPMessage):
246+
def parse_headers(fp, _class=HTTPMessage, _max_headers=None):
245247
"""Parses only RFC2822 headers from a file pointer."""
246248

247-
headers = _read_headers(fp)
249+
headers = _read_headers(fp, _max_headers)
248250
return _parse_header_lines(headers, _class)
249251

250252

@@ -320,7 +322,7 @@ def _read_status(self):
320322
raise BadStatusLine(line)
321323
return version, status, reason
322324

323-
def begin(self):
325+
def begin(self, _max_headers=None):
324326
if self.headers is not None:
325327
# we've already started reading the response
326328
return
@@ -331,7 +333,7 @@ def begin(self):
331333
if status != CONTINUE:
332334
break
333335
# skip the header from the 100 response
334-
skipped_headers = _read_headers(self.fp)
336+
skipped_headers = _read_headers(self.fp, _max_headers)
335337
if self.debuglevel > 0:
336338
print("headers:", skipped_headers)
337339
del skipped_headers
@@ -346,7 +348,9 @@ def begin(self):
346348
else:
347349
raise UnknownProtocol(version)
348350

349-
self.headers = self.msg = parse_headers(self.fp)
351+
self.headers = self.msg = parse_headers(
352+
self.fp, _max_headers=_max_headers
353+
)
350354

351355
if self.debuglevel > 0:
352356
for hdr, val in self.headers.items():
@@ -864,7 +868,7 @@ def _get_content_length(body, method):
864868
return None
865869

866870
def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
867-
source_address=None, blocksize=8192):
871+
source_address=None, blocksize=8192, max_headers=None):
868872
self.timeout = timeout
869873
self.source_address = source_address
870874
self.blocksize = blocksize
@@ -877,6 +881,9 @@ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
877881
self._tunnel_port = None
878882
self._tunnel_headers = {}
879883
self._raw_proxy_headers = None
884+
if max_headers is None:
885+
max_headers = _MAXHEADERS
886+
self.max_headers = max_headers
880887

881888
(self.host, self.port) = self._get_hostport(host, port)
882889

@@ -969,7 +976,7 @@ def _tunnel(self):
969976
try:
970977
(version, code, message) = response._read_status()
971978

972-
self._raw_proxy_headers = _read_headers(response.fp)
979+
self._raw_proxy_headers = _read_headers(response.fp, max_headers=self.max_headers)
973980

974981
if self.debuglevel > 0:
975982
for header in self._raw_proxy_headers:
@@ -1426,7 +1433,7 @@ def getresponse(self):
14261433

14271434
try:
14281435
try:
1429-
response.begin()
1436+
response.begin(_max_headers=self.max_headers)
14301437
except ConnectionError:
14311438
self.close()
14321439
raise
@@ -1457,10 +1464,12 @@ class HTTPSConnection(HTTPConnection):
14571464

14581465
def __init__(self, host, port=None,
14591466
*, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
1460-
source_address=None, context=None, blocksize=8192):
1467+
source_address=None, context=None, blocksize=8192,
1468+
max_headers=None):
14611469
super(HTTPSConnection, self).__init__(host, port, timeout,
14621470
source_address,
1463-
blocksize=blocksize)
1471+
blocksize=blocksize,
1472+
max_headers=max_headers)
14641473
if context is None:
14651474
context = _create_https_context(self._http_vsn)
14661475
self._context = context

Lib/test/test_httplib.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,50 @@ def test_headers_debuglevel(self):
386386
self.assertEqual(lines[2], "header: Second: val1")
387387
self.assertEqual(lines[3], "header: Second: val2")
388388

389+
def test_max_response_headers(self):
390+
max_headers = client._MAXHEADERS + 20
391+
headers = [f"Name{i}: Value{i}".encode() for i in range(max_headers)]
392+
body = b"HTTP/1.1 200 OK\r\n" + b"\r\n".join(headers)
393+
394+
with self.subTest(max_headers=None):
395+
sock = FakeSocket(body)
396+
resp = client.HTTPResponse(sock)
397+
with self.assertRaisesRegex(
398+
client.HTTPException, f"got more than 100 headers"
399+
):
400+
resp.begin()
401+
402+
with self.subTest(max_headers=max_headers):
403+
sock = FakeSocket(body)
404+
resp = client.HTTPResponse(sock)
405+
resp.begin(_max_headers=max_headers)
406+
407+
def test_max_connection_headers(self):
408+
max_headers = client._MAXHEADERS + 20
409+
headers = (
410+
f"Name{i}: Value{i}".encode() for i in range(max_headers - 1)
411+
)
412+
body = (
413+
b"HTTP/1.1 200 OK\r\n"
414+
+ b"\r\n".join(headers)
415+
+ b"\r\nContent-Length: 12\r\n\r\nDummy body\r\n"
416+
)
417+
418+
with self.subTest(max_headers=None):
419+
conn = client.HTTPConnection("example.com")
420+
conn.sock = FakeSocket(body)
421+
conn.request("GET", "/")
422+
with self.assertRaisesRegex(
423+
client.HTTPException, f"got more than {client._MAXHEADERS} headers"
424+
):
425+
response = conn.getresponse()
426+
427+
with self.subTest(max_headers=None):
428+
conn = client.HTTPConnection("example.com", max_headers=max_headers)
429+
conn.sock = FakeSocket(body)
430+
conn.request("GET", "/")
431+
response = conn.getresponse()
432+
response.read()
389433

390434
class HttpMethodTests(TestCase):
391435
def test_invalid_method_names(self):

0 commit comments

Comments
 (0)