diff -r 62e88e5a61c0 Lib/zipfile.py --- a/Lib/zipfile.py Wed Jan 06 21:31:09 2016 -0800 +++ b/Lib/zipfile.py Thu Jan 07 14:48:02 2016 +0000 @@ -1423,6 +1423,105 @@ raise LargeZipFile(requires_zip64 + " would require ZIP64 extensions") + def writefile(self, file_obj, zinfo: ZipInfo, force_zip64=False): + """Write data from a file like object into the zip file. + + This is a lower level method than ZipFile.write(), offering more + precise control. + + If you know the length of the data to be read, set zinfo.file_size before + calling this. If you don't know the length and it could exceed 2 GiB, + pass force_zip64=True to write with the ZIP64 format, which can handle + large files. The correct size will be written after the file has been read. + """ + if not self.fp: + raise RuntimeError( + "Attempt to write to ZIP archive that was already closed") + if force_zip64 and not self._allowZip64: + raise ValueError( + "force_zip64 is True, but self._allowZip64 is False" + ) + + if zinfo.compress_type is None: + zinfo.compress_type = self.compression + + # Sizes and CRC are overwritten with correct data after processing the file + if not hasattr(zinfo, 'file_size'): + zinfo.file_size = 0 + zinfo.compress_size = compress_size = 0 + zinfo.CRC = CRC = 0 + + zinfo.flag_bits = 0x00 + if zinfo.compress_type == ZIP_LZMA: + # Compressed data includes an end-of-stream (EOS) marker + zinfo.flag_bits |= 0x02 + if not self._seekable: + zinfo.flag_bits |= 0x08 + + with self._lock: + if self._seekable: + self.fp.seek(self.start_dir) + zinfo.header_offset = self.fp.tell() # Start of header bytes + + self._writecheck(zinfo) + self._didModify = True + + cmpr = _get_compressor(zinfo.compress_type) + + # Compressed size can be larger than uncompressed size + zip64 = self._allowZip64 and \ + (force_zip64 or zinfo.file_size * 1.05 > ZIP64_LIMIT) + + self.fp.write(zinfo.FileHeader(zip64)) + + # Write the file data itself + file_size = 0 + while True: + buf = file_obj.read(1024 * 8) + if not buf: + break + file_size = file_size + len(buf) + CRC = crc32(buf, CRC) + if cmpr: + buf = cmpr.compress(buf) + compress_size = compress_size + len(buf) + self.fp.write(buf) + + # Flush any data from the compressor, and update header info + if cmpr: + buf = cmpr.flush() + compress_size = compress_size + len(buf) + self.fp.write(buf) + zinfo.compress_size = compress_size + else: + zinfo.compress_size = file_size + zinfo.CRC = CRC + zinfo.file_size = file_size + + # Write updated header info + if zinfo.flag_bits & 0x08: + # Write CRC and file sizes after the file data + fmt = ' ZIP64_LIMIT: + raise RuntimeError('File size unexpectedly exceeded ZIP64 limit') + if compress_size > ZIP64_LIMIT: + raise RuntimeError('Compressed size unexpectedly exceeded ZIP64 limit') + # Seek backwards and write file header (which will now include + # correct CRC and file sizes) + self.start_dir = self.fp.tell() # Preserve current position in file + self.fp.seek(zinfo.header_offset) + self.fp.write(zinfo.FileHeader(zip64)) + self.fp.seek(self.start_dir) + + # Add file to our caches + self.filelist.append(zinfo) + self.NameToInfo[zinfo.filename] = zinfo + def write(self, filename, arcname=None, compress_type=None): """Put the bytes from filename into the archive under the name arcname.""" @@ -1453,18 +1552,19 @@ zinfo.file_size = st.st_size zinfo.flag_bits = 0x00 - with self._lock: - if self._seekable: - self.fp.seek(self.start_dir) - zinfo.header_offset = self.fp.tell() # Start of header bytes - if zinfo.compress_type == ZIP_LZMA: - # Compressed data includes an end-of-stream (EOS) marker - zinfo.flag_bits |= 0x02 - self._writecheck(zinfo) - self._didModify = True + if isdir: + with self._lock: + if self._seekable: + self.fp.seek(self.start_dir) + zinfo.header_offset = self.fp.tell() # Start of header bytes + if zinfo.compress_type == ZIP_LZMA: + # Compressed data includes an end-of-stream (EOS) marker + zinfo.flag_bits |= 0x02 - if isdir: + self._writecheck(zinfo) + self._didModify = True + zinfo.file_size = 0 zinfo.compress_size = 0 zinfo.CRC = 0 @@ -1473,59 +1573,9 @@ self.NameToInfo[zinfo.filename] = zinfo self.fp.write(zinfo.FileHeader(False)) self.start_dir = self.fp.tell() - return - - cmpr = _get_compressor(zinfo.compress_type) - if not self._seekable: - zinfo.flag_bits |= 0x08 - with open(filename, "rb") as fp: - # Must overwrite CRC and sizes with correct data later - zinfo.CRC = CRC = 0 - zinfo.compress_size = compress_size = 0 - # Compressed size can be larger than uncompressed size - zip64 = self._allowZip64 and \ - zinfo.file_size * 1.05 > ZIP64_LIMIT - self.fp.write(zinfo.FileHeader(zip64)) - file_size = 0 - while 1: - buf = fp.read(1024 * 8) - if not buf: - break - file_size = file_size + len(buf) - CRC = crc32(buf, CRC) - if cmpr: - buf = cmpr.compress(buf) - compress_size = compress_size + len(buf) - self.fp.write(buf) - if cmpr: - buf = cmpr.flush() - compress_size = compress_size + len(buf) - self.fp.write(buf) - zinfo.compress_size = compress_size - else: - zinfo.compress_size = file_size - zinfo.CRC = CRC - zinfo.file_size = file_size - if zinfo.flag_bits & 0x08: - # Write CRC and file sizes after the file data - fmt = ' ZIP64_LIMIT: - raise RuntimeError('File size has increased during compressing') - if compress_size > ZIP64_LIMIT: - raise RuntimeError('Compressed size larger than uncompressed size') - # Seek backwards and write file header (which will now include - # correct CRC and file sizes) - self.start_dir = self.fp.tell() # Preserve current position in file - self.fp.seek(zinfo.header_offset) - self.fp.write(zinfo.FileHeader(zip64)) - self.fp.seek(self.start_dir) - self.filelist.append(zinfo) - self.NameToInfo[zinfo.filename] = zinfo + else: + with open(filename, "rb") as fp: + self.writefile(fp, zinfo) def writestr(self, zinfo_or_arcname, data, compress_type=None): """Write a file into the archive. The contents is 'data', which