aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/zipfile.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/zipfile.py')
-rw-r--r--Lib/zipfile.py395
1 files changed, 232 insertions, 163 deletions
diff --git a/Lib/zipfile.py b/Lib/zipfile.py
index 6e776882539..5b3f6f9603e 100644
--- a/Lib/zipfile.py
+++ b/Lib/zipfile.py
@@ -1,10 +1,19 @@
"""
Read and write ZIP files.
+
+XXX references to utf-8 need further investigation.
"""
-import struct, os, time, sys, shutil
-import binascii, cStringIO, stat
import io
+import os
import re
+import imp
+import sys
+import time
+import stat
+import shutil
+import struct
+import binascii
+
try:
import zlib # We may need its compression method
@@ -13,10 +22,10 @@ except ImportError:
zlib = None
crc32 = binascii.crc32
-__all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile",
- "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile" ]
+__all__ = ["BadZipFile", "BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED",
+ "is_zipfile", "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile"]
-class BadZipfile(Exception):
+class BadZipFile(Exception):
pass
@@ -26,7 +35,8 @@ class LargeZipFile(Exception):
and those extensions are disabled.
"""
-error = BadZipfile # The exception raised by this module
+error = BadZipfile = BadZipFile # Pre-3.2 compatibility names
+
ZIP64_LIMIT = (1 << 31) - 1
ZIP_FILECOUNT_LIMIT = 1 << 16
@@ -45,8 +55,8 @@ ZIP_DEFLATED = 8
# The "end of central directory" structure, magic number, size, and indices
# (section V.I in the format document)
-structEndArchive = "<4s4H2LH"
-stringEndArchive = "PK\005\006"
+structEndArchive = b"<4s4H2LH"
+stringEndArchive = b"PK\005\006"
sizeEndCentDir = struct.calcsize(structEndArchive)
_ECD_SIGNATURE = 0
@@ -65,7 +75,7 @@ _ECD_LOCATION = 9
# The "central directory" structure, magic number, size, and indices
# of entries in the structure (section V.F in the format document)
structCentralDir = "<4s4B4HL2L5H2L"
-stringCentralDir = "PK\001\002"
+stringCentralDir = b"PK\001\002"
sizeCentralDir = struct.calcsize(structCentralDir)
# indexes of entries in the central directory structure
@@ -92,7 +102,7 @@ _CD_LOCAL_HEADER_OFFSET = 18
# The "local file header" structure, magic number, size, and indices
# (section V.A in the format document)
structFileHeader = "<4s2B4HL2L2H"
-stringFileHeader = "PK\003\004"
+stringFileHeader = b"PK\003\004"
sizeFileHeader = struct.calcsize(structFileHeader)
_FH_SIGNATURE = 0
@@ -110,13 +120,13 @@ _FH_EXTRA_FIELD_LENGTH = 11
# The "Zip64 end of central directory locator" structure, magic number, and size
structEndArchive64Locator = "<4sLQL"
-stringEndArchive64Locator = "PK\x06\x07"
+stringEndArchive64Locator = b"PK\x06\x07"
sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
# The "Zip64 end of central directory" record, magic number, size, and indices
# (section V.G in the format document)
structEndArchive64 = "<4sQ2H2L4Q"
-stringEndArchive64 = "PK\x06\x06"
+stringEndArchive64 = b"PK\x06\x06"
sizeEndCentDir64 = struct.calcsize(structEndArchive64)
_CD64_SIGNATURE = 0
@@ -171,7 +181,7 @@ def _EndRecData64(fpin, offset, endrec):
return endrec
if diskno != 0 or disks != 1:
- raise BadZipfile("zipfiles that span multiple disks are not supported")
+ raise BadZipFile("zipfiles that span multiple disks are not supported")
# Assume no 'zip64 extensible data'
fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2)
@@ -211,13 +221,13 @@ def _EndRecData(fpin):
except IOError:
return None
data = fpin.read()
- if data[0:4] == stringEndArchive and data[-2:] == "\000\000":
+ if data[0:4] == stringEndArchive and data[-2:] == b"\000\000":
# the signature is correct and there's no comment, unpack structure
endrec = struct.unpack(structEndArchive, data)
endrec=list(endrec)
# Append a blank comment and record start offset
- endrec.append("")
+ endrec.append(b"")
endrec.append(filesize - sizeEndCentDir)
# Try to read the "Zip64 end of central directory" structure
@@ -296,8 +306,8 @@ class ZipInfo (object):
# Standard values:
self.compress_type = ZIP_STORED # Type of compression for the file
- self.comment = "" # Comment for each file
- self.extra = "" # ZIP extra data
+ self.comment = b"" # Comment for each file
+ self.extra = b"" # ZIP extra data
if sys.platform == 'win32':
self.create_system = 0 # System which created ZIP archive
else:
@@ -351,19 +361,10 @@ class ZipInfo (object):
return header + filename + extra
def _encodeFilenameFlags(self):
- if isinstance(self.filename, unicode):
- try:
- return self.filename.encode('ascii'), self.flag_bits
- except UnicodeEncodeError:
- return self.filename.encode('utf-8'), self.flag_bits | 0x800
- else:
- return self.filename, self.flag_bits
-
- def _decodeFilename(self):
- if self.flag_bits & 0x800:
- return self.filename.decode('utf-8')
- else:
- return self.filename
+ try:
+ return self.filename.encode('ascii'), self.flag_bits
+ except UnicodeEncodeError:
+ return self.filename.encode('utf-8'), self.flag_bits | 0x800
def _decodeExtra(self):
# Try to decode the extra field.
@@ -381,20 +382,20 @@ class ZipInfo (object):
elif ln == 0:
counts = ()
else:
- raise RuntimeError, "Corrupt extra field %s"%(ln,)
+ raise RuntimeError("Corrupt extra field %s"%(ln,))
idx = 0
# ZIP64 extension (large files and/or large archives)
- if self.file_size in (0xffffffffffffffffL, 0xffffffffL):
+ if self.file_size in (0xffffffffffffffff, 0xffffffff):
self.file_size = counts[idx]
idx += 1
- if self.compress_size == 0xFFFFFFFFL:
+ if self.compress_size == 0xFFFFFFFF:
self.compress_size = counts[idx]
idx += 1
- if self.header_offset == 0xffffffffL:
+ if self.header_offset == 0xffffffff:
old = self.header_offset
self.header_offset = counts[idx]
idx+=1
@@ -437,7 +438,7 @@ class _ZipDecrypter:
def _crc32(self, ch, crc):
"""Compute the CRC32 primitive on one byte."""
- return ((crc >> 8) & 0xffffff) ^ self.crctable[(crc ^ ord(ch)) & 0xff]
+ return ((crc >> 8) & 0xffffff) ^ self.crctable[(crc ^ ch) & 0xff]
def __init__(self, pwd):
self.key0 = 305419896
@@ -450,14 +451,13 @@ class _ZipDecrypter:
self.key0 = self._crc32(c, self.key0)
self.key1 = (self.key1 + (self.key0 & 255)) & 4294967295
self.key1 = (self.key1 * 134775813 + 1) & 4294967295
- self.key2 = self._crc32(chr((self.key1 >> 24) & 255), self.key2)
+ self.key2 = self._crc32((self.key1 >> 24) & 255, self.key2)
def __call__(self, c):
"""Decrypt a single character."""
- c = ord(c)
+ assert isinstance(c, int)
k = self.key2 | 2
c = c ^ (((k * (k^1)) >> 8) & 255)
- c = chr(c)
self._UpdateKeys(c)
return c
@@ -495,10 +495,10 @@ class ZipExtFile(io.BufferedIOBase):
MIN_READ_SIZE = 4096
# Search for universal newlines or line chunks.
- PATTERN = re.compile(r'^(?P<chunk>[^\r\n]+)|(?P<newline>\n|\r\n?)')
+ PATTERN = re.compile(br'^(?P<chunk>[^\r\n]+)|(?P<newline>\n|\r\n?)')
def __init__(self, fileobj, mode, zipinfo, decrypter=None,
- close_fileobj=False):
+ close_fileobj=False):
self._fileobj = fileobj
self._decrypter = decrypter
self._close_fileobj = close_fileobj
@@ -515,9 +515,9 @@ class ZipExtFile(io.BufferedIOBase):
raise NotImplementedError("compression type %d (%s)" % (self._compress_type, descr))
else:
raise NotImplementedError("compression type %d" % (self._compress_type,))
- self._unconsumed = ''
+ self._unconsumed = b''
- self._readbuffer = ''
+ self._readbuffer = b''
self._offset = 0
self._universal = 'U' in mode
@@ -545,7 +545,7 @@ class ZipExtFile(io.BufferedIOBase):
if not self._universal and limit < 0:
# Shortcut common case - newline found in buffer.
- i = self._readbuffer.find('\n', self._offset) + 1
+ i = self._readbuffer.find(b'\n', self._offset) + 1
if i > 0:
line = self._readbuffer[self._offset: i]
self._offset = i
@@ -554,10 +554,10 @@ class ZipExtFile(io.BufferedIOBase):
if not self._universal:
return io.BufferedIOBase.readline(self, limit)
- line = ''
+ line = b''
while limit < 0 or len(line) < limit:
readahead = self.peek(2)
- if readahead == '':
+ if readahead == b'':
return line
#
@@ -576,7 +576,7 @@ class ZipExtFile(io.BufferedIOBase):
if newline not in self.newlines:
self.newlines.append(newline)
self._offset += len(newline)
- return line + '\n'
+ return line + b'\n'
chunk = match.group('chunk')
if limit >= 0:
@@ -603,7 +603,7 @@ class ZipExtFile(io.BufferedIOBase):
"""Read and return up to n bytes.
If the argument is omitted, None, or negative, data is read and returned until EOF is reached..
"""
- buf = ''
+ buf = b''
if n is None:
n = -1
while True:
@@ -625,7 +625,7 @@ class ZipExtFile(io.BufferedIOBase):
self._running_crc = crc32(newdata, self._running_crc) & 0xffffffff
# Check the CRC if we're at the end of the file
if eof and self._running_crc != self._expected_crc:
- raise BadZipfile("Bad CRC-32 for file %r" % self.name)
+ raise BadZipFile("Bad CRC-32 for file %r" % self.name)
def read1(self, n):
"""Read up to n bytes with at most one read() system call."""
@@ -647,7 +647,7 @@ class ZipExtFile(io.BufferedIOBase):
self._compress_left -= len(data)
if data and self._decrypter is not None:
- data = ''.join(map(self._decrypter, data))
+ data = bytes(map(self._decrypter, data))
if self._compress_type == ZIP_STORED:
self._update_crc(data, eof=(self._compress_left==0))
@@ -680,14 +680,14 @@ class ZipExtFile(io.BufferedIOBase):
return data
def close(self):
- try :
+ try:
if self._close_fileobj:
self._fileobj.close()
finally:
- super(ZipExtFile, self).close()
+ super().close()
-class ZipFile(object):
+class ZipFile:
""" Class with methods to open, read, write, close, list zip files.
z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=False)
@@ -713,10 +713,10 @@ class ZipFile(object):
pass
elif compression == ZIP_DEFLATED:
if not zlib:
- raise RuntimeError,\
- "Compression requires the (missing) zlib module"
+ raise RuntimeError(
+ "Compression requires the (missing) zlib module")
else:
- raise RuntimeError, "That compression method is not supported"
+ raise RuntimeError("That compression method is not supported")
self._allowZip64 = allowZip64
self._didModify = False
@@ -726,19 +726,20 @@ class ZipFile(object):
self.compression = compression # Method of compression
self.mode = key = mode.replace('b', '')[0]
self.pwd = None
- self._comment = ''
+ self._comment = b''
# Check if we were passed a file-like object
- if isinstance(file, basestring):
+ if isinstance(file, str):
+ # No, it's a filename
self._filePassed = 0
self.filename = file
modeDict = {'r' : 'rb', 'w': 'wb', 'a' : 'r+b'}
try:
- self.fp = open(file, modeDict[mode])
+ self.fp = io.open(file, modeDict[mode])
except IOError:
if mode == 'a':
mode = key = 'w'
- self.fp = open(file, modeDict[mode])
+ self.fp = io.open(file, modeDict[mode])
else:
raise
else:
@@ -759,7 +760,7 @@ class ZipFile(object):
self._RealGetContents()
# seek to start of directory and overwrite
self.fp.seek(self.start_dir, 0)
- except BadZipfile:
+ except BadZipFile:
# file is not a zip file, just append
self.fp.seek(0, 2)
@@ -787,11 +788,11 @@ class ZipFile(object):
try:
endrec = _EndRecData(fp)
except IOError:
- raise BadZipfile("File is not a zip file")
+ raise BadZipFile("File is not a zip file")
if not endrec:
- raise BadZipfile, "File is not a zip file"
+ raise BadZipFile("File is not a zip file")
if self.debug > 1:
- print endrec
+ print(endrec)
size_cd = endrec[_ECD_SIZE] # bytes in central directory
offset_cd = endrec[_ECD_OFFSET] # offset of central directory
self._comment = endrec[_ECD_COMMENT] # archive comment
@@ -804,21 +805,28 @@ class ZipFile(object):
if self.debug > 2:
inferred = concat + offset_cd
- print "given, inferred, offset", offset_cd, inferred, concat
+ print("given, inferred, offset", offset_cd, inferred, concat)
# self.start_dir: Position of start of central directory
self.start_dir = offset_cd + concat
fp.seek(self.start_dir, 0)
data = fp.read(size_cd)
- fp = cStringIO.StringIO(data)
+ fp = io.BytesIO(data)
total = 0
while total < size_cd:
centdir = fp.read(sizeCentralDir)
if centdir[0:4] != stringCentralDir:
- raise BadZipfile, "Bad magic number for central directory"
+ raise BadZipFile("Bad magic number for central directory")
centdir = struct.unpack(structCentralDir, centdir)
if self.debug > 2:
- print centdir
+ print(centdir)
filename = fp.read(centdir[_CD_FILENAME_LENGTH])
+ flags = centdir[5]
+ if flags & 0x800:
+ # UTF-8 file names extension
+ filename = filename.decode('utf-8')
+ else:
+ # Historical ZIP filename encoding
+ filename = filename.decode('cp437')
# Create ZipInfo instance to store file information
x = ZipInfo(filename)
x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
@@ -835,7 +843,6 @@ class ZipFile(object):
x._decodeExtra()
x.header_offset = x.header_offset + concat
- x.filename = x._decodeFilename()
self.filelist.append(x)
self.NameToInfo[x.filename] = x
@@ -845,7 +852,7 @@ class ZipFile(object):
+ centdir[_CD_COMMENT_LENGTH])
if self.debug > 2:
- print "total", total
+ print("total", total)
def namelist(self):
@@ -860,12 +867,14 @@ class ZipFile(object):
archive."""
return self.filelist
- def printdir(self):
+ def printdir(self, file=None):
"""Print a table of contents for the zip file."""
- print "%-46s %19s %12s" % ("File Name", "Modified ", "Size")
+ print("%-46s %19s %12s" % ("File Name", "Modified ", "Size"),
+ file=file)
for zinfo in self.filelist:
date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
- print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
+ print("%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size),
+ file=file)
def testzip(self):
"""Read all the files and check the CRC."""
@@ -877,7 +886,7 @@ class ZipFile(object):
with self.open(zinfo.filename, "r") as f:
while f.read(chunk_size): # Check CRC-32
pass
- except BadZipfile:
+ except BadZipFile:
return zinfo.filename
def getinfo(self, name):
@@ -891,7 +900,12 @@ class ZipFile(object):
def setpassword(self, pwd):
"""Set default password for encrypted files."""
- self.pwd = pwd
+ if pwd and not isinstance(pwd, bytes):
+ raise TypeError("pwd: expected bytes, got %s" % type(pwd))
+ if pwd:
+ self.pwd = pwd
+ else:
+ self.pwd = None
@property
def comment(self):
@@ -900,6 +914,8 @@ class ZipFile(object):
@comment.setter
def comment(self, comment):
+ if not isinstance(comment, bytes):
+ raise TypeError("comment: expected bytes, got %s" % type(comment))
# check for valid comment length
if len(comment) >= ZIP_MAX_COMMENT:
if self.debug:
@@ -911,24 +927,25 @@ class ZipFile(object):
def read(self, name, pwd=None):
"""Return file bytes (as a string) for name."""
- return self.open(name, "r", pwd).read()
+ with self.open(name, "r", pwd) as fp:
+ return fp.read()
def open(self, name, mode="r", pwd=None):
"""Return file-like object for 'name'."""
if mode not in ("r", "U", "rU"):
- raise RuntimeError, 'open() requires mode "r", "U", or "rU"'
+ raise RuntimeError('open() requires mode "r", "U", or "rU"')
+ if pwd and not isinstance(pwd, bytes):
+ raise TypeError("pwd: expected bytes, got %s" % type(pwd))
if not self.fp:
- raise RuntimeError, \
- "Attempt to read ZIP archive that was already closed"
+ raise RuntimeError(
+ "Attempt to read ZIP archive that was already closed")
# Only open a new file for instances where we were not
# given a file object in the constructor
if self._filePassed:
zef_file = self.fp
- should_close = False
else:
- zef_file = open(self.filename, 'rb')
- should_close = True
+ zef_file = io.open(self.filename, 'rb')
try:
# Make sure we have an info object
@@ -938,23 +955,28 @@ class ZipFile(object):
else:
# Get info object for name
zinfo = self.getinfo(name)
-
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"
+ 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 fname != zinfo.orig_filename:
- raise BadZipfile, \
- 'File name in directory "%s" and header "%s" differ.' % (
- zinfo.orig_filename, fname)
+ 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
@@ -963,8 +985,8 @@ class ZipFile(object):
if not pwd:
pwd = self.pwd
if not pwd:
- raise RuntimeError, "File %s is encrypted, " \
- "password required for extraction" % name
+ 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
@@ -972,21 +994,21 @@ class ZipFile(object):
# 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.
- bytes = zef_file.read(12)
- h = map(zd, bytes[0:12])
+ 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 ord(h[11]) != check_byte:
+ if h[11] != check_byte:
raise RuntimeError("Bad password for file", name)
return ZipExtFile(zef_file, mode, zinfo, zd,
- close_fileobj=should_close)
+ close_fileobj=not self._filePassed)
except:
- if should_close:
+ if not self._filePassed:
zef_file.close()
raise
@@ -1046,7 +1068,7 @@ class ZipFile(object):
return targetpath
with self.open(member, pwd=pwd) as source, \
- file(targetpath, "wb") as target:
+ open(targetpath, "wb") as target:
shutil.copyfileobj(source, target)
return targetpath
@@ -1055,24 +1077,24 @@ class ZipFile(object):
"""Check for errors before writing a file to the archive."""
if zinfo.filename in self.NameToInfo:
if self.debug: # Warning for duplicate names
- print "Duplicate name:", zinfo.filename
+ print("Duplicate name:", zinfo.filename)
if self.mode not in ("w", "a"):
- raise RuntimeError, 'write() requires mode "w" or "a"'
+ raise RuntimeError('write() requires mode "w" or "a"')
if not self.fp:
- raise RuntimeError, \
- "Attempt to write ZIP archive that was already closed"
+ raise RuntimeError(
+ "Attempt to write ZIP archive that was already closed")
if zinfo.compress_type == ZIP_DEFLATED and not zlib:
- raise RuntimeError, \
- "Compression requires the (missing) zlib module"
+ raise RuntimeError(
+ "Compression requires the (missing) zlib module")
if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
- raise RuntimeError, \
- "That compression method is not supported"
+ raise RuntimeError("That compression method is not supported")
if zinfo.file_size > ZIP64_LIMIT:
if not self._allowZip64:
raise LargeZipFile("Filesize would require ZIP64 extensions")
if zinfo.header_offset > ZIP64_LIMIT:
if not self._allowZip64:
- raise LargeZipFile("Zipfile size would require ZIP64 extensions")
+ raise LargeZipFile(
+ "Zipfile size would require ZIP64 extensions")
def write(self, filename, arcname=None, compress_type=None):
"""Put the bytes from filename into the archive under the name
@@ -1094,7 +1116,7 @@ class ZipFile(object):
if isdir:
arcname += '/'
zinfo = ZipInfo(arcname, date_time)
- zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes
+ zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes
if compress_type is None:
zinfo.compress_type = self.compression
else:
@@ -1155,16 +1177,19 @@ class ZipFile(object):
self.filelist.append(zinfo)
self.NameToInfo[zinfo.filename] = zinfo
- def writestr(self, zinfo_or_arcname, bytes, compress_type=None):
- """Write a file into the archive. The contents is the string
- 'bytes'. 'zinfo_or_arcname' is either a ZipInfo instance or
+ def writestr(self, zinfo_or_arcname, data, compress_type=None):
+ """Write a file into the archive. The contents is 'data', which
+ may be either a 'str' or a 'bytes' instance; if it is a 'str',
+ it is encoded as UTF-8 first.
+ 'zinfo_or_arcname' is either a ZipInfo instance or
the name of the file in the archive."""
+ if isinstance(data, str):
+ data = data.encode("utf-8")
if not isinstance(zinfo_or_arcname, ZipInfo):
zinfo = ZipInfo(filename=zinfo_or_arcname,
date_time=time.localtime(time.time())[:6])
-
zinfo.compress_type = self.compression
- zinfo.external_attr = 0600 << 16
+ zinfo.external_attr = 0o600 << 16
else:
zinfo = zinfo_or_arcname
@@ -1172,24 +1197,24 @@ class ZipFile(object):
raise RuntimeError(
"Attempt to write to ZIP archive that was already closed")
+ zinfo.file_size = len(data) # Uncompressed size
+ zinfo.header_offset = self.fp.tell() # Start of header data
if compress_type is not None:
zinfo.compress_type = compress_type
- zinfo.file_size = len(bytes) # Uncompressed size
- zinfo.header_offset = self.fp.tell() # Start of header bytes
self._writecheck(zinfo)
self._didModify = True
- zinfo.CRC = crc32(bytes) & 0xffffffff # CRC-32 checksum
+ zinfo.CRC = crc32(data) & 0xffffffff # CRC-32 checksum
if zinfo.compress_type == ZIP_DEFLATED:
co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
zlib.DEFLATED, -15)
- bytes = co.compress(bytes) + co.flush()
- zinfo.compress_size = len(bytes) # Compressed size
+ data = co.compress(data) + co.flush()
+ zinfo.compress_size = len(data) # Compressed size
else:
zinfo.compress_size = zinfo.file_size
- zinfo.header_offset = self.fp.tell() # Start of header bytes
+ zinfo.header_offset = self.fp.tell() # Start of header data
self.fp.write(zinfo.FileHeader())
- self.fp.write(bytes)
+ self.fp.write(data)
self.fp.flush()
if zinfo.flag_bits & 0x08:
# Write CRC and file sizes after the file data
@@ -1230,7 +1255,7 @@ class ZipFile(object):
if zinfo.header_offset > ZIP64_LIMIT:
extra.append(zinfo.header_offset)
- header_offset = 0xffffffffL
+ header_offset = 0xffffffff
else:
header_offset = zinfo.header_offset
@@ -1250,22 +1275,21 @@ class ZipFile(object):
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)
+ 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 >>sys.stderr, (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)
+ 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)
@@ -1311,7 +1335,13 @@ class ZipFile(object):
class PyZipFile(ZipFile):
"""Class to create ZIP archives with Python library files and packages."""
- def writepy(self, pathname, basename = ""):
+ def __init__(self, file, mode="r", compression=ZIP_STORED,
+ allowZip64=False, optimize=-1):
+ ZipFile.__init__(self, file, mode=mode, compression=compression,
+ allowZip64=allowZip64)
+ self._optimize = optimize
+
+ def writepy(self, pathname, basename=""):
"""Add all files from "pathname" to the ZIP archive.
If pathname is a package directory, search the directory and
@@ -1333,10 +1363,10 @@ class PyZipFile(ZipFile):
else:
basename = name
if self.debug:
- print "Adding package in", pathname, "as", basename
+ print("Adding package in", pathname, "as", basename)
fname, arcname = self._get_codename(initname[0:-3], basename)
if self.debug:
- print "Adding", arcname
+ print("Adding", arcname)
self.write(fname, arcname)
dirlist = os.listdir(pathname)
dirlist.remove("__init__.py")
@@ -1352,12 +1382,12 @@ class PyZipFile(ZipFile):
fname, arcname = self._get_codename(path[0:-3],
basename)
if self.debug:
- print "Adding", arcname
+ print("Adding", arcname)
self.write(fname, arcname)
else:
# This is NOT a package directory, add its files at top level
if self.debug:
- print "Adding files from directory", pathname
+ print("Adding files from directory", pathname)
for filename in os.listdir(pathname):
path = os.path.join(pathname, filename)
root, ext = os.path.splitext(filename)
@@ -1365,15 +1395,15 @@ class PyZipFile(ZipFile):
fname, arcname = self._get_codename(path[0:-3],
basename)
if self.debug:
- print "Adding", arcname
+ print("Adding", arcname)
self.write(fname, arcname)
else:
if pathname[-3:] != ".py":
- raise RuntimeError, \
- 'Files added with writepy() must end with ".py"'
+ raise RuntimeError(
+ 'Files added with writepy() must end with ".py"')
fname, arcname = self._get_codename(pathname[0:-3], basename)
if self.debug:
- print "Adding file", arcname
+ print("Adding file", arcname)
self.write(fname, arcname)
def _get_codename(self, pathname, basename):
@@ -1383,25 +1413,64 @@ class PyZipFile(ZipFile):
archive name, compiling if necessary. For example, given
/python/lib/string, return (/python/lib/string.pyc, string).
"""
- file_py = pathname + ".py"
- file_pyc = pathname + ".pyc"
- file_pyo = pathname + ".pyo"
- if os.path.isfile(file_pyo) and \
- os.stat(file_pyo).st_mtime >= os.stat(file_py).st_mtime:
- fname = file_pyo # Use .pyo file
- elif not os.path.isfile(file_pyc) or \
- os.stat(file_pyc).st_mtime < os.stat(file_py).st_mtime:
+ def _compile(file, optimize=-1):
import py_compile
if self.debug:
- print "Compiling", file_py
+ print("Compiling", file)
try:
- py_compile.compile(file_py, file_pyc, None, True)
- except py_compile.PyCompileError,err:
- print err.msg
- fname = file_pyc
+ py_compile.compile(file, doraise=True, optimize=optimize)
+ except py_compile.PyCompileError as error:
+ print(err.msg)
+ return False
+ return True
+
+ file_py = pathname + ".py"
+ file_pyc = pathname + ".pyc"
+ file_pyo = pathname + ".pyo"
+ pycache_pyc = imp.cache_from_source(file_py, True)
+ pycache_pyo = imp.cache_from_source(file_py, False)
+ if self._optimize == -1:
+ # legacy mode: use whatever file is present
+ if (os.path.isfile(file_pyo) and
+ os.stat(file_pyo).st_mtime >= os.stat(file_py).st_mtime):
+ # Use .pyo file.
+ arcname = fname = file_pyo
+ elif (os.path.isfile(file_pyc) and
+ os.stat(file_pyc).st_mtime >= os.stat(file_py).st_mtime):
+ # Use .pyc file.
+ arcname = fname = file_pyc
+ elif (os.path.isfile(pycache_pyc) and
+ os.stat(pycache_pyc).st_mtime >= os.stat(file_py).st_mtime):
+ # Use the __pycache__/*.pyc file, but write it to the legacy pyc
+ # file name in the archive.
+ fname = pycache_pyc
+ arcname = file_pyc
+ elif (os.path.isfile(pycache_pyo) and
+ os.stat(pycache_pyo).st_mtime >= os.stat(file_py).st_mtime):
+ # Use the __pycache__/*.pyo file, but write it to the legacy pyo
+ # file name in the archive.
+ fname = pycache_pyo
+ arcname = file_pyo
+ else:
+ # Compile py into PEP 3147 pyc file.
+ if _compile(file_py):
+ fname = (pycache_pyc if __debug__ else pycache_pyo)
+ arcname = (file_pyc if __debug__ else file_pyo)
+ else:
+ fname = arcname = file_py
else:
- fname = file_pyc
- archivename = os.path.split(fname)[1]
+ # new mode: use given optimization level
+ if self._optimize == 0:
+ fname = pycache_pyc
+ arcname = file_pyc
+ else:
+ fname = pycache_pyo
+ arcname = file_pyo
+ if not (os.path.isfile(fname) and
+ os.stat(fname).st_mtime >= os.stat(file_py).st_mtime):
+ if not _compile(file_py, optimize=self._optimize):
+ fname = arcname = file_py
+ archivename = os.path.split(arcname)[1]
if basename:
archivename = "%s/%s" % (basename, archivename)
return (fname, archivename)
@@ -1420,29 +1489,29 @@ def main(args = None):
args = sys.argv[1:]
if not args or args[0] not in ('-l', '-c', '-e', '-t'):
- print USAGE
+ print(USAGE)
sys.exit(1)
if args[0] == '-l':
if len(args) != 2:
- print USAGE
+ print(USAGE)
sys.exit(1)
with ZipFile(args[1], 'r') as zf:
zf.printdir()
elif args[0] == '-t':
if len(args) != 2:
- print USAGE
+ print(USAGE)
sys.exit(1)
with ZipFile(args[1], 'r') as zf:
badfile = zf.testzip()
if badfile:
print("The following enclosed file is corrupted: {!r}".format(badfile))
- print "Done testing"
+ print("Done testing")
elif args[0] == '-e':
if len(args) != 3:
- print USAGE
+ print(USAGE)
sys.exit(1)
with ZipFile(args[1], 'r') as zf:
@@ -1461,7 +1530,7 @@ def main(args = None):
elif args[0] == '-c':
if len(args) < 3:
- print USAGE
+ print(USAGE)
sys.exit(1)
def addToZip(zf, path, zippath):