Skip to content

Commit ce79274

Browse files
graingertvstinner
andauthored
gh-131492, gh-131461: handle exceptions in GzipFile constructor while owning resources (#131462)
Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent f53e7de commit ce79274

File tree

4 files changed

+68
-52
lines changed

4 files changed

+68
-52
lines changed

Lib/gzip.py

Lines changed: 58 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -202,51 +202,58 @@ def __init__(self, filename=None, mode=None,
202202
raise ValueError("Invalid mode: {!r}".format(mode))
203203
if mode and 'b' not in mode:
204204
mode += 'b'
205-
if fileobj is None:
206-
fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
207-
if filename is None:
208-
filename = getattr(fileobj, 'name', '')
209-
if not isinstance(filename, (str, bytes)):
210-
filename = ''
211-
else:
212-
filename = os.fspath(filename)
213-
origmode = mode
214-
if mode is None:
215-
mode = getattr(fileobj, 'mode', 'rb')
216-
217-
218-
if mode.startswith('r'):
219-
self.mode = READ
220-
raw = _GzipReader(fileobj)
221-
self._buffer = io.BufferedReader(raw)
222-
self.name = filename
223-
224-
elif mode.startswith(('w', 'a', 'x')):
225-
if origmode is None:
226-
import warnings
227-
warnings.warn(
228-
"GzipFile was opened for writing, but this will "
229-
"change in future Python releases. "
230-
"Specify the mode argument for opening it for writing.",
231-
FutureWarning, 2)
232-
self.mode = WRITE
233-
self._init_write(filename)
234-
self.compress = zlib.compressobj(compresslevel,
235-
zlib.DEFLATED,
236-
-zlib.MAX_WBITS,
237-
zlib.DEF_MEM_LEVEL,
238-
0)
239-
self._write_mtime = mtime
240-
self._buffer_size = _WRITE_BUFFER_SIZE
241-
self._buffer = io.BufferedWriter(_WriteBufferStream(self),
242-
buffer_size=self._buffer_size)
243-
else:
244-
raise ValueError("Invalid mode: {!r}".format(mode))
245205

246-
self.fileobj = fileobj
206+
try:
207+
if fileobj is None:
208+
fileobj = self.myfileobj = builtins.open(filename, mode or 'rb')
209+
if filename is None:
210+
filename = getattr(fileobj, 'name', '')
211+
if not isinstance(filename, (str, bytes)):
212+
filename = ''
213+
else:
214+
filename = os.fspath(filename)
215+
origmode = mode
216+
if mode is None:
217+
mode = getattr(fileobj, 'mode', 'rb')
218+
219+
220+
if mode.startswith('r'):
221+
self.mode = READ
222+
raw = _GzipReader(fileobj)
223+
self._buffer = io.BufferedReader(raw)
224+
self.name = filename
225+
226+
elif mode.startswith(('w', 'a', 'x')):
227+
if origmode is None:
228+
import warnings
229+
warnings.warn(
230+
"GzipFile was opened for writing, but this will "
231+
"change in future Python releases. "
232+
"Specify the mode argument for opening it for writing.",
233+
FutureWarning, 2)
234+
self.mode = WRITE
235+
self._init_write(filename)
236+
self.compress = zlib.compressobj(compresslevel,
237+
zlib.DEFLATED,
238+
-zlib.MAX_WBITS,
239+
zlib.DEF_MEM_LEVEL,
240+
0)
241+
self._write_mtime = mtime
242+
self._buffer_size = _WRITE_BUFFER_SIZE
243+
self._buffer = io.BufferedWriter(_WriteBufferStream(self),
244+
buffer_size=self._buffer_size)
245+
else:
246+
raise ValueError("Invalid mode: {!r}".format(mode))
247+
248+
self.fileobj = fileobj
247249

248-
if self.mode == WRITE:
249-
self._write_gzip_header(compresslevel)
250+
if self.mode == WRITE:
251+
self._write_gzip_header(compresslevel)
252+
except:
253+
# Avoid a ResourceWarning if the write fails,
254+
# eg read-only file or KeyboardInterrupt
255+
self._close()
256+
raise
250257

251258
@property
252259
def mtime(self):
@@ -387,11 +394,14 @@ def close(self):
387394
elif self.mode == READ:
388395
self._buffer.close()
389396
finally:
390-
self.fileobj = None
391-
myfileobj = self.myfileobj
392-
if myfileobj:
393-
self.myfileobj = None
394-
myfileobj.close()
397+
self._close()
398+
399+
def _close(self):
400+
self.fileobj = None
401+
myfileobj = self.myfileobj
402+
if myfileobj is not None:
403+
self.myfileobj = None
404+
myfileobj.close()
395405

396406
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
397407
self._check_not_closed()

Lib/test/test_tarfile.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from test import support
2020
from test.support import os_helper
2121
from test.support import script_helper
22+
from test.support import warnings_helper
2223

2324
# Check for our compression modules.
2425
try:
@@ -1638,10 +1639,13 @@ def write(self, data):
16381639
raise exctype
16391640

16401641
f = BadFile()
1641-
with self.assertRaises(exctype):
1642-
tar = tarfile.open(tmpname, self.mode, fileobj=f,
1643-
format=tarfile.PAX_FORMAT,
1644-
pax_headers={'non': 'empty'})
1642+
with (
1643+
warnings_helper.check_no_resource_warning(self),
1644+
self.assertRaises(exctype),
1645+
):
1646+
tarfile.open(tmpname, self.mode, fileobj=f,
1647+
format=tarfile.PAX_FORMAT,
1648+
pax_headers={'non': 'empty'})
16451649
self.assertFalse(f.closed)
16461650

16471651
def test_missing_fileobj(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix :exc:`ResourceWarning` when constructing a :class:`gzip.GzipFile` in write mode with a broken file object.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a resource leak when constructing a :class:`gzip.GzipFile` with a filename fails, for example when passing an invalid ``compresslevel``.

0 commit comments

Comments
 (0)