From 52777baea2533c190f3e4f301ab3a45f2b7cf5a7 Mon Sep 17 00:00:00 2001 From: shindonghwi Date: Fri, 19 Dec 2025 11:40:21 +0900 Subject: [PATCH 1/2] fix: use context manager for ZipFile in extract_zipped_paths Fix resource leak in extract_zipped_paths() where the ZipFile object was never properly closed. The function opened a ZipFile but returned early in multiple code paths without closing it, potentially leaking file descriptors. This change wraps the ZipFile usage in a context manager to ensure proper cleanup regardless of which code path is taken. --- src/requests/utils.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/requests/utils.py b/src/requests/utils.py index 8ab55852cc..2ee0041cf1 100644 --- a/src/requests/utils.py +++ b/src/requests/utils.py @@ -278,18 +278,18 @@ def extract_zipped_paths(path): if not zipfile.is_zipfile(archive): return path - zip_file = zipfile.ZipFile(archive) - if member not in zip_file.namelist(): - return path - - # we have a valid zip archive and a valid member of that archive - tmp = tempfile.gettempdir() - extracted_path = os.path.join(tmp, member.split("/")[-1]) - if not os.path.exists(extracted_path): - # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition - with atomic_open(extracted_path) as file_handler: - file_handler.write(zip_file.read(member)) - return extracted_path + with zipfile.ZipFile(archive) as zip_file: + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + tmp = tempfile.gettempdir() + extracted_path = os.path.join(tmp, member.split("/")[-1]) + if not os.path.exists(extracted_path): + # use read + write to avoid the creating nested folders, we only want the file, avoids mkdir racing condition + with atomic_open(extracted_path) as file_handler: + file_handler.write(zip_file.read(member)) + return extracted_path @contextlib.contextmanager From f621223b2ee467b466b8f06ef26b8930fe844191 Mon Sep 17 00:00:00 2001 From: shindonghwi Date: Fri, 19 Dec 2025 12:14:57 +0900 Subject: [PATCH 2/2] add test for ZipFile resource cleanup --- tests/test_utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index f9a287af1b..dfe6cd6d5d 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -359,6 +359,26 @@ def test_invalid_unc_path(self): path = r"\\localhost\invalid\location" assert extract_zipped_paths(path) == path + def test_zipfile_closed_on_member_not_found(self, tmpdir): + """Ensure ZipFile is properly closed when member is not found.""" + zipped_py = tmpdir.join("test.zip") + with zipfile.ZipFile(zipped_py.strpath, "w") as f: + f.writestr("existing_file.txt", "content") + + nonexistent_path = os.path.join(zipped_py.strpath, "nonexistent_member.txt") + + close_called = [] + original_close = zipfile.ZipFile.close + + def tracking_close(self): + close_called.append(True) + return original_close(self) + + with mock.patch.object(zipfile.ZipFile, "close", tracking_close): + result = extract_zipped_paths(nonexistent_path) + assert result == nonexistent_path + assert close_called, "ZipFile.close() was not called" + class TestContentEncodingDetection: def test_none(self):