5aef6b62 by 周伟奇

first commit

0 parents
1 *.idea
2 .DS_Store
...\ No newline at end of file ...\ No newline at end of file
1 from setuptools import setup
2
3 setup(name='zipfiles',
4 version='1.0',
5 description='modify form python package zipfile',
6 author='clay',
7 author_email='zhouweiqi@situdata.com',
8 packages=['zipfiles'],
9 classifiers=[
10 "Programming Language :: Python :: 3",
11 "Operating System :: OS Independent",
12 ],)
...\ No newline at end of file ...\ No newline at end of file
1 """
2 Read and write ZIP files.
3
4 XXX references to utf-8 need further investigation.
5 """
6 import io
7 import os
8 import re
9 import importlib.util
10 import sys
11 import time
12 import stat
13 import shutil
14 import struct
15 import binascii
16
17 try:
18 import threading
19 except ImportError:
20 import dummy_threading as threading
21
22 try:
23 import zlib # We may need its compression method
24 crc32 = zlib.crc32
25 except ImportError:
26 zlib = None
27 crc32 = binascii.crc32
28
29 try:
30 import bz2 # We may need its compression method
31 except ImportError:
32 bz2 = None
33
34 try:
35 import lzma # We may need its compression method
36 except ImportError:
37 lzma = None
38
39 __all__ = ["BadZipFile", "BadZipfile", "error",
40 "ZIP_STORED", "ZIP_DEFLATED", "ZIP_BZIP2", "ZIP_LZMA",
41 "is_zipfile", "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile"]
42
43 class BadZipFile(Exception):
44 pass
45
46
47 class LargeZipFile(Exception):
48 """
49 Raised when writing a zipfile, the zipfile requires ZIP64 extensions
50 and those extensions are disabled.
51 """
52
53 error = BadZipfile = BadZipFile # Pre-3.2 compatibility names
54
55
56 ZIP64_LIMIT = (1 << 31) - 1
57 ZIP_FILECOUNT_LIMIT = (1 << 16) - 1
58 ZIP_MAX_COMMENT = (1 << 16) - 1
59
60 # constants for Zip file compression methods
61 ZIP_STORED = 0
62 ZIP_DEFLATED = 8
63 ZIP_BZIP2 = 12
64 ZIP_LZMA = 14
65 # Other ZIP compression methods not supported
66
67 DEFAULT_VERSION = 20
68 ZIP64_VERSION = 45
69 BZIP2_VERSION = 46
70 LZMA_VERSION = 63
71 # we recognize (but not necessarily support) all features up to that version
72 MAX_EXTRACT_VERSION = 63
73
74 # Below are some formats and associated data for reading/writing headers using
75 # the struct module. The names and structures of headers/records are those used
76 # in the PKWARE description of the ZIP file format:
77 # http://www.pkware.com/documents/casestudies/APPNOTE.TXT
78 # (URL valid as of January 2008)
79
80 # The "end of central directory" structure, magic number, size, and indices
81 # (section V.I in the format document)
82 structEndArchive = b"<4s4H2LH"
83 stringEndArchive = b"PK\005\006"
84 sizeEndCentDir = struct.calcsize(structEndArchive)
85
86 _ECD_SIGNATURE = 0
87 _ECD_DISK_NUMBER = 1
88 _ECD_DISK_START = 2
89 _ECD_ENTRIES_THIS_DISK = 3
90 _ECD_ENTRIES_TOTAL = 4
91 _ECD_SIZE = 5
92 _ECD_OFFSET = 6
93 _ECD_COMMENT_SIZE = 7
94 # These last two indices are not part of the structure as defined in the
95 # spec, but they are used internally by this module as a convenience
96 _ECD_COMMENT = 8
97 _ECD_LOCATION = 9
98
99 # The "central directory" structure, magic number, size, and indices
100 # of entries in the structure (section V.F in the format document)
101 structCentralDir = "<4s4B4HL2L5H2L"
102 stringCentralDir = b"PK\001\002"
103 sizeCentralDir = struct.calcsize(structCentralDir)
104
105 # indexes of entries in the central directory structure
106 _CD_SIGNATURE = 0
107 _CD_CREATE_VERSION = 1
108 _CD_CREATE_SYSTEM = 2
109 _CD_EXTRACT_VERSION = 3
110 _CD_EXTRACT_SYSTEM = 4
111 _CD_FLAG_BITS = 5
112 _CD_COMPRESS_TYPE = 6
113 _CD_TIME = 7
114 _CD_DATE = 8
115 _CD_CRC = 9
116 _CD_COMPRESSED_SIZE = 10
117 _CD_UNCOMPRESSED_SIZE = 11
118 _CD_FILENAME_LENGTH = 12
119 _CD_EXTRA_FIELD_LENGTH = 13
120 _CD_COMMENT_LENGTH = 14
121 _CD_DISK_NUMBER_START = 15
122 _CD_INTERNAL_FILE_ATTRIBUTES = 16
123 _CD_EXTERNAL_FILE_ATTRIBUTES = 17
124 _CD_LOCAL_HEADER_OFFSET = 18
125
126 # The "local file header" structure, magic number, size, and indices
127 # (section V.A in the format document)
128 structFileHeader = "<4s2B4HL2L2H"
129 stringFileHeader = b"PK\003\004"
130 sizeFileHeader = struct.calcsize(structFileHeader)
131
132 _FH_SIGNATURE = 0
133 _FH_EXTRACT_VERSION = 1
134 _FH_EXTRACT_SYSTEM = 2
135 _FH_GENERAL_PURPOSE_FLAG_BITS = 3
136 _FH_COMPRESSION_METHOD = 4
137 _FH_LAST_MOD_TIME = 5
138 _FH_LAST_MOD_DATE = 6
139 _FH_CRC = 7
140 _FH_COMPRESSED_SIZE = 8
141 _FH_UNCOMPRESSED_SIZE = 9
142 _FH_FILENAME_LENGTH = 10
143 _FH_EXTRA_FIELD_LENGTH = 11
144
145 # The "Zip64 end of central directory locator" structure, magic number, and size
146 structEndArchive64Locator = "<4sLQL"
147 stringEndArchive64Locator = b"PK\x06\x07"
148 sizeEndCentDir64Locator = struct.calcsize(structEndArchive64Locator)
149
150 # The "Zip64 end of central directory" record, magic number, size, and indices
151 # (section V.G in the format document)
152 structEndArchive64 = "<4sQ2H2L4Q"
153 stringEndArchive64 = b"PK\x06\x06"
154 sizeEndCentDir64 = struct.calcsize(structEndArchive64)
155
156 _CD64_SIGNATURE = 0
157 _CD64_DIRECTORY_RECSIZE = 1
158 _CD64_CREATE_VERSION = 2
159 _CD64_EXTRACT_VERSION = 3
160 _CD64_DISK_NUMBER = 4
161 _CD64_DISK_NUMBER_START = 5
162 _CD64_NUMBER_ENTRIES_THIS_DISK = 6
163 _CD64_NUMBER_ENTRIES_TOTAL = 7
164 _CD64_DIRECTORY_SIZE = 8
165 _CD64_OFFSET_START_CENTDIR = 9
166
167 def _check_zipfile(fp):
168 try:
169 if _EndRecData(fp):
170 return True # file has correct magic number
171 except OSError:
172 pass
173 return False
174
175 def is_zipfile(filename):
176 """Quickly see if a file is a ZIP file by checking the magic number.
177
178 The filename argument may be a file or file-like object too.
179 """
180 result = False
181 try:
182 if hasattr(filename, "read"):
183 result = _check_zipfile(fp=filename)
184 else:
185 with open(filename, "rb") as fp:
186 result = _check_zipfile(fp)
187 except OSError:
188 pass
189 return result
190
191 def _EndRecData64(fpin, offset, endrec):
192 """
193 Read the ZIP64 end-of-archive records and use that to update endrec
194 """
195 try:
196 fpin.seek(offset - sizeEndCentDir64Locator, 2)
197 except OSError:
198 # If the seek fails, the file is not large enough to contain a ZIP64
199 # end-of-archive record, so just return the end record we were given.
200 return endrec
201
202 data = fpin.read(sizeEndCentDir64Locator)
203 if len(data) != sizeEndCentDir64Locator:
204 return endrec
205 sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data)
206 if sig != stringEndArchive64Locator:
207 return endrec
208
209 if diskno != 0 or disks != 1:
210 raise BadZipFile("zipfiles that span multiple disks are not supported")
211
212 # Assume no 'zip64 extensible data'
213 fpin.seek(offset - sizeEndCentDir64Locator - sizeEndCentDir64, 2)
214 data = fpin.read(sizeEndCentDir64)
215 if len(data) != sizeEndCentDir64:
216 return endrec
217 sig, sz, create_version, read_version, disk_num, disk_dir, \
218 dircount, dircount2, dirsize, diroffset = \
219 struct.unpack(structEndArchive64, data)
220 if sig != stringEndArchive64:
221 return endrec
222
223 # Update the original endrec using data from the ZIP64 record
224 endrec[_ECD_SIGNATURE] = sig
225 endrec[_ECD_DISK_NUMBER] = disk_num
226 endrec[_ECD_DISK_START] = disk_dir
227 endrec[_ECD_ENTRIES_THIS_DISK] = dircount
228 endrec[_ECD_ENTRIES_TOTAL] = dircount2
229 endrec[_ECD_SIZE] = dirsize
230 endrec[_ECD_OFFSET] = diroffset
231 return endrec
232
233
234 def _EndRecData(fpin):
235 """Return data from the "End of Central Directory" record, or None.
236
237 The data is a list of the nine items in the ZIP "End of central dir"
238 record followed by a tenth item, the file seek offset of this record."""
239
240 # Determine file size
241 fpin.seek(0, 2)
242 filesize = fpin.tell()
243
244 # Check to see if this is ZIP file with no archive comment (the
245 # "end of central directory" structure should be the last item in the
246 # file if this is the case).
247 try:
248 fpin.seek(-sizeEndCentDir, 2)
249 except OSError:
250 return None
251 data = fpin.read()
252 if (len(data) == sizeEndCentDir and
253 data[0:4] == stringEndArchive and
254 data[-2:] == b"\000\000"):
255 # the signature is correct and there's no comment, unpack structure
256 endrec = struct.unpack(structEndArchive, data)
257 endrec=list(endrec)
258
259 # Append a blank comment and record start offset
260 endrec.append(b"")
261 endrec.append(filesize - sizeEndCentDir)
262
263 # Try to read the "Zip64 end of central directory" structure
264 return _EndRecData64(fpin, -sizeEndCentDir, endrec)
265
266 # Either this is not a ZIP file, or it is a ZIP file with an archive
267 # comment. Search the end of the file for the "end of central directory"
268 # record signature. The comment is the last item in the ZIP file and may be
269 # up to 64K long. It is assumed that the "end of central directory" magic
270 # number does not appear in the comment.
271 maxCommentStart = max(filesize - (1 << 16) - sizeEndCentDir, 0)
272 fpin.seek(maxCommentStart, 0)
273 data = fpin.read()
274 start = data.rfind(stringEndArchive)
275 if start >= 0:
276 # found the magic number; attempt to unpack and interpret
277 recData = data[start:start+sizeEndCentDir]
278 if len(recData) != sizeEndCentDir:
279 # Zip file is corrupted.
280 return None
281 endrec = list(struct.unpack(structEndArchive, recData))
282 commentSize = endrec[_ECD_COMMENT_SIZE] #as claimed by the zip file
283 comment = data[start+sizeEndCentDir:start+sizeEndCentDir+commentSize]
284 endrec.append(comment)
285 endrec.append(maxCommentStart + start)
286
287 # Try to read the "Zip64 end of central directory" structure
288 return _EndRecData64(fpin, maxCommentStart + start - filesize,
289 endrec)
290
291 # Unable to find a valid end of central directory structure
292 return None
293
294
295 class ZipInfo (object):
296 """Class with attributes describing each file in the ZIP archive."""
297
298 __slots__ = (
299 'orig_filename',
300 'filename',
301 'date_time',
302 'compress_type',
303 'comment',
304 'extra',
305 'create_system',
306 'create_version',
307 'extract_version',
308 'reserved',
309 'flag_bits',
310 'volume',
311 'internal_attr',
312 'external_attr',
313 'header_offset',
314 'CRC',
315 'compress_size',
316 'file_size',
317 '_raw_time',
318 )
319
320 def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
321 self.orig_filename = filename # Original file name in archive
322
323 # Terminate the file name at the first null byte. Null bytes in file
324 # names are used as tricks by viruses in archives.
325 null_byte = filename.find(chr(0))
326 if null_byte >= 0:
327 filename = filename[0:null_byte]
328 # This is used to ensure paths in generated ZIP files always use
329 # forward slashes as the directory separator, as required by the
330 # ZIP format specification.
331 if os.sep != "/" and os.sep in filename:
332 filename = filename.replace(os.sep, "/")
333
334 self.filename = filename # Normalized file name
335 self.date_time = date_time # year, month, day, hour, min, sec
336
337 if date_time[0] < 1980:
338 raise ValueError('ZIP does not support timestamps before 1980')
339
340 # Standard values:
341 self.compress_type = ZIP_STORED # Type of compression for the file
342 self.comment = b"" # Comment for each file
343 self.extra = b"" # ZIP extra data
344 if sys.platform == 'win32':
345 self.create_system = 0 # System which created ZIP archive
346 else:
347 # Assume everything else is unix-y
348 self.create_system = 3 # System which created ZIP archive
349 self.create_version = DEFAULT_VERSION # Version which created ZIP archive
350 self.extract_version = DEFAULT_VERSION # Version needed to extract archive
351 self.reserved = 0 # Must be zero
352 self.flag_bits = 0 # ZIP flag bits
353 self.volume = 0 # Volume number of file header
354 self.internal_attr = 0 # Internal attributes
355 self.external_attr = 0 # External file attributes
356 # Other attributes are set by class ZipFile:
357 # header_offset Byte offset to the file header
358 # CRC CRC-32 of the uncompressed file
359 # compress_size Size of the compressed file
360 # file_size Size of the uncompressed file
361
362 def __repr__(self):
363 result = ['<%s filename=%r' % (self.__class__.__name__, self.filename)]
364 if self.compress_type != ZIP_STORED:
365 result.append(' compress_type=%s' %
366 compressor_names.get(self.compress_type,
367 self.compress_type))
368 hi = self.external_attr >> 16
369 lo = self.external_attr & 0xFFFF
370 if hi:
371 result.append(' filemode=%r' % stat.filemode(hi))
372 if lo:
373 result.append(' external_attr=%#x' % lo)
374 isdir = self.is_dir()
375 if not isdir or self.file_size:
376 result.append(' file_size=%r' % self.file_size)
377 if ((not isdir or self.compress_size) and
378 (self.compress_type != ZIP_STORED or
379 self.file_size != self.compress_size)):
380 result.append(' compress_size=%r' % self.compress_size)
381 result.append('>')
382 return ''.join(result)
383
384 def FileHeader(self, zip64=None):
385 """Return the per-file header as a string."""
386 dt = self.date_time
387 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
388 dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
389 if self.flag_bits & 0x08:
390 # Set these to zero because we write them after the file data
391 CRC = compress_size = file_size = 0
392 else:
393 CRC = self.CRC
394 compress_size = self.compress_size
395 file_size = self.file_size
396
397 extra = self.extra
398
399 min_version = 0
400 if zip64 is None:
401 zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT
402 if zip64:
403 fmt = '<HHQQ'
404 extra = extra + struct.pack(fmt,
405 1, struct.calcsize(fmt)-4, file_size, compress_size)
406 if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT:
407 if not zip64:
408 raise LargeZipFile("Filesize would require ZIP64 extensions")
409 # File is larger than what fits into a 4 byte integer,
410 # fall back to the ZIP64 extension
411 file_size = 0xffffffff
412 compress_size = 0xffffffff
413 min_version = ZIP64_VERSION
414
415 if self.compress_type == ZIP_BZIP2:
416 min_version = max(BZIP2_VERSION, min_version)
417 elif self.compress_type == ZIP_LZMA:
418 min_version = max(LZMA_VERSION, min_version)
419
420 self.extract_version = max(min_version, self.extract_version)
421 self.create_version = max(min_version, self.create_version)
422 filename, flag_bits = self._encodeFilenameFlags()
423 header = struct.pack(structFileHeader, stringFileHeader,
424 self.extract_version, self.reserved, flag_bits,
425 self.compress_type, dostime, dosdate, CRC,
426 compress_size, file_size,
427 len(filename), len(extra))
428 return header + filename + extra
429
430 def _encodeFilenameFlags(self):
431 try:
432 return self.filename.encode('ascii'), self.flag_bits
433 except UnicodeEncodeError:
434 return self.filename.encode('utf-8'), self.flag_bits | 0x800
435
436 def _decodeExtra(self):
437 # Try to decode the extra field.
438 extra = self.extra
439 unpack = struct.unpack
440 while len(extra) >= 4:
441 tp, ln = unpack('<HH', extra[:4])
442 if tp == 1:
443 if ln >= 24:
444 counts = unpack('<QQQ', extra[4:28])
445 elif ln == 16:
446 counts = unpack('<QQ', extra[4:20])
447 elif ln == 8:
448 counts = unpack('<Q', extra[4:12])
449 elif ln == 0:
450 counts = ()
451 else:
452 raise BadZipFile("Corrupt extra field %04x (size=%d)" % (tp, ln))
453
454 idx = 0
455
456 # ZIP64 extension (large files and/or large archives)
457 if self.file_size in (0xffffffffffffffff, 0xffffffff):
458 self.file_size = counts[idx]
459 idx += 1
460
461 if self.compress_size == 0xFFFFFFFF:
462 self.compress_size = counts[idx]
463 idx += 1
464
465 if self.header_offset == 0xffffffff:
466 old = self.header_offset
467 self.header_offset = counts[idx]
468 idx+=1
469
470 extra = extra[ln+4:]
471
472 @classmethod
473 def from_file(cls, filename, arcname=None):
474 """Construct an appropriate ZipInfo for a file on the filesystem.
475
476 filename should be the path to a file or directory on the filesystem.
477
478 arcname is the name which it will have within the archive (by default,
479 this will be the same as filename, but without a drive letter and with
480 leading path separators removed).
481 """
482 if isinstance(filename, os.PathLike):
483 filename = os.fspath(filename)
484 st = os.stat(filename)
485 isdir = stat.S_ISDIR(st.st_mode)
486 mtime = time.localtime(st.st_mtime)
487 date_time = mtime[0:6]
488 # Create ZipInfo instance to store file information
489 if arcname is None:
490 arcname = filename
491 arcname = os.path.normpath(os.path.splitdrive(arcname)[1])
492 while arcname[0] in (os.sep, os.altsep):
493 arcname = arcname[1:]
494 if isdir:
495 arcname += '/'
496 zinfo = cls(arcname, date_time)
497 zinfo.external_attr = (st.st_mode & 0xFFFF) << 16 # Unix attributes
498 if isdir:
499 zinfo.file_size = 0
500 zinfo.external_attr |= 0x10 # MS-DOS directory flag
501 else:
502 zinfo.file_size = st.st_size
503
504 return zinfo
505
506 def is_dir(self):
507 """Return True if this archive member is a directory."""
508 return self.filename[-1] == '/'
509
510
511 class _ZipDecrypter:
512 """Class to handle decryption of files stored within a ZIP archive.
513
514 ZIP supports a password-based form of encryption. Even though known
515 plaintext attacks have been found against it, it is still useful
516 to be able to get data out of such a file.
517
518 Usage:
519 zd = _ZipDecrypter(mypwd)
520 plain_char = zd(cypher_char)
521 plain_text = map(zd, cypher_text)
522 """
523
524 def _GenerateCRCTable():
525 """Generate a CRC-32 table.
526
527 ZIP encryption uses the CRC32 one-byte primitive for scrambling some
528 internal keys. We noticed that a direct implementation is faster than
529 relying on binascii.crc32().
530 """
531 poly = 0xedb88320
532 table = [0] * 256
533 for i in range(256):
534 crc = i
535 for j in range(8):
536 if crc & 1:
537 crc = ((crc >> 1) & 0x7FFFFFFF) ^ poly
538 else:
539 crc = ((crc >> 1) & 0x7FFFFFFF)
540 table[i] = crc
541 return table
542 crctable = None
543
544 def _crc32(self, ch, crc):
545 """Compute the CRC32 primitive on one byte."""
546 return ((crc >> 8) & 0xffffff) ^ self.crctable[(crc ^ ch) & 0xff]
547
548 def __init__(self, pwd):
549 if _ZipDecrypter.crctable is None:
550 _ZipDecrypter.crctable = _ZipDecrypter._GenerateCRCTable()
551 self.key0 = 305419896
552 self.key1 = 591751049
553 self.key2 = 878082192
554 for p in pwd:
555 self._UpdateKeys(p)
556
557 def _UpdateKeys(self, c):
558 self.key0 = self._crc32(c, self.key0)
559 self.key1 = (self.key1 + (self.key0 & 255)) & 4294967295
560 self.key1 = (self.key1 * 134775813 + 1) & 4294967295
561 self.key2 = self._crc32((self.key1 >> 24) & 255, self.key2)
562
563 def __call__(self, c):
564 """Decrypt a single character."""
565 assert isinstance(c, int)
566 k = self.key2 | 2
567 c = c ^ (((k * (k^1)) >> 8) & 255)
568 self._UpdateKeys(c)
569 return c
570
571
572 class LZMACompressor:
573
574 def __init__(self):
575 self._comp = None
576
577 def _init(self):
578 props = lzma._encode_filter_properties({'id': lzma.FILTER_LZMA1})
579 self._comp = lzma.LZMACompressor(lzma.FORMAT_RAW, filters=[
580 lzma._decode_filter_properties(lzma.FILTER_LZMA1, props)
581 ])
582 return struct.pack('<BBH', 9, 4, len(props)) + props
583
584 def compress(self, data):
585 if self._comp is None:
586 return self._init() + self._comp.compress(data)
587 return self._comp.compress(data)
588
589 def flush(self):
590 if self._comp is None:
591 return self._init() + self._comp.flush()
592 return self._comp.flush()
593
594
595 class LZMADecompressor:
596
597 def __init__(self):
598 self._decomp = None
599 self._unconsumed = b''
600 self.eof = False
601
602 def decompress(self, data):
603 if self._decomp is None:
604 self._unconsumed += data
605 if len(self._unconsumed) <= 4:
606 return b''
607 psize, = struct.unpack('<H', self._unconsumed[2:4])
608 if len(self._unconsumed) <= 4 + psize:
609 return b''
610
611 self._decomp = lzma.LZMADecompressor(lzma.FORMAT_RAW, filters=[
612 lzma._decode_filter_properties(lzma.FILTER_LZMA1,
613 self._unconsumed[4:4 + psize])
614 ])
615 data = self._unconsumed[4 + psize:]
616 del self._unconsumed
617
618 result = self._decomp.decompress(data)
619 self.eof = self._decomp.eof
620 return result
621
622
623 compressor_names = {
624 0: 'store',
625 1: 'shrink',
626 2: 'reduce',
627 3: 'reduce',
628 4: 'reduce',
629 5: 'reduce',
630 6: 'implode',
631 7: 'tokenize',
632 8: 'deflate',
633 9: 'deflate64',
634 10: 'implode',
635 12: 'bzip2',
636 14: 'lzma',
637 18: 'terse',
638 19: 'lz77',
639 97: 'wavpack',
640 98: 'ppmd',
641 }
642
643 def _check_compression(compression):
644 if compression == ZIP_STORED:
645 pass
646 elif compression == ZIP_DEFLATED:
647 if not zlib:
648 raise RuntimeError(
649 "Compression requires the (missing) zlib module")
650 elif compression == ZIP_BZIP2:
651 if not bz2:
652 raise RuntimeError(
653 "Compression requires the (missing) bz2 module")
654 elif compression == ZIP_LZMA:
655 if not lzma:
656 raise RuntimeError(
657 "Compression requires the (missing) lzma module")
658 else:
659 raise NotImplementedError("That compression method is not supported")
660
661
662 def _get_compressor(compress_type):
663 if compress_type == ZIP_DEFLATED:
664 return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
665 zlib.DEFLATED, -15)
666 elif compress_type == ZIP_BZIP2:
667 return bz2.BZ2Compressor()
668 elif compress_type == ZIP_LZMA:
669 return LZMACompressor()
670 else:
671 return None
672
673
674 def _get_decompressor(compress_type):
675 if compress_type == ZIP_STORED:
676 return None
677 elif compress_type == ZIP_DEFLATED:
678 return zlib.decompressobj(-15)
679 elif compress_type == ZIP_BZIP2:
680 return bz2.BZ2Decompressor()
681 elif compress_type == ZIP_LZMA:
682 return LZMADecompressor()
683 else:
684 descr = compressor_names.get(compress_type)
685 if descr:
686 raise NotImplementedError("compression type %d (%s)" % (compress_type, descr))
687 else:
688 raise NotImplementedError("compression type %d" % (compress_type,))
689
690
691 class _SharedFile:
692 def __init__(self, file, pos, close, lock, writing):
693 self._file = file
694 self._pos = pos
695 self._close = close
696 self._lock = lock
697 self._writing = writing
698
699 def read(self, n=-1):
700 with self._lock:
701 if self._writing():
702 raise ValueError("Can't read from the ZIP file while there "
703 "is an open writing handle on it. "
704 "Close the writing handle before trying to read.")
705 self._file.seek(self._pos)
706 data = self._file.read(n)
707 self._pos = self._file.tell()
708 return data
709
710 def close(self):
711 if self._file is not None:
712 fileobj = self._file
713 self._file = None
714 self._close(fileobj)
715
716 # Provide the tell method for unseekable stream
717 class _Tellable:
718 def __init__(self, fp):
719 self.fp = fp
720 self.offset = 0
721
722 def write(self, data):
723 n = self.fp.write(data)
724 self.offset += n
725 return n
726
727 def tell(self):
728 return self.offset
729
730 def flush(self):
731 self.fp.flush()
732
733 def close(self):
734 self.fp.close()
735
736
737 class ZipExtFile(io.BufferedIOBase):
738 """File-like object for reading an archive member.
739 Is returned by ZipFile.open().
740 """
741
742 # Max size supported by decompressor.
743 MAX_N = 1 << 31 - 1
744
745 # Read from compressed files in 4k blocks.
746 MIN_READ_SIZE = 4096
747
748 def __init__(self, fileobj, mode, zipinfo, decrypter=None,
749 close_fileobj=False):
750 self._fileobj = fileobj
751 self._decrypter = decrypter
752 self._close_fileobj = close_fileobj
753
754 self._compress_type = zipinfo.compress_type
755 self._compress_left = zipinfo.compress_size
756 self._left = zipinfo.file_size
757
758 self._decompressor = _get_decompressor(self._compress_type)
759
760 self._eof = False
761 self._readbuffer = b''
762 self._offset = 0
763
764 self.newlines = None
765
766 # Adjust read size for encrypted files since the first 12 bytes
767 # are for the encryption/password information.
768 if self._decrypter is not None:
769 self._compress_left -= 12
770
771 self.mode = mode
772 self.name = zipinfo.filename
773
774 if hasattr(zipinfo, 'CRC'):
775 self._expected_crc = zipinfo.CRC
776 self._running_crc = crc32(b'')
777 else:
778 self._expected_crc = None
779
780 def __repr__(self):
781 result = ['<%s.%s' % (self.__class__.__module__,
782 self.__class__.__qualname__)]
783 if not self.closed:
784 result.append(' name=%r mode=%r' % (self.name, self.mode))
785 if self._compress_type != ZIP_STORED:
786 result.append(' compress_type=%s' %
787 compressor_names.get(self._compress_type,
788 self._compress_type))
789 else:
790 result.append(' [closed]')
791 result.append('>')
792 return ''.join(result)
793
794 def readline(self, limit=-1):
795 """Read and return a line from the stream.
796
797 If limit is specified, at most limit bytes will be read.
798 """
799
800 if limit < 0:
801 # Shortcut common case - newline found in buffer.
802 i = self._readbuffer.find(b'\n', self._offset) + 1
803 if i > 0:
804 line = self._readbuffer[self._offset: i]
805 self._offset = i
806 return line
807
808 return io.BufferedIOBase.readline(self, limit)
809
810 def peek(self, n=1):
811 """Returns buffered bytes without advancing the position."""
812 if n > len(self._readbuffer) - self._offset:
813 chunk = self.read(n)
814 if len(chunk) > self._offset:
815 self._readbuffer = chunk + self._readbuffer[self._offset:]
816 self._offset = 0
817 else:
818 self._offset -= len(chunk)
819
820 # Return up to 512 bytes to reduce allocation overhead for tight loops.
821 return self._readbuffer[self._offset: self._offset + 512]
822
823 def readable(self):
824 return True
825
826 def read(self, n=-1):
827 """Read and return up to n bytes.
828 If the argument is omitted, None, or negative, data is read and returned until EOF is reached..
829 """
830 if n is None or n < 0:
831 buf = self._readbuffer[self._offset:]
832 self._readbuffer = b''
833 self._offset = 0
834 while not self._eof:
835 buf += self._read1(self.MAX_N)
836 return buf
837
838 end = n + self._offset
839 if end < len(self._readbuffer):
840 buf = self._readbuffer[self._offset:end]
841 self._offset = end
842 return buf
843
844 n = end - len(self._readbuffer)
845 buf = self._readbuffer[self._offset:]
846 self._readbuffer = b''
847 self._offset = 0
848 while n > 0 and not self._eof:
849 data = self._read1(n)
850 if n < len(data):
851 self._readbuffer = data
852 self._offset = n
853 buf += data[:n]
854 break
855 buf += data
856 n -= len(data)
857 return buf
858
859 def _update_crc(self, newdata):
860 # Update the CRC using the given data.
861 if self._expected_crc is None:
862 # No need to compute the CRC if we don't have a reference value
863 return
864 self._running_crc = crc32(newdata, self._running_crc)
865 # Check the CRC if we're at the end of the file
866 if self._eof and self._running_crc != self._expected_crc:
867 raise BadZipFile("Bad CRC-32 for file %r" % self.name)
868
869 def read1(self, n):
870 """Read up to n bytes with at most one read() system call."""
871
872 if n is None or n < 0:
873 buf = self._readbuffer[self._offset:]
874 self._readbuffer = b''
875 self._offset = 0
876 while not self._eof:
877 data = self._read1(self.MAX_N)
878 if data:
879 buf += data
880 break
881 return buf
882
883 end = n + self._offset
884 if end < len(self._readbuffer):
885 buf = self._readbuffer[self._offset:end]
886 self._offset = end
887 return buf
888
889 n = end - len(self._readbuffer)
890 buf = self._readbuffer[self._offset:]
891 self._readbuffer = b''
892 self._offset = 0
893 if n > 0:
894 while not self._eof:
895 data = self._read1(n)
896 if n < len(data):
897 self._readbuffer = data
898 self._offset = n
899 buf += data[:n]
900 break
901 if data:
902 buf += data
903 break
904 return buf
905
906 def _read1(self, n):
907 # Read up to n compressed bytes with at most one read() system call,
908 # decrypt and decompress them.
909 if self._eof or n <= 0:
910 return b''
911
912 # Read from file.
913 if self._compress_type == ZIP_DEFLATED:
914 ## Handle unconsumed data.
915 data = self._decompressor.unconsumed_tail
916 if n > len(data):
917 data += self._read2(n - len(data))
918 else:
919 data = self._read2(n)
920
921 if self._compress_type == ZIP_STORED:
922 self._eof = self._compress_left <= 0
923 elif self._compress_type == ZIP_DEFLATED:
924 n = max(n, self.MIN_READ_SIZE)
925 data = self._decompressor.decompress(data, n)
926 self._eof = (self._decompressor.eof or
927 self._compress_left <= 0 and
928 not self._decompressor.unconsumed_tail)
929 if self._eof:
930 data += self._decompressor.flush()
931 else:
932 data = self._decompressor.decompress(data)
933 self._eof = self._decompressor.eof or self._compress_left <= 0
934
935 data = data[:self._left]
936 self._left -= len(data)
937 if self._left <= 0:
938 self._eof = True
939 self._update_crc(data)
940 return data
941
942 def _read2(self, n):
943 if self._compress_left <= 0:
944 return b''
945
946 n = max(n, self.MIN_READ_SIZE)
947 n = min(n, self._compress_left)
948
949 data = self._fileobj.read(n)
950 self._compress_left -= len(data)
951 if not data:
952 raise EOFError
953
954 if self._decrypter is not None:
955 data = bytes(map(self._decrypter, data))
956 return data
957
958 def close(self):
959 try:
960 if self._close_fileobj:
961 self._fileobj.close()
962 finally:
963 super().close()
964
965
966 class _ZipWriteFile(io.BufferedIOBase):
967 def __init__(self, zf, zinfo, zip64):
968 self._zinfo = zinfo
969 self._zip64 = zip64
970 self._zipfile = zf
971 self._compressor = _get_compressor(zinfo.compress_type)
972 self._file_size = 0
973 self._compress_size = 0
974 self._crc = 0
975
976 @property
977 def _fileobj(self):
978 return self._zipfile.fp
979
980 def writable(self):
981 return True
982
983 def write(self, data):
984 if self.closed:
985 raise ValueError('I/O operation on closed file.')
986 nbytes = len(data)
987 self._file_size += nbytes
988 self._crc = crc32(data, self._crc)
989 if self._compressor:
990 data = self._compressor.compress(data)
991 self._compress_size += len(data)
992 self._fileobj.write(data)
993 return nbytes
994
995 def close(self):
996 if self.closed:
997 return
998 super().close()
999 # Flush any data from the compressor, and update header info
1000 if self._compressor:
1001 buf = self._compressor.flush()
1002 self._compress_size += len(buf)
1003 self._fileobj.write(buf)
1004 self._zinfo.compress_size = self._compress_size
1005 else:
1006 self._zinfo.compress_size = self._file_size
1007 self._zinfo.CRC = self._crc
1008 self._zinfo.file_size = self._file_size
1009
1010 # Write updated header info
1011 if self._zinfo.flag_bits & 0x08:
1012 # Write CRC and file sizes after the file data
1013 fmt = '<LQQ' if self._zip64 else '<LLL'
1014 self._fileobj.write(struct.pack(fmt, self._zinfo.CRC,
1015 self._zinfo.compress_size, self._zinfo.file_size))
1016 self._zipfile.start_dir = self._fileobj.tell()
1017 else:
1018 if not self._zip64:
1019 if self._file_size > ZIP64_LIMIT:
1020 raise RuntimeError('File size unexpectedly exceeded ZIP64 '
1021 'limit')
1022 if self._compress_size > ZIP64_LIMIT:
1023 raise RuntimeError('Compressed size unexpectedly exceeded '
1024 'ZIP64 limit')
1025 # Seek backwards and write file header (which will now include
1026 # correct CRC and file sizes)
1027
1028 # Preserve current position in file
1029 self._zipfile.start_dir = self._fileobj.tell()
1030 self._fileobj.seek(self._zinfo.header_offset)
1031 self._fileobj.write(self._zinfo.FileHeader(self._zip64))
1032 self._fileobj.seek(self._zipfile.start_dir)
1033
1034 self._zipfile._writing = False
1035
1036 # Successfully written: Add file to our caches
1037 self._zipfile.filelist.append(self._zinfo)
1038 self._zipfile.NameToInfo[self._zinfo.filename] = self._zinfo
1039
1040 class ZipFile:
1041 """ Class with methods to open, read, write, close, list zip files.
1042
1043 z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True)
1044
1045 file: Either the path to the file, or a file-like object.
1046 If it is a path, the file will be opened and closed by ZipFile.
1047 mode: The mode can be either read 'r', write 'w', exclusive create 'x',
1048 or append 'a'.
1049 compression: ZIP_STORED (no compression), ZIP_DEFLATED (requires zlib),
1050 ZIP_BZIP2 (requires bz2) or ZIP_LZMA (requires lzma).
1051 allowZip64: if True ZipFile will create files with ZIP64 extensions when
1052 needed, otherwise it will raise an exception when this would
1053 be necessary.
1054
1055 """
1056
1057 fp = None # Set here since __del__ checks it
1058 _windows_illegal_name_trans_table = None
1059
1060 def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=True):
1061 """Open the ZIP file with mode read 'r', write 'w', exclusive create 'x',
1062 or append 'a'."""
1063 if mode not in ('r', 'w', 'x', 'a'):
1064 raise ValueError("ZipFile requires mode 'r', 'w', 'x', or 'a'")
1065
1066 _check_compression(compression)
1067
1068 self._allowZip64 = allowZip64
1069 self._didModify = False
1070 self.debug = 0 # Level of printing: 0 through 3
1071 self.NameToInfo = {} # Find file info given name
1072 self.filelist = [] # List of ZipInfo instances for archive
1073 self.compression = compression # Method of compression
1074 self.mode = mode
1075 self.pwd = None
1076 self._comment = b''
1077
1078 # Check if we were passed a file-like object
1079 if isinstance(file, os.PathLike):
1080 file = os.fspath(file)
1081 if isinstance(file, str):
1082 # No, it's a filename
1083 self._filePassed = 0
1084 self.filename = file
1085 modeDict = {'r' : 'rb', 'w': 'w+b', 'x': 'x+b', 'a' : 'r+b',
1086 'r+b': 'w+b', 'w+b': 'wb', 'x+b': 'xb'}
1087 filemode = modeDict[mode]
1088 while True:
1089 try:
1090 self.fp = io.open(file, filemode)
1091 except OSError:
1092 if filemode in modeDict:
1093 filemode = modeDict[filemode]
1094 continue
1095 raise
1096 break
1097 else:
1098 self._filePassed = 1
1099 self.fp = file
1100 self.filename = getattr(file, 'name', None)
1101 self._fileRefCnt = 1
1102 self._lock = threading.RLock()
1103 self._seekable = True
1104 self._writing = False
1105
1106 try:
1107 if mode == 'r':
1108 self._RealGetContents()
1109 elif mode in ('w', 'x'):
1110 # set the modified flag so central directory gets written
1111 # even if no files are added to the archive
1112 self._didModify = True
1113 try:
1114 self.start_dir = self.fp.tell()
1115 except (AttributeError, OSError):
1116 self.fp = _Tellable(self.fp)
1117 self.start_dir = 0
1118 self._seekable = False
1119 else:
1120 # Some file-like objects can provide tell() but not seek()
1121 try:
1122 self.fp.seek(self.start_dir)
1123 except (AttributeError, OSError):
1124 self._seekable = False
1125 elif mode == 'a':
1126 try:
1127 # See if file is a zip file
1128 self._RealGetContents()
1129 # seek to start of directory and overwrite
1130 self.fp.seek(self.start_dir)
1131 except BadZipFile:
1132 # file is not a zip file, just append
1133 self.fp.seek(0, 2)
1134
1135 # set the modified flag so central directory gets written
1136 # even if no files are added to the archive
1137 self._didModify = True
1138 self.start_dir = self.fp.tell()
1139 else:
1140 raise ValueError("Mode must be 'r', 'w', 'x', or 'a'")
1141 except:
1142 fp = self.fp
1143 self.fp = None
1144 self._fpclose(fp)
1145 raise
1146
1147 def __enter__(self):
1148 return self
1149
1150 def __exit__(self, type, value, traceback):
1151 self.close()
1152
1153 def __repr__(self):
1154 result = ['<%s.%s' % (self.__class__.__module__,
1155 self.__class__.__qualname__)]
1156 if self.fp is not None:
1157 if self._filePassed:
1158 result.append(' file=%r' % self.fp)
1159 elif self.filename is not None:
1160 result.append(' filename=%r' % self.filename)
1161 result.append(' mode=%r' % self.mode)
1162 else:
1163 result.append(' [closed]')
1164 result.append('>')
1165 return ''.join(result)
1166
1167 def _RealGetContents(self):
1168 """Read in the table of contents for the ZIP file."""
1169 fp = self.fp
1170 try:
1171 endrec = _EndRecData(fp)
1172 except OSError:
1173 raise BadZipFile("File is not a zip file")
1174 if not endrec:
1175 raise BadZipFile("File is not a zip file")
1176 if self.debug > 1:
1177 print(endrec)
1178 size_cd = endrec[_ECD_SIZE] # bytes in central directory
1179 offset_cd = endrec[_ECD_OFFSET] # offset of central directory
1180 self._comment = endrec[_ECD_COMMENT] # archive comment
1181
1182 # "concat" is zero, unless zip was concatenated to another file
1183 concat = endrec[_ECD_LOCATION] - size_cd - offset_cd
1184 if endrec[_ECD_SIGNATURE] == stringEndArchive64:
1185 # If Zip64 extension structures are present, account for them
1186 concat -= (sizeEndCentDir64 + sizeEndCentDir64Locator)
1187
1188 if self.debug > 2:
1189 inferred = concat + offset_cd
1190 print("given, inferred, offset", offset_cd, inferred, concat)
1191 # self.start_dir: Position of start of central directory
1192 self.start_dir = offset_cd + concat
1193 fp.seek(self.start_dir, 0)
1194 data = fp.read(size_cd)
1195 fp = io.BytesIO(data)
1196 total = 0
1197 while total < size_cd:
1198 centdir = fp.read(sizeCentralDir)
1199 if len(centdir) != sizeCentralDir:
1200 raise BadZipFile("Truncated central directory")
1201 centdir = struct.unpack(structCentralDir, centdir)
1202 if centdir[_CD_SIGNATURE] != stringCentralDir:
1203 raise BadZipFile("Bad magic number for central directory")
1204 if self.debug > 2:
1205 print(centdir)
1206 filename = fp.read(centdir[_CD_FILENAME_LENGTH])
1207 flags = centdir[5]
1208 if flags & 0x800:
1209 # UTF-8 file names extension
1210 filename = filename.decode('utf-8')
1211 else:
1212 # Historical ZIP filename encoding
1213 filename = filename.decode('cp437')
1214 try:
1215 filename = filename.encode("cp437").decode('gbk')
1216 except:
1217 pass
1218 # Create ZipInfo instance to store file information
1219 x = ZipInfo(filename)
1220 x.extra = fp.read(centdir[_CD_EXTRA_FIELD_LENGTH])
1221 x.comment = fp.read(centdir[_CD_COMMENT_LENGTH])
1222 x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET]
1223 (x.create_version, x.create_system, x.extract_version, x.reserved,
1224 x.flag_bits, x.compress_type, t, d,
1225 x.CRC, x.compress_size, x.file_size) = centdir[1:12]
1226 if x.extract_version > MAX_EXTRACT_VERSION:
1227 raise NotImplementedError("zip file version %.1f" %
1228 (x.extract_version / 10))
1229 x.volume, x.internal_attr, x.external_attr = centdir[15:18]
1230 # Convert date/time code to (year, month, day, hour, min, sec)
1231 x._raw_time = t
1232 x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
1233 t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
1234
1235 x._decodeExtra()
1236 x.header_offset = x.header_offset + concat
1237 self.filelist.append(x)
1238 self.NameToInfo[x.filename] = x
1239
1240 # update total bytes read from central directory
1241 total = (total + sizeCentralDir + centdir[_CD_FILENAME_LENGTH]
1242 + centdir[_CD_EXTRA_FIELD_LENGTH]
1243 + centdir[_CD_COMMENT_LENGTH])
1244
1245 if self.debug > 2:
1246 print("total", total)
1247
1248
1249 def namelist(self):
1250 """Return a list of file names in the archive."""
1251 return [data.filename for data in self.filelist]
1252
1253 def infolist(self):
1254 """Return a list of class ZipInfo instances for files in the
1255 archive."""
1256 return self.filelist
1257
1258 def printdir(self, file=None):
1259 """Print a table of contents for the zip file."""
1260 print("%-46s %19s %12s" % ("File Name", "Modified ", "Size"),
1261 file=file)
1262 for zinfo in self.filelist:
1263 date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time[:6]
1264 print("%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size),
1265 file=file)
1266
1267 def testzip(self):
1268 """Read all the files and check the CRC."""
1269 chunk_size = 2 ** 20
1270 for zinfo in self.filelist:
1271 try:
1272 # Read by chunks, to avoid an OverflowError or a
1273 # MemoryError with very large embedded files.
1274 with self.open(zinfo.filename, "r") as f:
1275 while f.read(chunk_size): # Check CRC-32
1276 pass
1277 except BadZipFile:
1278 return zinfo.filename
1279
1280 def getinfo(self, name):
1281 """Return the instance of ZipInfo given 'name'."""
1282 info = self.NameToInfo.get(name)
1283 if info is None:
1284 raise KeyError(
1285 'There is no item named %r in the archive' % name)
1286
1287 return info
1288
1289 def setpassword(self, pwd):
1290 """Set default password for encrypted files."""
1291 if pwd and not isinstance(pwd, bytes):
1292 raise TypeError("pwd: expected bytes, got %s" % type(pwd).__name__)
1293 if pwd:
1294 self.pwd = pwd
1295 else:
1296 self.pwd = None
1297
1298 @property
1299 def comment(self):
1300 """The comment text associated with the ZIP file."""
1301 return self._comment
1302
1303 @comment.setter
1304 def comment(self, comment):
1305 if not isinstance(comment, bytes):
1306 raise TypeError("comment: expected bytes, got %s" % type(comment).__name__)
1307 # check for valid comment length
1308 if len(comment) > ZIP_MAX_COMMENT:
1309 import warnings
1310 warnings.warn('Archive comment is too long; truncating to %d bytes'
1311 % ZIP_MAX_COMMENT, stacklevel=2)
1312 comment = comment[:ZIP_MAX_COMMENT]
1313 self._comment = comment
1314 self._didModify = True
1315
1316 def read(self, name, pwd=None):
1317 """Return file bytes (as a string) for name."""
1318 with self.open(name, "r", pwd) as fp:
1319 return fp.read()
1320
1321 def open(self, name, mode="r", pwd=None, *, force_zip64=False):
1322 """Return file-like object for 'name'.
1323
1324 name is a string for the file name within the ZIP file, or a ZipInfo
1325 object.
1326
1327 mode should be 'r' to read a file already in the ZIP file, or 'w' to
1328 write to a file newly added to the archive.
1329
1330 pwd is the password to decrypt files (only used for reading).
1331
1332 When writing, if the file size is not known in advance but may exceed
1333 2 GiB, pass force_zip64 to use the ZIP64 format, which can handle large
1334 files. If the size is known in advance, it is best to pass a ZipInfo
1335 instance for name, with zinfo.file_size set.
1336 """
1337 if mode not in {"r", "w"}:
1338 raise ValueError('open() requires mode "r" or "w"')
1339 if pwd and not isinstance(pwd, bytes):
1340 raise TypeError("pwd: expected bytes, got %s" % type(pwd).__name__)
1341 if pwd and (mode == "w"):
1342 raise ValueError("pwd is only supported for reading files")
1343 if not self.fp:
1344 raise ValueError(
1345 "Attempt to use ZIP archive that was already closed")
1346
1347 # Make sure we have an info object
1348 if isinstance(name, ZipInfo):
1349 # 'name' is already an info object
1350 zinfo = name
1351 elif mode == 'w':
1352 zinfo = ZipInfo(name)
1353 zinfo.compress_type = self.compression
1354 else:
1355 # Get info object for name
1356 zinfo = self.getinfo(name)
1357
1358 if mode == 'w':
1359 return self._open_to_write(zinfo, force_zip64=force_zip64)
1360
1361 if self._writing:
1362 raise ValueError("Can't read from the ZIP file while there "
1363 "is an open writing handle on it. "
1364 "Close the writing handle before trying to read.")
1365
1366 # Open for reading:
1367 self._fileRefCnt += 1
1368 zef_file = _SharedFile(self.fp, zinfo.header_offset,
1369 self._fpclose, self._lock, lambda: self._writing)
1370 try:
1371 # Skip the file header:
1372 fheader = zef_file.read(sizeFileHeader)
1373 if len(fheader) != sizeFileHeader:
1374 raise BadZipFile("Truncated file header")
1375 fheader = struct.unpack(structFileHeader, fheader)
1376 if fheader[_FH_SIGNATURE] != stringFileHeader:
1377 raise BadZipFile("Bad magic number for file header")
1378
1379 fname = zef_file.read(fheader[_FH_FILENAME_LENGTH])
1380 if fheader[_FH_EXTRA_FIELD_LENGTH]:
1381 zef_file.read(fheader[_FH_EXTRA_FIELD_LENGTH])
1382
1383 if zinfo.flag_bits & 0x20:
1384 # Zip 2.7: compressed patched data
1385 raise NotImplementedError("compressed patched data (flag bit 5)")
1386
1387 if zinfo.flag_bits & 0x40:
1388 # strong encryption
1389 raise NotImplementedError("strong encryption (flag bit 6)")
1390
1391 if zinfo.flag_bits & 0x800:
1392 # UTF-8 filename
1393 fname_str = fname.decode("utf-8")
1394 else:
1395 fname_str = fname.decode("cp437")
1396 try:
1397 fname_str = fname_str.encode("cp437").decode('gbk')
1398 except:
1399 pass
1400
1401 if fname_str != zinfo.orig_filename:
1402 raise BadZipFile(
1403 'File name in directory %r and header %r differ.'
1404 % (zinfo.orig_filename, fname))
1405
1406 # check for encrypted flag & handle password
1407 is_encrypted = zinfo.flag_bits & 0x1
1408 zd = None
1409 if is_encrypted:
1410 if not pwd:
1411 pwd = self.pwd
1412 if not pwd:
1413 raise RuntimeError("File %r is encrypted, password "
1414 "required for extraction" % name)
1415
1416 zd = _ZipDecrypter(pwd)
1417 # The first 12 bytes in the cypher stream is an encryption header
1418 # used to strengthen the algorithm. The first 11 bytes are
1419 # completely random, while the 12th contains the MSB of the CRC,
1420 # or the MSB of the file time depending on the header type
1421 # and is used to check the correctness of the password.
1422 header = zef_file.read(12)
1423 h = list(map(zd, header[0:12]))
1424 if zinfo.flag_bits & 0x8:
1425 # compare against the file type from extended local headers
1426 check_byte = (zinfo._raw_time >> 8) & 0xff
1427 else:
1428 # compare against the CRC otherwise
1429 check_byte = (zinfo.CRC >> 24) & 0xff
1430 if h[11] != check_byte:
1431 raise RuntimeError("Bad password for file %r" % name)
1432
1433 return ZipExtFile(zef_file, mode, zinfo, zd, True)
1434 except:
1435 zef_file.close()
1436 raise
1437
1438 def _open_to_write(self, zinfo, force_zip64=False):
1439 if force_zip64 and not self._allowZip64:
1440 raise ValueError(
1441 "force_zip64 is True, but allowZip64 was False when opening "
1442 "the ZIP file."
1443 )
1444 if self._writing:
1445 raise ValueError("Can't write to the ZIP file while there is "
1446 "another write handle open on it. "
1447 "Close the first handle before opening another.")
1448
1449 # Sizes and CRC are overwritten with correct data after processing the file
1450 if not hasattr(zinfo, 'file_size'):
1451 zinfo.file_size = 0
1452 zinfo.compress_size = 0
1453 zinfo.CRC = 0
1454
1455 zinfo.flag_bits = 0x00
1456 if zinfo.compress_type == ZIP_LZMA:
1457 # Compressed data includes an end-of-stream (EOS) marker
1458 zinfo.flag_bits |= 0x02
1459 if not self._seekable:
1460 zinfo.flag_bits |= 0x08
1461
1462 if not zinfo.external_attr:
1463 zinfo.external_attr = 0o600 << 16 # permissions: ?rw-------
1464
1465 # Compressed size can be larger than uncompressed size
1466 zip64 = self._allowZip64 and \
1467 (force_zip64 or zinfo.file_size * 1.05 > ZIP64_LIMIT)
1468
1469 if self._seekable:
1470 self.fp.seek(self.start_dir)
1471 zinfo.header_offset = self.fp.tell()
1472
1473 self._writecheck(zinfo)
1474 self._didModify = True
1475
1476 self.fp.write(zinfo.FileHeader(zip64))
1477
1478 self._writing = True
1479 return _ZipWriteFile(self, zinfo, zip64)
1480
1481 def extract(self, member, path=None, pwd=None):
1482 """Extract a member from the archive to the current working directory,
1483 using its full name. Its file information is extracted as accurately
1484 as possible. `member' may be a filename or a ZipInfo object. You can
1485 specify a different directory using `path'.
1486 """
1487 if path is None:
1488 path = os.getcwd()
1489 else:
1490 path = os.fspath(path)
1491
1492 return self._extract_member(member, path, pwd)
1493
1494 def extractall(self, path=None, members=None, pwd=None):
1495 """Extract all members from the archive to the current working
1496 directory. `path' specifies a different directory to extract to.
1497 `members' is optional and must be a subset of the list returned
1498 by namelist().
1499 """
1500 if members is None:
1501 members = self.namelist()
1502
1503 if path is None:
1504 path = os.getcwd()
1505 else:
1506 path = os.fspath(path)
1507
1508 for zipinfo in members:
1509 self._extract_member(zipinfo, path, pwd)
1510
1511 @classmethod
1512 def _sanitize_windows_name(cls, arcname, pathsep):
1513 """Replace bad characters and remove trailing dots from parts."""
1514 table = cls._windows_illegal_name_trans_table
1515 if not table:
1516 illegal = ':<>|"?*'
1517 table = str.maketrans(illegal, '_' * len(illegal))
1518 cls._windows_illegal_name_trans_table = table
1519 arcname = arcname.translate(table)
1520 # remove trailing dots
1521 arcname = (x.rstrip('.') for x in arcname.split(pathsep))
1522 # rejoin, removing empty parts.
1523 arcname = pathsep.join(x for x in arcname if x)
1524 return arcname
1525
1526 def _extract_member(self, member, targetpath, pwd):
1527 """Extract the ZipInfo object 'member' to a physical
1528 file on the path targetpath.
1529 """
1530 if not isinstance(member, ZipInfo):
1531 member = self.getinfo(member)
1532
1533 # build the destination pathname, replacing
1534 # forward slashes to platform specific separators.
1535 arcname = member.filename.replace('/', os.path.sep)
1536
1537 if os.path.altsep:
1538 arcname = arcname.replace(os.path.altsep, os.path.sep)
1539 # interpret absolute pathname as relative, remove drive letter or
1540 # UNC path, redundant separators, "." and ".." components.
1541 arcname = os.path.splitdrive(arcname)[1]
1542 invalid_path_parts = ('', os.path.curdir, os.path.pardir)
1543 arcname = os.path.sep.join(x for x in arcname.split(os.path.sep)
1544 if x not in invalid_path_parts)
1545 if os.path.sep == '\\':
1546 # filter illegal characters on Windows
1547 arcname = self._sanitize_windows_name(arcname, os.path.sep)
1548
1549 targetpath = os.path.join(targetpath, arcname)
1550 targetpath = os.path.normpath(targetpath)
1551
1552 # Create all upper directories if necessary.
1553 upperdirs = os.path.dirname(targetpath)
1554 if upperdirs and not os.path.exists(upperdirs):
1555 os.makedirs(upperdirs)
1556
1557 if member.is_dir():
1558 if not os.path.isdir(targetpath):
1559 os.mkdir(targetpath)
1560 return targetpath
1561
1562 with self.open(member, pwd=pwd) as source, \
1563 open(targetpath, "wb") as target:
1564 shutil.copyfileobj(source, target)
1565
1566 return targetpath
1567
1568 def _writecheck(self, zinfo):
1569 """Check for errors before writing a file to the archive."""
1570 if zinfo.filename in self.NameToInfo:
1571 import warnings
1572 warnings.warn('Duplicate name: %r' % zinfo.filename, stacklevel=3)
1573 if self.mode not in ('w', 'x', 'a'):
1574 raise ValueError("write() requires mode 'w', 'x', or 'a'")
1575 if not self.fp:
1576 raise ValueError(
1577 "Attempt to write ZIP archive that was already closed")
1578 _check_compression(zinfo.compress_type)
1579 if not self._allowZip64:
1580 requires_zip64 = None
1581 if len(self.filelist) >= ZIP_FILECOUNT_LIMIT:
1582 requires_zip64 = "Files count"
1583 elif zinfo.file_size > ZIP64_LIMIT:
1584 requires_zip64 = "Filesize"
1585 elif zinfo.header_offset > ZIP64_LIMIT:
1586 requires_zip64 = "Zipfile size"
1587 if requires_zip64:
1588 raise LargeZipFile(requires_zip64 +
1589 " would require ZIP64 extensions")
1590
1591 def write(self, filename, arcname=None, compress_type=None):
1592 """Put the bytes from filename into the archive under the name
1593 arcname."""
1594 if not self.fp:
1595 raise ValueError(
1596 "Attempt to write to ZIP archive that was already closed")
1597 if self._writing:
1598 raise ValueError(
1599 "Can't write to ZIP archive while an open writing handle exists"
1600 )
1601
1602 zinfo = ZipInfo.from_file(filename, arcname)
1603
1604 if zinfo.is_dir():
1605 zinfo.compress_size = 0
1606 zinfo.CRC = 0
1607 else:
1608 if compress_type is not None:
1609 zinfo.compress_type = compress_type
1610 else:
1611 zinfo.compress_type = self.compression
1612
1613 if zinfo.is_dir():
1614 with self._lock:
1615 if self._seekable:
1616 self.fp.seek(self.start_dir)
1617 zinfo.header_offset = self.fp.tell() # Start of header bytes
1618 if zinfo.compress_type == ZIP_LZMA:
1619 # Compressed data includes an end-of-stream (EOS) marker
1620 zinfo.flag_bits |= 0x02
1621
1622 self._writecheck(zinfo)
1623 self._didModify = True
1624
1625 self.filelist.append(zinfo)
1626 self.NameToInfo[zinfo.filename] = zinfo
1627 self.fp.write(zinfo.FileHeader(False))
1628 self.start_dir = self.fp.tell()
1629 else:
1630 with open(filename, "rb") as src, self.open(zinfo, 'w') as dest:
1631 shutil.copyfileobj(src, dest, 1024*8)
1632
1633 def writestr(self, zinfo_or_arcname, data, compress_type=None):
1634 """Write a file into the archive. The contents is 'data', which
1635 may be either a 'str' or a 'bytes' instance; if it is a 'str',
1636 it is encoded as UTF-8 first.
1637 'zinfo_or_arcname' is either a ZipInfo instance or
1638 the name of the file in the archive."""
1639 if isinstance(data, str):
1640 data = data.encode("utf-8")
1641 if not isinstance(zinfo_or_arcname, ZipInfo):
1642 zinfo = ZipInfo(filename=zinfo_or_arcname,
1643 date_time=time.localtime(time.time())[:6])
1644 zinfo.compress_type = self.compression
1645 if zinfo.filename[-1] == '/':
1646 zinfo.external_attr = 0o40775 << 16 # drwxrwxr-x
1647 zinfo.external_attr |= 0x10 # MS-DOS directory flag
1648 else:
1649 zinfo.external_attr = 0o600 << 16 # ?rw-------
1650 else:
1651 zinfo = zinfo_or_arcname
1652
1653 if not self.fp:
1654 raise ValueError(
1655 "Attempt to write to ZIP archive that was already closed")
1656 if self._writing:
1657 raise ValueError(
1658 "Can't write to ZIP archive while an open writing handle exists."
1659 )
1660
1661 if compress_type is not None:
1662 zinfo.compress_type = compress_type
1663
1664 zinfo.file_size = len(data) # Uncompressed size
1665 with self._lock:
1666 with self.open(zinfo, mode='w') as dest:
1667 dest.write(data)
1668
1669 def __del__(self):
1670 """Call the "close()" method in case the user forgot."""
1671 self.close()
1672
1673 def close(self):
1674 """Close the file, and for mode 'w', 'x' and 'a' write the ending
1675 records."""
1676 if self.fp is None:
1677 return
1678
1679 if self._writing:
1680 raise ValueError("Can't close the ZIP file while there is "
1681 "an open writing handle on it. "
1682 "Close the writing handle before closing the zip.")
1683
1684 try:
1685 if self.mode in ('w', 'x', 'a') and self._didModify: # write ending records
1686 with self._lock:
1687 if self._seekable:
1688 self.fp.seek(self.start_dir)
1689 self._write_end_record()
1690 finally:
1691 fp = self.fp
1692 self.fp = None
1693 self._fpclose(fp)
1694
1695 def _write_end_record(self):
1696 for zinfo in self.filelist: # write central directory
1697 dt = zinfo.date_time
1698 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
1699 dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2)
1700 extra = []
1701 if zinfo.file_size > ZIP64_LIMIT \
1702 or zinfo.compress_size > ZIP64_LIMIT:
1703 extra.append(zinfo.file_size)
1704 extra.append(zinfo.compress_size)
1705 file_size = 0xffffffff
1706 compress_size = 0xffffffff
1707 else:
1708 file_size = zinfo.file_size
1709 compress_size = zinfo.compress_size
1710
1711 if zinfo.header_offset > ZIP64_LIMIT:
1712 extra.append(zinfo.header_offset)
1713 header_offset = 0xffffffff
1714 else:
1715 header_offset = zinfo.header_offset
1716
1717 extra_data = zinfo.extra
1718 min_version = 0
1719 if extra:
1720 # Append a ZIP64 field to the extra's
1721 extra_data = struct.pack(
1722 '<HH' + 'Q'*len(extra),
1723 1, 8*len(extra), *extra) + extra_data
1724
1725 min_version = ZIP64_VERSION
1726
1727 if zinfo.compress_type == ZIP_BZIP2:
1728 min_version = max(BZIP2_VERSION, min_version)
1729 elif zinfo.compress_type == ZIP_LZMA:
1730 min_version = max(LZMA_VERSION, min_version)
1731
1732 extract_version = max(min_version, zinfo.extract_version)
1733 create_version = max(min_version, zinfo.create_version)
1734 try:
1735 filename, flag_bits = zinfo._encodeFilenameFlags()
1736 centdir = struct.pack(structCentralDir,
1737 stringCentralDir, create_version,
1738 zinfo.create_system, extract_version, zinfo.reserved,
1739 flag_bits, zinfo.compress_type, dostime, dosdate,
1740 zinfo.CRC, compress_size, file_size,
1741 len(filename), len(extra_data), len(zinfo.comment),
1742 0, zinfo.internal_attr, zinfo.external_attr,
1743 header_offset)
1744 except DeprecationWarning:
1745 print((structCentralDir, stringCentralDir, create_version,
1746 zinfo.create_system, extract_version, zinfo.reserved,
1747 zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
1748 zinfo.CRC, compress_size, file_size,
1749 len(zinfo.filename), len(extra_data), len(zinfo.comment),
1750 0, zinfo.internal_attr, zinfo.external_attr,
1751 header_offset), file=sys.stderr)
1752 raise
1753 self.fp.write(centdir)
1754 self.fp.write(filename)
1755 self.fp.write(extra_data)
1756 self.fp.write(zinfo.comment)
1757
1758 pos2 = self.fp.tell()
1759 # Write end-of-zip-archive record
1760 centDirCount = len(self.filelist)
1761 centDirSize = pos2 - self.start_dir
1762 centDirOffset = self.start_dir
1763 requires_zip64 = None
1764 if centDirCount > ZIP_FILECOUNT_LIMIT:
1765 requires_zip64 = "Files count"
1766 elif centDirOffset > ZIP64_LIMIT:
1767 requires_zip64 = "Central directory offset"
1768 elif centDirSize > ZIP64_LIMIT:
1769 requires_zip64 = "Central directory size"
1770 if requires_zip64:
1771 # Need to write the ZIP64 end-of-archive records
1772 if not self._allowZip64:
1773 raise LargeZipFile(requires_zip64 +
1774 " would require ZIP64 extensions")
1775 zip64endrec = struct.pack(
1776 structEndArchive64, stringEndArchive64,
1777 44, 45, 45, 0, 0, centDirCount, centDirCount,
1778 centDirSize, centDirOffset)
1779 self.fp.write(zip64endrec)
1780
1781 zip64locrec = struct.pack(
1782 structEndArchive64Locator,
1783 stringEndArchive64Locator, 0, pos2, 1)
1784 self.fp.write(zip64locrec)
1785 centDirCount = min(centDirCount, 0xFFFF)
1786 centDirSize = min(centDirSize, 0xFFFFFFFF)
1787 centDirOffset = min(centDirOffset, 0xFFFFFFFF)
1788
1789 endrec = struct.pack(structEndArchive, stringEndArchive,
1790 0, 0, centDirCount, centDirCount,
1791 centDirSize, centDirOffset, len(self._comment))
1792 self.fp.write(endrec)
1793 self.fp.write(self._comment)
1794 self.fp.flush()
1795
1796 def _fpclose(self, fp):
1797 assert self._fileRefCnt > 0
1798 self._fileRefCnt -= 1
1799 if not self._fileRefCnt and not self._filePassed:
1800 fp.close()
1801
1802
1803 class PyZipFile(ZipFile):
1804 """Class to create ZIP archives with Python library files and packages."""
1805
1806 def __init__(self, file, mode="r", compression=ZIP_STORED,
1807 allowZip64=True, optimize=-1):
1808 ZipFile.__init__(self, file, mode=mode, compression=compression,
1809 allowZip64=allowZip64)
1810 self._optimize = optimize
1811
1812 def writepy(self, pathname, basename="", filterfunc=None):
1813 """Add all files from "pathname" to the ZIP archive.
1814
1815 If pathname is a package directory, search the directory and
1816 all package subdirectories recursively for all *.py and enter
1817 the modules into the archive. If pathname is a plain
1818 directory, listdir *.py and enter all modules. Else, pathname
1819 must be a Python *.py file and the module will be put into the
1820 archive. Added modules are always module.pyc.
1821 This method will compile the module.py into module.pyc if
1822 necessary.
1823 If filterfunc(pathname) is given, it is called with every argument.
1824 When it is False, the file or directory is skipped.
1825 """
1826 pathname = os.fspath(pathname)
1827 if filterfunc and not filterfunc(pathname):
1828 if self.debug:
1829 label = 'path' if os.path.isdir(pathname) else 'file'
1830 print('%s %r skipped by filterfunc' % (label, pathname))
1831 return
1832 dir, name = os.path.split(pathname)
1833 if os.path.isdir(pathname):
1834 initname = os.path.join(pathname, "__init__.py")
1835 if os.path.isfile(initname):
1836 # This is a package directory, add it
1837 if basename:
1838 basename = "%s/%s" % (basename, name)
1839 else:
1840 basename = name
1841 if self.debug:
1842 print("Adding package in", pathname, "as", basename)
1843 fname, arcname = self._get_codename(initname[0:-3], basename)
1844 if self.debug:
1845 print("Adding", arcname)
1846 self.write(fname, arcname)
1847 dirlist = os.listdir(pathname)
1848 dirlist.remove("__init__.py")
1849 # Add all *.py files and package subdirectories
1850 for filename in dirlist:
1851 path = os.path.join(pathname, filename)
1852 root, ext = os.path.splitext(filename)
1853 if os.path.isdir(path):
1854 if os.path.isfile(os.path.join(path, "__init__.py")):
1855 # This is a package directory, add it
1856 self.writepy(path, basename,
1857 filterfunc=filterfunc) # Recursive call
1858 elif ext == ".py":
1859 if filterfunc and not filterfunc(path):
1860 if self.debug:
1861 print('file %r skipped by filterfunc' % path)
1862 continue
1863 fname, arcname = self._get_codename(path[0:-3],
1864 basename)
1865 if self.debug:
1866 print("Adding", arcname)
1867 self.write(fname, arcname)
1868 else:
1869 # This is NOT a package directory, add its files at top level
1870 if self.debug:
1871 print("Adding files from directory", pathname)
1872 for filename in os.listdir(pathname):
1873 path = os.path.join(pathname, filename)
1874 root, ext = os.path.splitext(filename)
1875 if ext == ".py":
1876 if filterfunc and not filterfunc(path):
1877 if self.debug:
1878 print('file %r skipped by filterfunc' % path)
1879 continue
1880 fname, arcname = self._get_codename(path[0:-3],
1881 basename)
1882 if self.debug:
1883 print("Adding", arcname)
1884 self.write(fname, arcname)
1885 else:
1886 if pathname[-3:] != ".py":
1887 raise RuntimeError(
1888 'Files added with writepy() must end with ".py"')
1889 fname, arcname = self._get_codename(pathname[0:-3], basename)
1890 if self.debug:
1891 print("Adding file", arcname)
1892 self.write(fname, arcname)
1893
1894 def _get_codename(self, pathname, basename):
1895 """Return (filename, archivename) for the path.
1896
1897 Given a module name path, return the correct file path and
1898 archive name, compiling if necessary. For example, given
1899 /python/lib/string, return (/python/lib/string.pyc, string).
1900 """
1901 def _compile(file, optimize=-1):
1902 import py_compile
1903 if self.debug:
1904 print("Compiling", file)
1905 try:
1906 py_compile.compile(file, doraise=True, optimize=optimize)
1907 except py_compile.PyCompileError as err:
1908 print(err.msg)
1909 return False
1910 return True
1911
1912 file_py = pathname + ".py"
1913 file_pyc = pathname + ".pyc"
1914 pycache_opt0 = importlib.util.cache_from_source(file_py, optimization='')
1915 pycache_opt1 = importlib.util.cache_from_source(file_py, optimization=1)
1916 pycache_opt2 = importlib.util.cache_from_source(file_py, optimization=2)
1917 if self._optimize == -1:
1918 # legacy mode: use whatever file is present
1919 if (os.path.isfile(file_pyc) and
1920 os.stat(file_pyc).st_mtime >= os.stat(file_py).st_mtime):
1921 # Use .pyc file.
1922 arcname = fname = file_pyc
1923 elif (os.path.isfile(pycache_opt0) and
1924 os.stat(pycache_opt0).st_mtime >= os.stat(file_py).st_mtime):
1925 # Use the __pycache__/*.pyc file, but write it to the legacy pyc
1926 # file name in the archive.
1927 fname = pycache_opt0
1928 arcname = file_pyc
1929 elif (os.path.isfile(pycache_opt1) and
1930 os.stat(pycache_opt1).st_mtime >= os.stat(file_py).st_mtime):
1931 # Use the __pycache__/*.pyc file, but write it to the legacy pyc
1932 # file name in the archive.
1933 fname = pycache_opt1
1934 arcname = file_pyc
1935 elif (os.path.isfile(pycache_opt2) and
1936 os.stat(pycache_opt2).st_mtime >= os.stat(file_py).st_mtime):
1937 # Use the __pycache__/*.pyc file, but write it to the legacy pyc
1938 # file name in the archive.
1939 fname = pycache_opt2
1940 arcname = file_pyc
1941 else:
1942 # Compile py into PEP 3147 pyc file.
1943 if _compile(file_py):
1944 if sys.flags.optimize == 0:
1945 fname = pycache_opt0
1946 elif sys.flags.optimize == 1:
1947 fname = pycache_opt1
1948 else:
1949 fname = pycache_opt2
1950 arcname = file_pyc
1951 else:
1952 fname = arcname = file_py
1953 else:
1954 # new mode: use given optimization level
1955 if self._optimize == 0:
1956 fname = pycache_opt0
1957 arcname = file_pyc
1958 else:
1959 arcname = file_pyc
1960 if self._optimize == 1:
1961 fname = pycache_opt1
1962 elif self._optimize == 2:
1963 fname = pycache_opt2
1964 else:
1965 msg = "invalid value for 'optimize': {!r}".format(self._optimize)
1966 raise ValueError(msg)
1967 if not (os.path.isfile(fname) and
1968 os.stat(fname).st_mtime >= os.stat(file_py).st_mtime):
1969 if not _compile(file_py, optimize=self._optimize):
1970 fname = arcname = file_py
1971 archivename = os.path.split(arcname)[1]
1972 if basename:
1973 archivename = "%s/%s" % (basename, archivename)
1974 return (fname, archivename)
1975
1976
1977 def main(args = None):
1978 import textwrap
1979 USAGE=textwrap.dedent("""\
1980 Usage:
1981 zipfile.py -l zipfile.zip # Show listing of a zipfile
1982 zipfile.py -t zipfile.zip # Test if a zipfile is valid
1983 zipfile.py -e zipfile.zip target # Extract zipfile into target dir
1984 zipfile.py -c zipfile.zip src ... # Create zipfile from sources
1985 """)
1986 if args is None:
1987 args = sys.argv[1:]
1988
1989 if not args or args[0] not in ('-l', '-c', '-e', '-t'):
1990 print(USAGE)
1991 sys.exit(1)
1992
1993 if args[0] == '-l':
1994 if len(args) != 2:
1995 print(USAGE)
1996 sys.exit(1)
1997 with ZipFile(args[1], 'r') as zf:
1998 zf.printdir()
1999
2000 elif args[0] == '-t':
2001 if len(args) != 2:
2002 print(USAGE)
2003 sys.exit(1)
2004 with ZipFile(args[1], 'r') as zf:
2005 badfile = zf.testzip()
2006 if badfile:
2007 print("The following enclosed file is corrupted: {!r}".format(badfile))
2008 print("Done testing")
2009
2010 elif args[0] == '-e':
2011 if len(args) != 3:
2012 print(USAGE)
2013 sys.exit(1)
2014
2015 with ZipFile(args[1], 'r') as zf:
2016 zf.extractall(args[2])
2017
2018 elif args[0] == '-c':
2019 if len(args) < 3:
2020 print(USAGE)
2021 sys.exit(1)
2022
2023 def addToZip(zf, path, zippath):
2024 if os.path.isfile(path):
2025 zf.write(path, zippath, ZIP_DEFLATED)
2026 elif os.path.isdir(path):
2027 if zippath:
2028 zf.write(path, zippath)
2029 for nm in os.listdir(path):
2030 addToZip(zf,
2031 os.path.join(path, nm), os.path.join(zippath, nm))
2032 # else: ignore
2033
2034 with ZipFile(args[1], 'w') as zf:
2035 for path in args[2:]:
2036 zippath = os.path.basename(path)
2037 if not zippath:
2038 zippath = os.path.basename(os.path.dirname(path))
2039 if zippath in ('', os.curdir, os.pardir):
2040 zippath = ''
2041 addToZip(zf, path, zippath)
2042
2043 if __name__ == "__main__":
2044 main()
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!