aio-libs/aiohttp · error · ValueError

Reader did not read all the data or it is malformed

Error message

Reader did not read all the data or it is malformed

What it means

Raised at end-of-stream when the body part's trailing CRLF is missing or wrong. After reading exactly _length bytes, BodyPartReader expects the next two bytes read via readline() to equal b'\r\n'; anything else means the declared Content-Length did not match the actual body, or the framing CRLF before the boundary is absent.

Solutions

  1. Verify the producer writes exactly Content-Length bytes followed by '\r\n' before the next boundary.
  2. If Content-Length is uncertain, emit the part without it and rely on boundary framing instead.
  3. Re-encode transfer-encoding (base64/QP) consistently on both length computation and body bytes.
  4. Catch ValueError at the call site of read_chunk()/read() to tolerate truncated streams and log the offending part.

Example fix

// before
--BOUNDARY
Content-Length: 5

hello--BOUNDARY--

// after
--BOUNDARY
Content-Length: 5

hello
--BOUNDARY--
Defensive patterns

Strategy: try-catch

Try / catch

try:
    while not part.at_eof():
        chunk = await part.read_chunk(8192)
except ValueError as e:
    # part body did not match Content-Length or lacked trailing CRLF
    log.warning('Malformed body part %s: %s', part.name, e)
    raise web.HTTPBadRequest(text='Malformed multipart body')

Prevention

When it happens

Trigger: A body part whose Content-Length under- or over-counts the real payload bytes, or a producer that omits the CRLF separating part body from the boundary line. Also triggered when the stream is truncated mid-part.

Common situations: Multipart generators that compute Content-Length from the undecoded size but the reader decodes base64/quoted-printable, or vice versa; truncated responses from a closing proxy; tests with hand-written multipart strings missing the trailing CRLF.

Understand the failure class

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/ec50413820657ea7. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/multipart.py:395

                if self._prev_chunk:
                    over_chunk = self._prev_chunk[:over_chunk_size]
                    self._prev_chunk = self._prev_chunk[len(over_chunk) :]

                if len(over_chunk) != over_chunk_size:
                    over_chunk += await self._content.read(4 - len(over_chunk))

                if not over_chunk:
                    self._at_eof = True

                stripped_chunk += b"".join(over_chunk.split())
                chunk += over_chunk
                remainder = len(stripped_chunk) % 4

        self._read_bytes += len(chunk)
        if self._read_bytes == self._length:
            self._at_eof = True
        if self._at_eof and await self._content.readline() != b"\r\n":
            raise ValueError("Reader did not read all the data or it is malformed")
        return chunk

    async def _read_chunk_from_length(self, size: int) -> bytes:
        # Reads body part content chunk of the specified size.
        # The body part must has Content-Length header with proper value.
        assert self._length is not None, "Content-Length required for chunked read"
        chunk_size = min(size, self._length - self._read_bytes)
        chunk = await self._content.read(chunk_size)
        if self._content.at_eof():
            self._at_eof = True
        return chunk

    async def _read_chunk_from_stream(self, size: int) -> bytes:
        # Reads content chunk of body part with unknown length.
        # The Content-Length header for body part is not necessary.
        assert (
            size >= self._boundary_len
        ), "Chunk size must be greater or equal than boundary length + 2"

View on GitHub (pinned to d041d4d0fd)