]> granicus.if.org Git - python/commitdiff
Issue #16408: Fix file descriptors not being closed in error conditions in the zipfil...
authorAntoine Pitrou <solipsis@pitrou.net>
Sat, 17 Nov 2012 22:52:05 +0000 (23:52 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Sat, 17 Nov 2012 22:52:05 +0000 (23:52 +0100)
Patch by Serhiy Storchaka.

1  2 
Lib/zipfile.py
Misc/NEWS

diff --cc Lib/zipfile.py
index 209dc4a0f30bf1a86d385306f38c4d0696859a23,2da70b5e60d3e0941ea26e6764189fc4650d9ade..68051c8e130b35334c5599d5905a9bbb01e82f7e
@@@ -1113,84 -919,70 +1106,78 @@@ class ZipFile
          else:
              zef_file = io.open(self.filename, 'rb')
  
-         # Make sure we have an info object
-         if isinstance(name, ZipInfo):
-             # 'name' is already an info object
-             zinfo = name
-         else:
-             # Get info object for name
-             try:
+         try:
+             # Make sure we have an info object
+             if isinstance(name, ZipInfo):
+                 # 'name' is already an info object
+                 zinfo = name
+             else:
+                 # Get info object for name
                  zinfo = self.getinfo(name)
-             except KeyError:
-                 if not self._filePassed:
-                     zef_file.close()
-                 raise
-         zef_file.seek(zinfo.header_offset, 0)
-         # Skip the file header:
-         fheader = zef_file.read(sizeFileHeader)
-         if fheader[0:4] != stringFileHeader:
-             raise BadZipFile("Bad magic number for file header")
-         fheader = struct.unpack(structFileHeader, fheader)
-         fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
-         if fheader[_FH_EXTRA_FIELD_LENGTH]:
-             zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
-         if zinfo.flag_bits & 0x20:
-             # Zip 2.7: compressed patched data
-             raise NotImplementedError("compressed patched data (flag bit 5)")
-         if zinfo.flag_bits & 0x40:
-             # strong encryption
-             raise NotImplementedError("strong encryption (flag bit 6)")
-         if zinfo.flag_bits & 0x800:
-             # UTF-8 filename
-             fname_str = fname.decode("utf-8")
-         else:
-             fname_str = fname.decode("cp437")
+             zef_file.seek(zinfo.header_offset, 0)
+             # Skip the file header:
+             fheader = zef_file.read(sizeFileHeader)
+             if fheader[0:4] != stringFileHeader:
+                 raise BadZipFile("Bad magic number for file header")
+             fheader = struct.unpack(structFileHeader, fheader)
+             fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
+             if fheader[_FH_EXTRA_FIELD_LENGTH]:
+                 zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
++            if zinfo.flag_bits & 0x20:
++                # Zip 2.7: compressed patched data
++                raise NotImplementedError("compressed patched data (flag bit 5)")
++
++            if zinfo.flag_bits & 0x40:
++                # strong encryption
++                raise NotImplementedError("strong encryption (flag bit 6)")
 +
-         if fname_str != zinfo.orig_filename:
+             if zinfo.flag_bits & 0x800:
+                 # UTF-8 filename
+                 fname_str = fname.decode("utf-8")
+             else:
+                 fname_str = fname.decode("cp437")
+             if fname_str != zinfo.orig_filename:
+                 raise BadZipFile(
+                     'File name in directory %r and header %r differ.'
+                     % (zinfo.orig_filename, fname))
+             # check for encrypted flag & handle password
+             is_encrypted = zinfo.flag_bits & 0x1
+             zd = None
+             if is_encrypted:
+                 if not pwd:
+                     pwd = self.pwd
+                 if not pwd:
+                     raise RuntimeError("File %s is encrypted, password "
+                                        "required for extraction" % name)
+                 zd = _ZipDecrypter(pwd)
+                 # The first 12 bytes in the cypher stream is an encryption header
+                 #  used to strengthen the algorithm. The first 11 bytes are
+                 #  completely random, while the 12th contains the MSB of the CRC,
+                 #  or the MSB of the file time depending on the header type
+                 #  and is used to check the correctness of the password.
+                 header = zef_file.read(12)
+                 h = list(map(zd, header[0:12]))
+                 if zinfo.flag_bits & 0x8:
+                     # compare against the file type from extended local headers
+                     check_byte = (zinfo._raw_time >> 8) & 0xff
+                 else:
+                     # compare against the CRC otherwise
+                     check_byte = (zinfo.CRC >> 24) & 0xff
+                 if h[11] != check_byte:
+                     raise RuntimeError("Bad password for file", name)
+             return ZipExtFile(zef_file, mode, zinfo, zd,
+                               close_fileobj=not self._filePassed)
+         except:
              if not self._filePassed:
                  zef_file.close()
-             raise BadZipFile(
-                   'File name in directory %r and header %r differ.'
-                   % (zinfo.orig_filename, fname))
-         # check for encrypted flag & handle password
-         is_encrypted = zinfo.flag_bits & 0x1
-         zd = None
-         if is_encrypted:
-             if not pwd:
-                 pwd = self.pwd
-             if not pwd:
-                 if not self._filePassed:
-                     zef_file.close()
-                 raise RuntimeError("File %s is encrypted, "
-                                    "password required for extraction" % name)
-             zd = _ZipDecrypter(pwd)
-             # The first 12 bytes in the cypher stream is an encryption header
-             #  used to strengthen the algorithm. The first 11 bytes are
-             #  completely random, while the 12th contains the MSB of the CRC,
-             #  or the MSB of the file time depending on the header type
-             #  and is used to check the correctness of the password.
-             header = zef_file.read(12)
-             h = list(map(zd, header[0:12]))
-             if zinfo.flag_bits & 0x8:
-                 # compare against the file type from extended local headers
-                 check_byte = (zinfo._raw_time >> 8) & 0xff
-             else:
-                 # compare against the CRC otherwise
-                 check_byte = (zinfo.CRC >> 24) & 0xff
-             if h[11] != check_byte:
-                 if not self._filePassed:
-                     zef_file.close()
-                 raise RuntimeError("Bad password for file", name)
-         return ZipExtFile(zef_file, mode, zinfo, zd,
-                           close_fileobj=not self._filePassed)
+             raise
  
      def extract(self, member, path=None, pwd=None):
          """Extract a member from the archive to the current working directory,
          if self.fp is None:
              return
  
-         if self.mode in ("w", "a") and self._didModify: # write ending records
-             count = 0
-             pos1 = self.fp.tell()
-             for zinfo in self.filelist:         # write central directory
-                 count = count + 1
-                 dt = zinfo.date_time
-                 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
-                 dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
-                 extra = []
-                 if zinfo.file_size > ZIP64_LIMIT \
-                         or zinfo.compress_size > ZIP64_LIMIT:
-                     extra.append(zinfo.file_size)
-                     extra.append(zinfo.compress_size)
-                     file_size = 0xffffffff
-                     compress_size = 0xffffffff
-                 else:
-                     file_size = zinfo.file_size
-                     compress_size = zinfo.compress_size
-                 if zinfo.header_offset > ZIP64_LIMIT:
-                     extra.append(zinfo.header_offset)
-                     header_offset = 0xffffffff
-                 else:
-                     header_offset = zinfo.header_offset
-                 extra_data = zinfo.extra
-                 min_version = 0
-                 if extra:
-                     # Append a ZIP64 field to the extra's
-                     extra_data = struct.pack(
-                             '<HH' + 'Q'*len(extra),
-                             1, 8*len(extra), *extra) + extra_data
-                     min_version = ZIP64_VERSION
-                 if zinfo.compress_type == ZIP_BZIP2:
-                     min_version = max(BZIP2_VERSION, min_version)
-                 elif zinfo.compress_type == ZIP_LZMA:
-                     min_version = max(LZMA_VERSION, min_version)
-                 extract_version = max(min_version, zinfo.extract_version)
-                 create_version = max(min_version, zinfo.create_version)
-                 try:
-                     filename, flag_bits = zinfo._encodeFilenameFlags()
-                     centdir = struct.pack(structCentralDir,
-                         stringCentralDir, create_version,
-                         zinfo.create_system, extract_version, zinfo.reserved,
-                         flag_bits, zinfo.compress_type, dostime, dosdate,
-                         zinfo.CRC, compress_size, file_size,
-                         len(filename), len(extra_data), len(zinfo.comment),
-                         0, zinfo.internal_attr, zinfo.external_attr,
-                         header_offset)
-                 except DeprecationWarning:
-                     print((structCentralDir, stringCentralDir, create_version,
-                         zinfo.create_system, extract_version, zinfo.reserved,
-                         zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
-                         zinfo.CRC, compress_size, file_size,
-                         len(zinfo.filename), len(extra_data), len(zinfo.comment),
-                         0, zinfo.internal_attr, zinfo.external_attr,
-                         header_offset), file=sys.stderr)
-                     raise
-                 self.fp.write(centdir)
-                 self.fp.write(filename)
-                 self.fp.write(extra_data)
-                 self.fp.write(zinfo.comment)
-             pos2 = self.fp.tell()
-             # Write end-of-zip-archive record
-             centDirCount = count
-             centDirSize = pos2 - pos1
-             centDirOffset = pos1
-             if (centDirCount >= ZIP_FILECOUNT_LIMIT or
-                 centDirOffset > ZIP64_LIMIT or
-                 centDirSize > ZIP64_LIMIT):
-                 # Need to write the ZIP64 end-of-archive records
-                 zip64endrec = struct.pack(
-                         structEndArchive64, stringEndArchive64,
-                         44, 45, 45, 0, 0, centDirCount, centDirCount,
-                         centDirSize, centDirOffset)
-                 self.fp.write(zip64endrec)
-                 zip64locrec = struct.pack(
-                         structEndArchive64Locator,
-                         stringEndArchive64Locator, 0, pos2, 1)
-                 self.fp.write(zip64locrec)
-                 centDirCount = min(centDirCount, 0xFFFF)
-                 centDirSize = min(centDirSize, 0xFFFFFFFF)
-                 centDirOffset = min(centDirOffset, 0xFFFFFFFF)
-             endrec = struct.pack(structEndArchive, stringEndArchive,
-                                  0, 0, centDirCount, centDirCount,
-                                  centDirSize, centDirOffset, len(self._comment))
-             self.fp.write(endrec)
-             self.fp.write(self._comment)
-             self.fp.flush()
-         if not self._filePassed:
-             self.fp.close()
-         self.fp = None
+         try:
+             if self.mode in ("w", "a") and self._didModify: # write ending records
+                 count = 0
+                 pos1 = self.fp.tell()
+                 for zinfo in self.filelist:         # write central directory
+                     count = count + 1
+                     dt = zinfo.date_time
+                     dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
+                     dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
+                     extra = []
+                     if zinfo.file_size > ZIP64_LIMIT \
+                             or zinfo.compress_size > ZIP64_LIMIT:
+                         extra.append(zinfo.file_size)
+                         extra.append(zinfo.compress_size)
+                         file_size = 0xffffffff
+                         compress_size = 0xffffffff
+                     else:
+                         file_size = zinfo.file_size
+                         compress_size = zinfo.compress_size
+                     if zinfo.header_offset > ZIP64_LIMIT:
+                         extra.append(zinfo.header_offset)
+                         header_offset = 0xffffffff
+                     else:
+                         header_offset = zinfo.header_offset
+                     extra_data = zinfo.extra
++                    min_version = 0
+                     if extra:
+                         # Append a ZIP64 field to the extra's
+                         extra_data = struct.pack(
+                                 '<HH' + 'Q'*len(extra),
+                                 1, 8*len(extra), *extra) + extra_data
 -                        extract_version = max(45, zinfo.extract_version)
 -                        create_version = max(45, zinfo.create_version)
 -                    else:
 -                        extract_version = zinfo.extract_version
 -                        create_version = zinfo.create_version
++                        min_version = ZIP64_VERSION
++
++                    if zinfo.compress_type == ZIP_BZIP2:
++                        min_version = max(BZIP2_VERSION, min_version)
++                    elif zinfo.compress_type == ZIP_LZMA:
++                        min_version = max(LZMA_VERSION, min_version)
++                    extract_version = max(min_version, zinfo.extract_version)
++                    create_version = max(min_version, zinfo.create_version)
+                     try:
+                         filename, flag_bits = zinfo._encodeFilenameFlags()
+                         centdir = struct.pack(structCentralDir,
+                             stringCentralDir, create_version,
+                             zinfo.create_system, extract_version, zinfo.reserved,
+                             flag_bits, zinfo.compress_type, dostime, dosdate,
+                             zinfo.CRC, compress_size, file_size,
+                             len(filename), len(extra_data), len(zinfo.comment),
+                             0, zinfo.internal_attr, zinfo.external_attr,
+                             header_offset)
+                     except DeprecationWarning:
+                         print((structCentralDir, stringCentralDir, create_version,
+                             zinfo.create_system, extract_version, zinfo.reserved,
+                             zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
+                             zinfo.CRC, compress_size, file_size,
+                             len(zinfo.filename), len(extra_data), len(zinfo.comment),
+                             0, zinfo.internal_attr, zinfo.external_attr,
+                             header_offset), file=sys.stderr)
+                         raise
+                     self.fp.write(centdir)
+                     self.fp.write(filename)
+                     self.fp.write(extra_data)
+                     self.fp.write(zinfo.comment)
+                 pos2 = self.fp.tell()
+                 # Write end-of-zip-archive record
+                 centDirCount = count
+                 centDirSize = pos2 - pos1
+                 centDirOffset = pos1
+                 if (centDirCount >= ZIP_FILECOUNT_LIMIT or
+                     centDirOffset > ZIP64_LIMIT or
+                     centDirSize > ZIP64_LIMIT):
+                     # Need to write the ZIP64 end-of-archive records
+                     zip64endrec = struct.pack(
+                             structEndArchive64, stringEndArchive64,
+                             44, 45, 45, 0, 0, centDirCount, centDirCount,
+                             centDirSize, centDirOffset)
+                     self.fp.write(zip64endrec)
+                     zip64locrec = struct.pack(
+                             structEndArchive64Locator,
+                             stringEndArchive64Locator, 0, pos2, 1)
+                     self.fp.write(zip64locrec)
+                     centDirCount = min(centDirCount, 0xFFFF)
+                     centDirSize = min(centDirSize, 0xFFFFFFFF)
+                     centDirOffset = min(centDirOffset, 0xFFFFFFFF)
+                 endrec = struct.pack(structEndArchive, stringEndArchive,
+                                     0, 0, centDirCount, centDirCount,
+                                     centDirSize, centDirOffset, len(self._comment))
+                 self.fp.write(endrec)
+                 self.fp.write(self._comment)
+                 self.fp.flush()
+         finally:
+             fp = self.fp
+             self.fp = None
+             if not self._filePassed:
+                 fp.close()
  
  
  class PyZipFile(ZipFile):
diff --cc Misc/NEWS
index b68ead6f9fd4d60ac97818ae2426ae938cf17f5f,3b48a132c6fd1b381e643e6102a94202bffae2b1..0cf165eb308a916b95cf1d7d87dee452be16f88c
+++ b/Misc/NEWS
@@@ -88,8 -164,9 +88,11 @@@ Core and Builtin
  Library
  -------
  
+ - Issue #16408: Fix file descriptors not being closed in error conditions
+   in the zipfile module.  Patch by Serhiy Storchaka.
 +- Issue #16481: multiprocessing no longer leaks process handles on Windows.
 +
  - Issue #16140: The subprocess module no longer double closes its child
    subprocess.PIPE parent file descriptors on child error prior to exec().