From 14d7580bdc74c2203f4eb3cb8d7e1028ba7a549d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 14 Feb 2023 09:54:45 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=90=9B=20Close=20all=20the=20multipar?= =?UTF-8?q?t=20files=20on=20error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- starlette/formparsers.py | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/starlette/formparsers.py b/starlette/formparsers.py index a2baa7a90..02485d45b 100644 --- a/starlette/formparsers.py +++ b/starlette/formparsers.py @@ -142,6 +142,7 @@ def __init__( self._charset = "" self._file_parts_to_write: typing.List[typing.Tuple[MultipartPart, bytes]] = [] self._file_parts_to_finish: typing.List[MultipartPart] = [] + self._files_to_close_on_error: typing.List[SpooledTemporaryFile] = [] def on_part_begin(self) -> None: self._current_part = MultipartPart() @@ -204,6 +205,7 @@ def on_headers_finished(self) -> None: ) filename = _user_safe_decode(options[b"filename"], self._charset) tempfile = SpooledTemporaryFile(max_size=self.max_file_size) + self._files_to_close_on_error.append(tempfile) self._current_part.file = UploadFile( file=tempfile, # type: ignore[arg-type] size=0, @@ -247,21 +249,27 @@ async def parse(self) -> FormData: # Create the parser. parser = multipart.MultipartParser(boundary, callbacks) - # Feed the parser with data from the request. - async for chunk in self.stream: - parser.write(chunk) - # Write file data, it needs to use await with the UploadFile methods that - # call the corresponding file methods *in a threadpool*, otherwise, if - # they were called directly in the callback methods above (regular, - # non-async functions), that would block the event loop in the main thread. - for part, data in self._file_parts_to_write: - assert part.file # for type checkers - await part.file.write(data) - for part in self._file_parts_to_finish: - assert part.file # for type checkers - await part.file.seek(0) - self._file_parts_to_write.clear() - self._file_parts_to_finish.clear() + try: + # Feed the parser with data from the request. + async for chunk in self.stream: + parser.write(chunk) + # Write file data, it needs to use await with the UploadFile methods that + # call the corresponding file methods *in a threadpool*, otherwise, if + # they were called directly in the callback methods above (regular, + # non-async functions), that would block the event loop in the main thread. + for part, data in self._file_parts_to_write: + assert part.file # for type checkers + await part.file.write(data) + for part in self._file_parts_to_finish: + assert part.file # for type checkers + await part.file.seek(0) + self._file_parts_to_write.clear() + self._file_parts_to_finish.clear() + except Exception as e: + # Close all the files if there was an error. + for file in self._files_to_close_on_error: + file.close() + raise e parser.finalize() return FormData(self.items) From a00186010da1ecde604237f159136608d2bd773c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 14 Feb 2023 09:57:56 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- starlette/formparsers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/starlette/formparsers.py b/starlette/formparsers.py index 02485d45b..e87826098 100644 --- a/starlette/formparsers.py +++ b/starlette/formparsers.py @@ -253,10 +253,11 @@ async def parse(self) -> FormData: # Feed the parser with data from the request. async for chunk in self.stream: parser.write(chunk) - # Write file data, it needs to use await with the UploadFile methods that - # call the corresponding file methods *in a threadpool*, otherwise, if - # they were called directly in the callback methods above (regular, - # non-async functions), that would block the event loop in the main thread. + # Write file data, it needs to use await with the UploadFile methods + # that call the corresponding file methods *in a threadpool*, + # otherwise, if they were called directly in the callback methods above + # (regular, non-async functions), that would block the event loop in + # the main thread. for part, data in self._file_parts_to_write: assert part.file # for type checkers await part.file.write(data) From b772203244be46203021083cc72d6f89fc6e4bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 14 Feb 2023 10:01:01 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=E2=99=BB=20Update=20starlette/formparsers.?= =?UTF-8?q?py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marcelo Trylesinski --- starlette/formparsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starlette/formparsers.py b/starlette/formparsers.py index e87826098..aa0183f32 100644 --- a/starlette/formparsers.py +++ b/starlette/formparsers.py @@ -266,7 +266,7 @@ async def parse(self) -> FormData: await part.file.seek(0) self._file_parts_to_write.clear() self._file_parts_to_finish.clear() - except Exception as e: + except MultiPartException as exc: # Close all the files if there was an error. for file in self._files_to_close_on_error: file.close() From 96341d3a98b7cb8cec2204a2a393b983c916342e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 14 Feb 2023 10:01:20 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=E2=99=BB=20Update=20starlette/formparsers.?= =?UTF-8?q?py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marcelo Trylesinski --- starlette/formparsers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starlette/formparsers.py b/starlette/formparsers.py index aa0183f32..8ab5d664c 100644 --- a/starlette/formparsers.py +++ b/starlette/formparsers.py @@ -270,7 +270,7 @@ async def parse(self) -> FormData: # Close all the files if there was an error. for file in self._files_to_close_on_error: file.close() - raise e + raise exc parser.finalize() return FormData(self.items)