aio-libs/aiohttp · error · ValueError
invalid Content-Length
Error message
invalid Content-Length: {length!r} What it means
Raised by BodyPartReader.__init__ when a non-form-data body part carries a Content-Length header whose value is not pure ASCII digits. The library rejects sign prefixes, underscores, whitespace, and non-ASCII digits that Python's int() would otherwise accept, in line with RFC 9110 section 8.6. form-data parts skip this check because per RFC 7578 section 4.8 their Content-Length is ignored.
Solutions
- Ensure every non-form-data body part's Content-Length header is a bare run of ASCII digits with no sign, whitespace, underscores, or thousands separators.
- If you control the producer, omit Content-Length on form-data parts entirely (the reader ignores it for form-data).
- If you must parse lenient/malformed input, sanitize the header before constructing the reader, or pre-validate with str.isascii() and str.isdigit().
Example fix
// before Content-Length: +1024 // after Content-Length: 1024
Defensive patterns
Strategy: validation
Validate before calling
def is_valid_content_length(value: str) -> bool:
return value is not None and value.isascii() and value.isdigit()
# before constructing the reader, validate each non-form-data part header
cl = part_headers.get('Content-Length')
if cl is not None and not is_valid_content_length(cl):
raise HTTPBadRequest(text='Malformed Content-Length') Type guard
import re
_VALID_CONTENT_LENGTH = re.compile(r'\A[0-9]+\Z')
def is_pure_digit_content_length(v: object) -> bool:
return isinstance(v, str) and bool(_VALID_CONTENT_LENGTH.match(v)) Try / catch
try:
part = BodyPartReader(boundary, headers, stream)
except ValueError as e:
# invalid Content-Length header on the body part
return web.Response(status=400, text=f'Malformed part: {e}') Prevention
- Always emit Content-Length as bare ASCII digits with no sign or whitespace.
- Omit Content-Length on form-data parts (the reader ignores it there).
- Validate incoming part headers at the trust boundary before handing them to MultipartReader.
When it happens
Trigger: A multipart body part (subtype != form-data) with a Content-Length header containing a '+'/'-' sign, leading/trailing whitespace, an underscore, or non-ASCII digit characters (e.g. ' 100', '+50', '1_000', fullwidth digits).
Common situations: A custom or buggy multipart producer emitting lenient Content-Length values; proxies/gateways that normalize headers in a way int() tolerates but the spec forbids; hand-crafted multipart bodies in tests.
Related errors
- Invalid Content-Length header
- boundary missed for Content-Type
- boundary %r is too long (70 chars max)
- boundary should contain ASCII only chars
- boundary value contains invalid characters
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/e625148e4230eed6.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/multipart.py:304
default_charset: str | None = None,
max_decompress_size: int = DEFAULT_CHUNK_SIZE,
client_max_size: int = sys.maxsize,
max_size_error_cls: type[Exception] = ValueError,
) -> None:
self.headers = headers
self._boundary = boundary
self._boundary_len = len(boundary) + 2 # Boundary + \r\n
self._content = content
self._default_charset = default_charset
self._at_eof = False
self._is_form_data = subtype == "form-data"
# https://datatracker.ietf.org/doc/html/rfc7578#section-4.8
length = None if self._is_form_data else self.headers.get(CONTENT_LENGTH, None)
if length is not None and not (length.isascii() and length.isdigit()):
# Reject sign prefixes, underscores, whitespace and non-ASCII
# digits that int() would otherwise accept.
# https://www.rfc-editor.org/rfc/rfc9110#section-8.6
raise ValueError(f"invalid Content-Length: {length!r}")
self._length = int(length) if length is not None else None
self._read_bytes = 0
self._unread: deque[bytes] = deque()
self._prev_chunk: bytes | None = None
self._content_eof = 0
self._cache: dict[str, Any] = {}
self._max_decompress_size = max_decompress_size
self._client_max_size = client_max_size
self._max_size_error_cls = max_size_error_cls
def __aiter__(self) -> Self:
return self
async def __anext__(self) -> bytes:
part = await self.next()
if part is None:
raise StopAsyncIteration
return partView on GitHub (pinned to d041d4d0fd)