Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGES/11955.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added ``max_headers`` parameter to limit the number of headers that should be read from a response -- by :user:`Dreamsorcerer`.
31 changes: 18 additions & 13 deletions aiohttp/_http_parser.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ cdef class HttpParser:
object _name
bytes _raw_value
bint _has_value
int _header_name_size

object _protocol
object _loop
Expand Down Expand Up @@ -329,7 +330,7 @@ cdef class HttpParser:
self, cparser.llhttp_type mode,
object protocol, object loop, int limit,
object timer=None,
size_t max_line_size=8190, size_t max_headers=32768,
size_t max_line_size=8190, size_t max_headers=128,
size_t max_field_size=8190, payload_exception=None,
bint response_with_body=True, bint read_until_eof=False,
bint auto_decompress=True,
Expand All @@ -352,6 +353,7 @@ cdef class HttpParser:
self._raw_name = EMPTY_BYTES
self._raw_value = EMPTY_BYTES
self._has_value = False
self._header_name_size = 0

self._max_line_size = max_line_size
self._max_headers = max_headers
Expand Down Expand Up @@ -383,11 +385,14 @@ cdef class HttpParser:
value = self._raw_value.decode('utf-8', 'surrogateescape')

self._headers.append((name, value))
if len(self._headers) > self._max_headers:
raise BadHttpMessage("Too many headers received")

if name is CONTENT_ENCODING:
self._content_encoding = value

self._has_value = False
self._header_name_size = 0
self._raw_headers.append((self._raw_name, self._raw_value))
self._raw_name = EMPTY_BYTES
self._raw_value = EMPTY_BYTES
Expand Down Expand Up @@ -574,7 +579,7 @@ cdef class HttpRequestParser(HttpParser):

def __init__(
self, protocol, loop, int limit, timer=None,
size_t max_line_size=8190, size_t max_headers=32768,
size_t max_line_size=8190, size_t max_headers=128,
size_t max_field_size=8190, payload_exception=None,
bint response_with_body=True, bint read_until_eof=False,
bint auto_decompress=True,
Expand Down Expand Up @@ -638,7 +643,7 @@ cdef class HttpResponseParser(HttpParser):

def __init__(
self, protocol, loop, int limit, timer=None,
size_t max_line_size=8190, size_t max_headers=32768,
size_t max_line_size=8190, size_t max_headers=128,
size_t max_field_size=8190, payload_exception=None,
bint response_with_body=True, bint read_until_eof=False,
bint auto_decompress=True
Expand Down Expand Up @@ -677,8 +682,8 @@ cdef int cb_on_url(cparser.llhttp_t* parser,
cdef HttpParser pyparser = <HttpParser>parser.data
try:
if length > pyparser._max_line_size:
raise LineTooLong(
'Status line is too long', pyparser._max_line_size, length)
status = pyparser._buf + at[:length]
raise LineTooLong(status[:100] + b"...", pyparser._max_line_size)
extend(pyparser._buf, at, length)
except BaseException as ex:
pyparser._last_error = ex
Expand All @@ -690,11 +695,10 @@ cdef int cb_on_url(cparser.llhttp_t* parser,
cdef int cb_on_status(cparser.llhttp_t* parser,
const char *at, size_t length) except -1:
cdef HttpParser pyparser = <HttpParser>parser.data
cdef str reason
try:
if length > pyparser._max_line_size:
raise LineTooLong(
'Status line is too long', pyparser._max_line_size, length)
reason = pyparser._buf + at[:length]
raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size)
extend(pyparser._buf, at, length)
except BaseException as ex:
pyparser._last_error = ex
Expand All @@ -711,8 +715,9 @@ cdef int cb_on_header_field(cparser.llhttp_t* parser,
pyparser._on_status_complete()
size = len(pyparser._raw_name) + length
if size > pyparser._max_field_size:
raise LineTooLong(
'Header name is too long', pyparser._max_field_size, size)
name = pyparser._raw_name + at[:length]
raise LineTooLong(name[:100] + b"...", pyparser._max_field_size)
pyparser._header_name_size = size
pyparser._on_header_field(at, length)
except BaseException as ex:
pyparser._last_error = ex
Expand All @@ -727,9 +732,9 @@ cdef int cb_on_header_value(cparser.llhttp_t* parser,
cdef Py_ssize_t size
try:
size = len(pyparser._raw_value) + length
if size > pyparser._max_field_size:
raise LineTooLong(
'Header value is too long', pyparser._max_field_size, size)
if pyparser._header_name_size + size > pyparser._max_field_size:
value = pyparser._raw_value + at[:length]
raise LineTooLong(value[:100] + b"...", pyparser._max_field_size)
pyparser._on_header_value(at, length)
except BaseException as ex:
pyparser._last_error = ex
Expand Down
9 changes: 9 additions & 0 deletions aiohttp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class _RequestOptions(TypedDict, total=False):
auto_decompress: bool | None
max_line_size: int | None
max_field_size: int | None
max_headers: int | None
middlewares: Sequence[ClientMiddlewareType] | None


Expand Down Expand Up @@ -283,6 +284,7 @@ class ClientSession:
"_read_bufsize",
"_max_line_size",
"_max_field_size",
"_max_headers",
"_resolve_charset",
"_default_proxy",
"_default_proxy_auth",
Expand Down Expand Up @@ -317,6 +319,7 @@ def __init__(
read_bufsize: int = 2**16,
max_line_size: int = 8190,
max_field_size: int = 8190,
max_headers: int = 128,
fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8",
middlewares: Sequence[ClientMiddlewareType] = (),
ssl_shutdown_timeout: _SENTINEL | None | float = sentinel,
Expand Down Expand Up @@ -386,6 +389,7 @@ def __init__(
self._read_bufsize = read_bufsize
self._max_line_size = max_line_size
self._max_field_size = max_field_size
self._max_headers = max_headers

# Convert to list of tuples
if headers:
Expand Down Expand Up @@ -485,6 +489,7 @@ async def _request(
auto_decompress: bool | None = None,
max_line_size: int | None = None,
max_field_size: int | None = None,
max_headers: int | None = None,
middlewares: Sequence[ClientMiddlewareType] | None = None,
) -> ClientResponse:
# NOTE: timeout clamps existing connect and read timeouts. We cannot
Expand Down Expand Up @@ -571,6 +576,9 @@ async def _request(
if max_field_size is None:
max_field_size = self._max_field_size

if max_headers is None:
max_headers = self._max_headers

traces = [
Trace(
self,
Expand Down Expand Up @@ -710,6 +718,7 @@ async def _connect_and_send_request(
timeout_ceil_threshold=self._connector._timeout_ceil_threshold,
max_line_size=max_line_size,
max_field_size=max_field_size,
max_headers=max_headers,
)
try:
resp = await req._send(conn)
Expand Down
2 changes: 2 additions & 0 deletions aiohttp/client_proto.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ def set_response_params(
timeout_ceil_threshold: float = 5,
max_line_size: int = 8190,
max_field_size: int = 8190,
max_headers: int = 128,
) -> None:
self._skip_payload = skip_payload

Expand All @@ -248,6 +249,7 @@ def set_response_params(
auto_decompress=auto_decompress,
max_line_size=max_line_size,
max_field_size=max_field_size,
max_headers=max_headers,
)

if self._tail:
Expand Down
10 changes: 3 additions & 7 deletions aiohttp/http_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,9 @@ class DecompressSizeError(PayloadEncodingError):


class LineTooLong(BadHttpMessage):
def __init__(
self, line: str, limit: str = "Unknown", actual_size: str = "Unknown"
) -> None:
super().__init__(
f"Got more than {limit} bytes ({actual_size}) when reading {line}."
)
self.args = (line, limit, actual_size)
def __init__(self, line: bytes, limit: int) -> None:
super().__init__(f"Got more than {limit} bytes when reading: {line!r}.")
self.args = (line, limit)


class InvalidHeader(BadHttpMessage):
Expand Down
Loading
Loading