.. class:: HTTPConnection(host, port=None[, timeout], source_address=None, \
- blocksize=8192)
+ blocksize=8192, max_response_headers=None)
An :class:`HTTPConnection` instance represents one transaction with an HTTP
server. It should be instantiated by passing it a host and optional port
The optional *source_address* parameter may be a tuple of a (host, port)
to use as the source address the HTTP connection is made from.
The optional *blocksize* parameter sets the buffer size in bytes for
- sending a file-like message body.
+ sending a file-like message body. The optional *max_response_headers*
+ parameter sets the maximum number of allowed response headers to help
+ prevent denial-of-service attacks, otherwise the default value (100) is used.
For example, the following calls all create instances that connect to the server
at the same host and port::
.. versionchanged:: 3.7
*blocksize* parameter was added.
+ .. versionchanged:: next
+ *max_response_headers* parameter was added.
+
.. class:: HTTPSConnection(host, port=None, *[, timeout], \
source_address=None, context=None, \
- blocksize=8192)
+ blocksize=8192, max_response_headers=None)
A subclass of :class:`HTTPConnection` that uses SSL for communication with
secure servers. Default port is ``443``. If *context* is specified, it
The deprecated *key_file*, *cert_file* and *check_hostname* parameters
have been removed.
+ .. versionchanged:: next
+ *max_response_headers* parameter was added.
+
.. class:: HTTPResponse(sock, debuglevel=0, method=None, url=None)
.. versionadded:: 3.7
+.. attribute:: HTTPConnection.max_response_headers
+
+ The maximum number of allowed response headers to help prevent denial-of-service
+ attacks. By default, the maximum number of allowed headers is set to 100.
+
+ .. versionadded:: next
+
+
As an alternative to using the :meth:`~HTTPConnection.request` method described above, you can
also send your request step by step, by using the four functions below.
(Contributed by Jiahao Li in :gh:`134580`.)
+http.client
+-----------
+
+* A new *max_response_headers* keyword-only parameter has been added to
+ :class:`~http.client.HTTPConnection` and :class:`~http.client.HTTPSConnection`
+ constructors. This parameter overrides the default maximum number of allowed
+ response headers.
+ (Contributed by Alexander Enrique Urieles Nieto in :gh:`131724`.)
+
+
math
----
lst.append(line)
return lst
-def _read_headers(fp):
+def _read_headers(fp, max_headers):
"""Reads potential header lines into a list from a file pointer.
Length of line is limited by _MAXLINE, and number of
- headers is limited by _MAXHEADERS.
+ headers is limited by max_headers.
"""
headers = []
+ if max_headers is None:
+ max_headers = _MAXHEADERS
while True:
line = fp.readline(_MAXLINE + 1)
if len(line) > _MAXLINE:
raise LineTooLong("header line")
- headers.append(line)
- if len(headers) > _MAXHEADERS:
- raise HTTPException("got more than %d headers" % _MAXHEADERS)
if line in (b'\r\n', b'\n', b''):
break
+ headers.append(line)
+ if len(headers) > max_headers:
+ raise HTTPException(f"got more than {max_headers} headers")
return headers
def _parse_header_lines(header_lines, _class=HTTPMessage):
hstring = b''.join(header_lines).decode('iso-8859-1')
return email.parser.Parser(_class=_class).parsestr(hstring)
-def parse_headers(fp, _class=HTTPMessage):
+def parse_headers(fp, _class=HTTPMessage, *, _max_headers=None):
"""Parses only RFC2822 headers from a file pointer."""
- headers = _read_headers(fp)
+ headers = _read_headers(fp, _max_headers)
return _parse_header_lines(headers, _class)
raise BadStatusLine(line)
return version, status, reason
- def begin(self):
+ def begin(self, *, _max_headers=None):
if self.headers is not None:
# we've already started reading the response
return
if status != CONTINUE:
break
# skip the header from the 100 response
- skipped_headers = _read_headers(self.fp)
+ skipped_headers = _read_headers(self.fp, _max_headers)
if self.debuglevel > 0:
print("headers:", skipped_headers)
del skipped_headers
else:
raise UnknownProtocol(version)
- self.headers = self.msg = parse_headers(self.fp)
+ self.headers = self.msg = parse_headers(
+ self.fp, _max_headers=_max_headers
+ )
if self.debuglevel > 0:
for hdr, val in self.headers.items():
return None
def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
- source_address=None, blocksize=8192):
+ source_address=None, blocksize=8192, *, max_response_headers=None):
self.timeout = timeout
self.source_address = source_address
self.blocksize = blocksize
self._tunnel_port = None
self._tunnel_headers = {}
self._raw_proxy_headers = None
+ self.max_response_headers = max_response_headers
(self.host, self.port) = self._get_hostport(host, port)
try:
(version, code, message) = response._read_status()
- self._raw_proxy_headers = _read_headers(response.fp)
+ self._raw_proxy_headers = _read_headers(response.fp, self.max_response_headers)
if self.debuglevel > 0:
for header in self._raw_proxy_headers:
try:
try:
- response.begin()
+ if self.max_response_headers is None:
+ response.begin()
+ else:
+ response.begin(_max_headers=self.max_response_headers)
except ConnectionError:
self.close()
raise
def __init__(self, host, port=None,
*, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
- source_address=None, context=None, blocksize=8192):
+ source_address=None, context=None, blocksize=8192,
+ max_response_headers=None):
super(HTTPSConnection, self).__init__(host, port, timeout,
source_address,
- blocksize=blocksize)
+ blocksize=blocksize,
+ max_response_headers=max_response_headers)
if context is None:
context = _create_https_context(self._http_vsn)
self._context = context
self.assertEqual(lines[2], "header: Second: val1")
self.assertEqual(lines[3], "header: Second: val2")
+ def test_max_response_headers(self):
+ max_headers = client._MAXHEADERS + 20
+ headers = [f"Name{i}: Value{i}".encode() for i in range(max_headers)]
+ body = b"HTTP/1.1 200 OK\r\n" + b"\r\n".join(headers)
+
+ with self.subTest(max_headers=None):
+ sock = FakeSocket(body)
+ resp = client.HTTPResponse(sock)
+ with self.assertRaisesRegex(
+ client.HTTPException, f"got more than 100 headers"
+ ):
+ resp.begin()
+
+ with self.subTest(max_headers=max_headers):
+ sock = FakeSocket(body)
+ resp = client.HTTPResponse(sock)
+ resp.begin(_max_headers=max_headers)
+
+ def test_max_connection_headers(self):
+ max_headers = client._MAXHEADERS + 20
+ headers = (
+ f"Name{i}: Value{i}".encode() for i in range(max_headers - 1)
+ )
+ body = (
+ b"HTTP/1.1 200 OK\r\n"
+ + b"\r\n".join(headers)
+ + b"\r\nContent-Length: 12\r\n\r\nDummy body\r\n"
+ )
+
+ with self.subTest(max_headers=None):
+ conn = client.HTTPConnection("example.com")
+ conn.sock = FakeSocket(body)
+ conn.request("GET", "/")
+ with self.assertRaisesRegex(
+ client.HTTPException, f"got more than {client._MAXHEADERS} headers"
+ ):
+ response = conn.getresponse()
+
+ with self.subTest(max_headers=None):
+ conn = client.HTTPConnection(
+ "example.com", max_response_headers=max_headers
+ )
+ conn.sock = FakeSocket(body)
+ conn.request("GET", "/")
+ response = conn.getresponse()
+ response.read()
class HttpMethodTests(TestCase):
def test_invalid_method_names(self):
Utkarsh Upadhyay
Roger Upole
Daniel Urban
+Alexander Enrique Urieles Nieto
Matthias Urlichs
Michael Urman
Hector Urtubia
--- /dev/null
+In :mod:`http.client`, a new *max_response_headers* keyword-only parameter has been
+added to :class:`~http.client.HTTPConnection` and :class:`~http.client.HTTPSConnection`
+constructors. This parameter sets the maximum number of allowed response headers,
+helping to prevent denial-of-service attacks.