{"record":{"id":"05fa0b24a44f2bf7","repo":"aio-libs/aiohttp","slug":"reading-after-eof","errorCode":null,"errorMessage":"Reading after EOF","messagePattern":"Reading after EOF","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/multipart.py","lineNumber":426,"sourceCode":"    async def _read_chunk_from_stream(self, size: int) -> bytes:\n        # Reads content chunk of body part with unknown length.\n        # The Content-Length header for body part is not necessary.\n        assert (\n            size >= self._boundary_len\n        ), \"Chunk size must be greater or equal than boundary length + 2\"\n        first_chunk = self._prev_chunk is None\n        if first_chunk:\n            # We need to re-add the CRLF that got removed from headers parsing.\n            self._prev_chunk = b\"\\r\\n\" + await self._content.read(size)\n\n        chunk = b\"\"\n        # content.read() may return less than size, so we need to loop to ensure\n        # we have enough data to detect the boundary.\n        while len(chunk) < self._boundary_len:\n            chunk += await self._content.read(size)\n            self._content_eof += int(self._content.at_eof())\n            if self._content_eof > 2:\n                raise ValueError(\"Reading after EOF\")\n            if self._content_eof:\n                break\n        if len(chunk) > size:\n            self._content.unread_data(chunk[size:])\n            chunk = chunk[:size]\n\n        assert self._prev_chunk is not None\n        window = self._prev_chunk + chunk\n        sub = b\"\\r\\n\" + self._boundary\n        if first_chunk:\n            idx = window.find(sub)\n        else:\n            idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub)))\n        if idx >= 0:\n            # pushing boundary back to content\n            with warnings.catch_warnings():\n                warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n                self._content.unread_data(window[idx:])","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/multipart.py#L408-L444","documentation":"Raised by BodyPartReader._read_chunk when the underlying content stream reports at_eof() more than twice while the boundary-search read loop tries to fill a chunk of at least _boundary_len bytes. It indicates the reader kept pulling from an exhausted stream without ever finding the boundary.","triggerScenarios":"A multipart body that ends abruptly without a closing boundary, a stream whose at_eof() flips on and off spuriously, or repeated read_chunk() calls after the part has already exhausted its content.","commonSituations":"Truncated uploads/downloads; a server that closes the connection mid-part; a client calling read_chunk() in a loop past EOF; nested multipart where the inner reader drains into the outer one incorrectly.","solutions":["Check at_eof() on the BodyPartReader before calling read_chunk() again and stop iterating.","Ensure the producer emits a proper closing boundary ('--BOUNDARY--') and that no upstream proxy truncates the body.","Wrap the read loop in try/except ValueError and treat Reading-after-EOF as a fatal framing error, aborting the request.","If consuming nested multipart, release each inner part fully before advancing the outer reader."],"exampleFix":"// before\nwhile True:\n    chunk = await part.read_chunk(8192)\n    ...\n\n// after\nwhile not part.at_eof():\n    chunk = await part.read_chunk(8192)\n    ...\n    if chunk == b'':\n        break","handlingStrategy":"validation","validationCode":"# stop iterating before pulling past EOF\nwhile not part.at_eof():\n    chunk = await part.read_chunk(DEFAULT_CHUNK_SIZE)\n    if chunk == b'':\n        break\n    process(chunk)","typeGuard":"async def safe_read_chunks(part) -> typing.AsyncIterator[bytes]:\n    while not part.at_eof():\n        chunk = await part.read_chunk(DEFAULT_CHUNK_SIZE)\n        if not chunk:\n            break\n        yield chunk","tryCatchPattern":"try:\n    async for chunk in part:\n        ...\nexcept ValueError as e:\n    if 'EOF' in str(e):\n        raise web.HTTPBadRequest(text='Truncated multipart stream')\n    raise","preventionTips":["Gate read loops on at_eof() and break on empty chunks.","Ensure producers emit a closing boundary so the stream ends cleanly.","For nested multipart, fully release inner parts before advancing the outer reader."],"tags":["multipart","stream-integrity","boundary","eof"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}