Skip to content
12 changes: 6 additions & 6 deletions litestar/_multipart.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
ParserLimitReached,
PushMultipartParser,
)

from litestar.utils.compat import async_next
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this being imported here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@provinzkraut I've addressed the feedback with one clarification:

I did not remove the except StopAsyncIteration: block. Removing it caused a runtime failure (NameError: name 'async_next' is not defined) and resulted in a 500 instead of the expected 413. Keeping the explicit exception handling ensures the correct 413 response is returned.

from litestar.datastructures.upload_file import UploadFile
from litestar.exceptions import ClientException
from litestar.status_codes import HTTP_413_REQUEST_ENTITY_TOO_LARGE
from litestar.exceptions.http_exceptions import HTTPException

__all__ = ("parse_content_header", "parse_multipart_form")

from litestar.utils.compat import async_next

if TYPE_CHECKING:
from collections.abc import AsyncGenerator

Expand Down Expand Up @@ -133,8 +133,8 @@ async def parse_multipart_form( # noqa: C901
await data.close()
await _close_upload_files(fields)

# FIXME (3.0): This should raise a '413 - Request Entity Too Large', but for
# backwards compatibility, we keep it as a 400 for now
raise ClientException("Request Entity Too Large") from None
raise HTTPException(
status_code=HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="Multipart form size limit exceeded")

return {k: v if len(v) > 1 else v[0] for k, v in fields.items()}
27 changes: 24 additions & 3 deletions tests/unit/test_data_extractors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
from litestar import Request
from litestar.connection.base import empty_receive
from litestar.data_extractors import ConnectionDataExtractor, ResponseDataExtractor
from litestar.datastructures import Cookie
from litestar.datastructures import Cookie, UploadFile
from litestar.enums import RequestEncodingType
from litestar.response.base import ASGIResponse
from litestar.status_codes import HTTP_200_OK
from litestar.testing import RequestFactory
from litestar.status_codes import HTTP_200_OK, HTTP_413_REQUEST_ENTITY_TOO_LARGE
from litestar.testing import RequestFactory, create_test_client
from litestar import post
from litestar.params import Body

factory = RequestFactory()

Expand Down Expand Up @@ -125,3 +127,22 @@ async def test_skip_parse_malformed_body_false_raises(mocker: MockFixture) -> No

with pytest.raises(ValueError):
await extractor.extract(req, {"body"})

async def test_multipart_exceeds_part_limit_returns_413() -> None:
@post("/upload")
async def upload_handler(
data: UploadFile = Body(media_type=RequestEncodingType.MULTI_PART)
) -> dict:
return {"filename": data.filename}

with create_test_client(route_handlers=[upload_handler], multipart_form_part_limit=2) as client:
response = client.post(
"/upload",
files={
"file1": ("test1.txt", b"content1"),
"file2": ("test2.txt", b"content2"),
"data": ("test3.txt", b"content3"),
},
)

assert response.status_code == HTTP_413_REQUEST_ENTITY_TOO_LARGE
Loading