BodyPartReader.read_chunk() crashes with AssertionError on a body part with Content-Length: 0 · Issue #13758 · aio-libs/aiohttp · GitHub
Skip to content

BodyPartReader.read_chunk() crashes with AssertionError on a body part with Content-Length: 0 #13758

Closed

Description

@ishan-1010

Describe the bug

BodyPartReader.read_chunk(size) picks its read strategy by testing self._length for truthiness:

if self._length:
    fresh = await self._read_chunk_from_length(want)
else:
    fresh = await self._read_chunk_from_stream(want)

self._length is int | None. A body part with an explicit Content-Length: 0 header (legal outside multipart/form-data, e.g. multipart/mixed or multipart/related, where the empty-part special case that nulls out _length does not apply) sets self._length = 0, which is falsy. The reader then falls through to _read_chunk_from_stream, which asserts the requested chunk size is at least the boundary length:

async def _read_chunk_from_stream(self, size: int) -> bytes:
    assert (
        size >= self._boundary_len
    ), "Chunk size must be greater or equal than boundary length + 2"

Any caller asking read_chunk() for a chunk smaller than the boundary length hits that assert instead of getting back an immediate empty chunk, even though Content-Length: 0 already says there's nothing to read.

To Reproduce

import asyncio
from aiohttp import multipart
from aiohttp.streams import StreamReader
from aiohttp.base_protocol import BaseProtocol


async def main():
    boundary = b"boundary42"
    body = (
        b"--" + boundary + b"\r\n"
        b"Content-Type: text/plain\r\n"
        b"Content-Length: 0\r\n"
        b"\r\n"
        b"\r\n"
        b"--" + boundary + b"--\r\n"
    )

    loop = asyncio.get_event_loop()
    protocol = BaseProtocol(loop=loop)
    stream = StreamReader(protocol, 2**16, loop=loop)
    stream.feed_data(body)
    stream.feed_eof()

    reader = multipart.MultipartReader(
        {"Content-Type": f'multipart/mixed; boundary="{boundary.decode()}"'},
        stream,
    )
    part = await reader.next()
    await part.read_chunk(4)  # any size smaller than len(boundary) + 2


asyncio.run(main())
AssertionError: Chunk size must be greater or equal than boundary length + 2

Expected behavior

read_chunk() on a part with Content-Length: 0 returns b"" immediately regardless of the requested size, the same as it does for a nonzero Content-Length.

Logs/tracebacks

Traceback (most recent call last):
  File "repro.py", line 30, in <module>
    asyncio.run(main())
  File "repro.py", line 27, in main
    await part.read_chunk(4)
  File ".../aiohttp/multipart.py", line 392, in read_chunk
    fresh = await self._read_chunk_from_stream(want)
  File ".../aiohttp/multipart.py", line 451, in _read_chunk_from_stream
    size >= self._boundary_len
AssertionError: Chunk size must be greater or equal than boundary length + 2

Python Version

$ python --version
Python 3.14.3

aiohttp Version

$ python -m pip show aiohttp
Name: aiohttp
Version: 3.14.3

multidict Version

N/A (not exercised by this bug)

yarl Version

N/A (not exercised by this bug)

OS

macOS (platform independent, pure-Python code path)

Related component

Server, Client

Additional context

Reading through BodyPartReader, every other use of self._length in multipart.py correctly checks is not None (_align_base64_chunk, _read_chunk_from_length's assert). read_chunk's if self._length: is the odd one out.

Code of Conduct

  • I agree to follow the aio-libs Code of Conduct

Activity

  1. added a commit that references this issue on Sep 23, 2026
    56383f7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions