https://github.com/python/cpython/commit/0a22407a233bf07cf8c5ade8edb5fc73fce14735 commit: 0a22407a233bf07cf8c5ade8edb5fc73fce14735 branch: 3.13 author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com> committer: vstinner <vstin...@python.org> date: 2025-03-21T11:20:52+01:00 summary:
[3.13] gh-131492, gh-131461: handle exceptions in GzipFile constructor while owning resources (GH-131462) (#131518) (cherry picked from commit ce79274e9f093bd06d2285c9af48dbcbc92173de) Co-authored-by: Thomas Grainger <tagr...@gmail.com> Co-authored-by: Victor Stinner <vstin...@python.org> files: A Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst M Lib/gzip.py M Lib/test/test_tarfile.py diff --git a/Lib/gzip.py b/Lib/gzip.py index 447fa2b0494fdf..a550c20a7a08ba 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -197,51 +197,58 @@ def __init__(self, filename=None, mode=None, raise ValueError("Invalid mode: {!r}".format(mode)) if mode and 'b' not in mode: mode += 'b' - if fileobj is None: - fileobj = self.myfileobj = builtins.open(filename, mode or 'rb') - if filename is None: - filename = getattr(fileobj, 'name', '') - if not isinstance(filename, (str, bytes)): - filename = '' - else: - filename = os.fspath(filename) - origmode = mode - if mode is None: - mode = getattr(fileobj, 'mode', 'rb') - - - if mode.startswith('r'): - self.mode = READ - raw = _GzipReader(fileobj) - self._buffer = io.BufferedReader(raw) - self.name = filename - - elif mode.startswith(('w', 'a', 'x')): - if origmode is None: - import warnings - warnings.warn( - "GzipFile was opened for writing, but this will " - "change in future Python releases. " - "Specify the mode argument for opening it for writing.", - FutureWarning, 2) - self.mode = WRITE - self._init_write(filename) - self.compress = zlib.compressobj(compresslevel, - zlib.DEFLATED, - -zlib.MAX_WBITS, - zlib.DEF_MEM_LEVEL, - 0) - self._write_mtime = mtime - self._buffer_size = _WRITE_BUFFER_SIZE - self._buffer = io.BufferedWriter(_WriteBufferStream(self), - buffer_size=self._buffer_size) - else: - raise ValueError("Invalid mode: {!r}".format(mode)) - self.fileobj = fileobj + try: + if fileobj is None: + fileobj = self.myfileobj = builtins.open(filename, mode or 'rb') + if filename is None: + filename = getattr(fileobj, 'name', '') + if not isinstance(filename, (str, bytes)): + filename = '' + else: + filename = os.fspath(filename) + origmode = mode + if mode is None: + mode = getattr(fileobj, 'mode', 'rb') + + + if mode.startswith('r'): + self.mode = READ + raw = _GzipReader(fileobj) + self._buffer = io.BufferedReader(raw) + self.name = filename + + elif mode.startswith(('w', 'a', 'x')): + if origmode is None: + import warnings + warnings.warn( + "GzipFile was opened for writing, but this will " + "change in future Python releases. " + "Specify the mode argument for opening it for writing.", + FutureWarning, 2) + self.mode = WRITE + self._init_write(filename) + self.compress = zlib.compressobj(compresslevel, + zlib.DEFLATED, + -zlib.MAX_WBITS, + zlib.DEF_MEM_LEVEL, + 0) + self._write_mtime = mtime + self._buffer_size = _WRITE_BUFFER_SIZE + self._buffer = io.BufferedWriter(_WriteBufferStream(self), + buffer_size=self._buffer_size) + else: + raise ValueError("Invalid mode: {!r}".format(mode)) - if self.mode == WRITE: - self._write_gzip_header(compresslevel) + self.fileobj = fileobj + + if self.mode == WRITE: + self._write_gzip_header(compresslevel) + except: + # Avoid a ResourceWarning if the write fails, + # eg read-only file or KeyboardInterrupt + self._close() + raise @property def mtime(self): @@ -370,11 +377,14 @@ def close(self): elif self.mode == READ: self._buffer.close() finally: - self.fileobj = None - myfileobj = self.myfileobj - if myfileobj: - self.myfileobj = None - myfileobj.close() + self._close() + + def _close(self): + self.fileobj = None + myfileobj = self.myfileobj + if myfileobj is not None: + self.myfileobj = None + myfileobj.close() def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH): self._check_not_closed() diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py index 9a540765f8c05e..2e59c1d749c7cb 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1646,10 +1646,13 @@ def write(self, data): raise exctype f = BadFile() - with self.assertRaises(exctype): - tar = tarfile.open(tmpname, self.mode, fileobj=f, - format=tarfile.PAX_FORMAT, - pax_headers={'non': 'empty'}) + with ( + warnings_helper.check_no_resource_warning(self), + self.assertRaises(exctype), + ): + tarfile.open(tmpname, self.mode, fileobj=f, + format=tarfile.PAX_FORMAT, + pax_headers={'non': 'empty'}) self.assertFalse(f.closed) def test_missing_fileobj(self): diff --git a/Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst b/Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst new file mode 100644 index 00000000000000..0f52dec7ce8a83 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-20-08-32-49.gh-issue-131492.saC2cA.rst @@ -0,0 +1 @@ +Fix a resource leak when constructing a :class:`gzip.GzipFile` with a filename fails, for example when passing an invalid ``compresslevel``. _______________________________________________ Python-checkins mailing list -- python-checkins@python.org To unsubscribe send an email to python-checkins-le...@python.org https://mail.python.org/mailman3/lists/python-checkins.python.org/ Member address: arch...@mail-archive.com