Commit 17babc5e authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #16408: Fix file descriptors not being closed in error conditions in the zipfile module.

Patch by Serhiy Storchaka.
parent a39a22dc
...@@ -719,8 +719,9 @@ class ZipFile: ...@@ -719,8 +719,9 @@ class ZipFile:
self.fp = file self.fp = file
self.filename = getattr(file, 'name', None) self.filename = getattr(file, 'name', None)
try:
if key == 'r': if key == 'r':
self._GetContents() self._RealGetContents()
elif key == 'w': elif key == 'w':
# set the modified flag so central directory gets written # set the modified flag so central directory gets written
# even if no files are added to the archive # even if no files are added to the archive
...@@ -739,10 +740,13 @@ class ZipFile: ...@@ -739,10 +740,13 @@ class ZipFile:
# even if no files are added to the archive # even if no files are added to the archive
self._didModify = True self._didModify = True
else: else:
if not self._filePassed:
self.fp.close()
self.fp = None
raise RuntimeError('Mode must be "r", "w" or "a"') raise RuntimeError('Mode must be "r", "w" or "a"')
except:
fp = self.fp
self.fp = None
if not self._filePassed:
fp.close()
raise
def __enter__(self): def __enter__(self):
return self return self
...@@ -750,17 +754,6 @@ class ZipFile: ...@@ -750,17 +754,6 @@ class ZipFile:
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
self.close() self.close()
def _GetContents(self):
"""Read the directory, making sure we close the file if the format
is bad."""
try:
self._RealGetContents()
except BadZipFile:
if not self._filePassed:
self.fp.close()
self.fp = None
raise
def _RealGetContents(self): def _RealGetContents(self):
"""Read in the table of contents for the ZIP file.""" """Read in the table of contents for the ZIP file."""
fp = self.fp fp = self.fp
...@@ -862,7 +855,7 @@ class ZipFile: ...@@ -862,7 +855,7 @@ class ZipFile:
try: try:
# Read by chunks, to avoid an OverflowError or a # Read by chunks, to avoid an OverflowError or a
# MemoryError with very large embedded files. # MemoryError with very large embedded files.
f = self.open(zinfo.filename, "r") with self.open(zinfo.filename, "r") as f:
while f.read(chunk_size): # Check CRC-32 while f.read(chunk_size): # Check CRC-32
pass pass
except BadZipFile: except BadZipFile:
...@@ -926,18 +919,14 @@ class ZipFile: ...@@ -926,18 +919,14 @@ class ZipFile:
else: else:
zef_file = io.open(self.filename, 'rb') zef_file = io.open(self.filename, 'rb')
try:
# Make sure we have an info object # Make sure we have an info object
if isinstance(name, ZipInfo): if isinstance(name, ZipInfo):
# 'name' is already an info object # 'name' is already an info object
zinfo = name zinfo = name
else: else:
# Get info object for name # Get info object for name
try:
zinfo = self.getinfo(name) zinfo = self.getinfo(name)
except KeyError:
if not self._filePassed:
zef_file.close()
raise
zef_file.seek(zinfo.header_offset, 0) zef_file.seek(zinfo.header_offset, 0)
# Skip the file header: # Skip the file header:
...@@ -957,8 +946,6 @@ class ZipFile: ...@@ -957,8 +946,6 @@ class ZipFile:
fname_str = fname.decode("cp437") fname_str = fname.decode("cp437")
if fname_str != zinfo.orig_filename: if fname_str != zinfo.orig_filename:
if not self._filePassed:
zef_file.close()
raise BadZipFile( raise BadZipFile(
'File name in directory %r and header %r differ.' 'File name in directory %r and header %r differ.'
% (zinfo.orig_filename, fname)) % (zinfo.orig_filename, fname))
...@@ -970,10 +957,8 @@ class ZipFile: ...@@ -970,10 +957,8 @@ class ZipFile:
if not pwd: if not pwd:
pwd = self.pwd pwd = self.pwd
if not pwd: if not pwd:
if not self._filePassed: raise RuntimeError("File %s is encrypted, password "
zef_file.close() "required for extraction" % name)
raise RuntimeError("File %s is encrypted, "
"password required for extraction" % name)
zd = _ZipDecrypter(pwd) zd = _ZipDecrypter(pwd)
# The first 12 bytes in the cypher stream is an encryption header # The first 12 bytes in the cypher stream is an encryption header
...@@ -990,12 +975,14 @@ class ZipFile: ...@@ -990,12 +975,14 @@ class ZipFile:
# compare against the CRC otherwise # compare against the CRC otherwise
check_byte = (zinfo.CRC >> 24) & 0xff check_byte = (zinfo.CRC >> 24) & 0xff
if h[11] != check_byte: if h[11] != check_byte:
if not self._filePassed:
zef_file.close()
raise RuntimeError("Bad password for file", name) raise RuntimeError("Bad password for file", name)
return ZipExtFile(zef_file, mode, zinfo, zd, return ZipExtFile(zef_file, mode, zinfo, zd,
close_fileobj=not self._filePassed) close_fileobj=not self._filePassed)
except:
if not self._filePassed:
zef_file.close()
raise
def extract(self, member, path=None, pwd=None): def extract(self, member, path=None, pwd=None):
"""Extract a member from the archive to the current working directory, """Extract a member from the archive to the current working directory,
...@@ -1052,11 +1039,9 @@ class ZipFile: ...@@ -1052,11 +1039,9 @@ class ZipFile:
os.mkdir(targetpath) os.mkdir(targetpath)
return targetpath return targetpath
source = self.open(member, pwd=pwd) with self.open(member, pwd=pwd) as source, \
target = open(targetpath, "wb") open(targetpath, "wb") as target:
shutil.copyfileobj(source, target) shutil.copyfileobj(source, target)
source.close()
target.close()
return targetpath return targetpath
...@@ -1220,6 +1205,7 @@ class ZipFile: ...@@ -1220,6 +1205,7 @@ class ZipFile:
if self.fp is None: if self.fp is None:
return return
try:
if self.mode in ("w", "a") and self._didModify: # write ending records if self.mode in ("w", "a") and self._didModify: # write ending records
count = 0 count = 0
pos1 = self.fp.tell() pos1 = self.fp.tell()
...@@ -1311,10 +1297,11 @@ class ZipFile: ...@@ -1311,10 +1297,11 @@ class ZipFile:
self.fp.write(endrec) self.fp.write(endrec)
self.fp.write(self._comment) self.fp.write(self._comment)
self.fp.flush() self.fp.flush()
finally:
if not self._filePassed: fp = self.fp
self.fp.close()
self.fp = None self.fp = None
if not self._filePassed:
fp.close()
class PyZipFile(ZipFile): class PyZipFile(ZipFile):
...@@ -1481,15 +1468,14 @@ def main(args = None): ...@@ -1481,15 +1468,14 @@ def main(args = None):
if len(args) != 2: if len(args) != 2:
print(USAGE) print(USAGE)
sys.exit(1) sys.exit(1)
zf = ZipFile(args[1], 'r') with ZipFile(args[1], 'r') as zf:
zf.printdir() zf.printdir()
zf.close()
elif args[0] == '-t': elif args[0] == '-t':
if len(args) != 2: if len(args) != 2:
print(USAGE) print(USAGE)
sys.exit(1) sys.exit(1)
zf = ZipFile(args[1], 'r') with ZipFile(args[1], 'r') as zf:
badfile = zf.testzip() badfile = zf.testzip()
if badfile: if badfile:
print("The following enclosed file is corrupted: {!r}".format(badfile)) print("The following enclosed file is corrupted: {!r}".format(badfile))
...@@ -1500,7 +1486,7 @@ def main(args = None): ...@@ -1500,7 +1486,7 @@ def main(args = None):
print(USAGE) print(USAGE)
sys.exit(1) sys.exit(1)
zf = ZipFile(args[1], 'r') with ZipFile(args[1], 'r') as zf:
out = args[2] out = args[2]
for path in zf.namelist(): for path in zf.namelist():
if path.startswith('./'): if path.startswith('./'):
...@@ -1513,7 +1499,6 @@ def main(args = None): ...@@ -1513,7 +1499,6 @@ def main(args = None):
os.makedirs(tgtdir) os.makedirs(tgtdir)
with open(tgt, 'wb') as fp: with open(tgt, 'wb') as fp:
fp.write(zf.read(path)) fp.write(zf.read(path))
zf.close()
elif args[0] == '-c': elif args[0] == '-c':
if len(args) < 3: if len(args) < 3:
...@@ -1529,11 +1514,9 @@ def main(args = None): ...@@ -1529,11 +1514,9 @@ def main(args = None):
os.path.join(path, nm), os.path.join(zippath, nm)) os.path.join(path, nm), os.path.join(zippath, nm))
# else: ignore # else: ignore
zf = ZipFile(args[1], 'w', allowZip64=True) with ZipFile(args[1], 'w', allowZip64=True) as zf:
for src in args[2:]: for src in args[2:]:
addToZip(zf, src, os.path.basename(src)) addToZip(zf, src, os.path.basename(src))
zf.close()
if __name__ == "__main__": if __name__ == "__main__":
main() main()
...@@ -164,6 +164,9 @@ Core and Builtins ...@@ -164,6 +164,9 @@ Core and Builtins
Library Library
------- -------
- Issue #16408: Fix file descriptors not being closed in error conditions
in the zipfile module. Patch by Serhiy Storchaka.
- Issue #16140: The subprocess module no longer double closes its child - Issue #16140: The subprocess module no longer double closes its child
subprocess.PIPE parent file descriptors on child error prior to exec(). subprocess.PIPE parent file descriptors on child error prior to exec().
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment