~ubuntu-branches/ubuntu/trusty/boa-constructor/trusty

« back to all changes in this revision

Viewing changes to ExternalLib/zipfile.py

  • Committer: Bazaar Package Importer
  • Author(s): Cédric Delfosse
  • Date: 2007-01-23 21:32:29 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20070123213229-1d9lxp9c4dutjwv5
Add a .desktop file (Closes: #349081)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
"Read and write ZIP files"
2
 
# Written by James C. Ahlstrom jim@interet.com
3
 
# All rights transferred to CNRI pursuant to the Python contribution agreement
4
 
 
5
 
import struct, os, time
6
 
import binascii, py_compile
7
 
 
8
 
try:
9
 
    import zlib # We may need its compression method
10
 
    try:
11
 
        binascii.crc32
12
 
    except AttributeError:
13
 
        binascii.crc32 = zlib.crc32
14
 
except:
15
 
    zlib = None
16
 
 
17
 
class _BadZipfile(Exception):
18
 
    pass
19
 
error = _BadZipfile     # The exception raised by this module
20
 
 
21
 
# constants for Zip file compression methods
22
 
ZIP_STORED = 0
23
 
ZIP_DEFLATED = 8
24
 
# Other ZIP compression methods not supported
25
 
 
26
 
# Here are some struct module formats for reading headers
27
 
structEndArchive = "<4s4H2lH"     # 9 items, end of archive, 22 bytes
28
 
stringEndArchive = "PK\005\006"   # magic number for end of archive record
29
 
structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
30
 
stringCentralDir = "PK\001\002"   # magic number for central directory
31
 
structFileHeader = "<4s2B4H3l2H"  # 12 items, file header record, 30 bytes
32
 
stringFileHeader = "PK\003\004"   # magic number for file header
33
 
 
34
 
def is_zipfile(filename):
35
 
    """Quickly see if file is a ZIP file by checking the magic number.
36
 
 
37
 
Will not accept a ZIP archive with an ending comment."""
38
 
    try:
39
 
        fpin = open(filename, "rb")
40
 
        fpin.seek(-22, 2)               # Seek to end-of-file record
41
 
        endrec = fpin.read()
42
 
        fpin.close()
43
 
        if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
44
 
            return 1    # file has correct magic number
45
 
    except:
46
 
        pass
47
 
 
48
 
class ZipInfo:
49
 
    "Class with attributes describing each file in the ZIP archive"
50
 
    def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
51
 
        self.filename = filename        # Name of the file in the archive
52
 
        self.date_time = date_time      # year, month, day, hour, min, sec
53
 
        # Standard values:
54
 
        self.compress_type = ZIP_STORED # Type of compression for the file
55
 
        self.comment = ""               # Comment for each file
56
 
        self.extra = ""                 # ZIP extra data
57
 
        self.create_system = 0          # System which created ZIP archive
58
 
        self.create_version = 20        # Version which created ZIP archive
59
 
        self.extract_version = 20       # Version needed to extract archive
60
 
        self.reserved = 0               # Must be zero
61
 
        self.flag_bits = 0              # ZIP flag bits
62
 
        self.volume = 0                 # Volume number of file header
63
 
        self.internal_attr = 0          # Internal attributes
64
 
        self.external_attr = 0          # External file attributes
65
 
        # Other attributes are set by class ZipFile:
66
 
        # header_offset         Byte offset to the file header
67
 
        # file_offset           Byte offset to the start of the file data
68
 
        # CRC                   CRC-32 of the uncompressed file
69
 
        # compress_size         Size of the compressed file
70
 
        # file_size             Size of the uncompressed file
71
 
 
72
 
    def FileHeader(self):
73
 
        'Return the per-file header as a string'
74
 
        dt = self.date_time
75
 
        dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
76
 
        dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
77
 
        if self.flag_bits & 0x08:
78
 
            # Set these to zero because we write them after the file data
79
 
            CRC = compress_size = file_size = 0
80
 
        else:
81
 
            CRC = self.CRC
82
 
            compress_size = self.compress_size
83
 
            file_size = self.file_size
84
 
        header = struct.pack(structFileHeader, stringFileHeader,
85
 
                 self.extract_version, self.reserved, self.flag_bits,
86
 
                 self.compress_type, dostime, dosdate, CRC,
87
 
                 compress_size, file_size,
88
 
                 len(self.filename), len(self.extra))
89
 
        return header + self.filename + self.extra
90
 
 
91
 
 
92
 
class ZipFile:
93
 
    "Class with methods to open, read, write, close, list zip files"
94
 
    def __init__(self, filename, mode="r", compression=ZIP_STORED):
95
 
        'Open the ZIP file with mode read "r", write "w" or append "a".'
96
 
        if compression == ZIP_STORED:
97
 
            pass
98
 
        elif compression == ZIP_DEFLATED:
99
 
            if not zlib:
100
 
                raise RuntimeError,\
101
 
                "Compression requires the (missing) zlib module"
102
 
        else:
103
 
            raise RuntimeError, "That compression method is not supported"
104
 
        self.debug = 0  # Level of printing: 0 through 3
105
 
        self.NameToInfo = {}    # Find file info given name
106
 
        self.filelist = []      # List of ZipInfo instances for archive
107
 
        self.compression = compression  # Method of compression
108
 
        self.filename = filename
109
 
        self.mode = key = mode[0]
110
 
        if key == 'r':
111
 
            self.fp = open(filename, "rb")
112
 
            self._GetContents()
113
 
        elif key == 'w':
114
 
            self.fp = open(filename, "wb")
115
 
        elif key == 'a':
116
 
            fp = self.fp = open(filename, "r+b")
117
 
            fp.seek(-22, 2)             # Seek to end-of-file record
118
 
            endrec = fp.read()
119
 
            if endrec[0:4] == stringEndArchive and \
120
 
                       endrec[-2:] == "\000\000":
121
 
                self._GetContents()     # file is a zip file
122
 
                # seek to start of directory and overwrite
123
 
                fp.seek(self.start_dir, 0)
124
 
            else:               # file is not a zip file, just append
125
 
                fp.seek(0, 2)
126
 
        else:
127
 
            raise RuntimeError, 'Mode must be "r", "w" or "a"'
128
 
 
129
 
    def _GetContents(self):
130
 
        "Read in the table of contents for the zip file"
131
 
        fp = self.fp
132
 
        fp.seek(-22, 2)         # Start of end-of-archive record
133
 
        filesize = fp.tell() + 22       # Get file size
134
 
        endrec = fp.read(22)    # Archive must not end with a comment!
135
 
        if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
136
 
            raise error, "File is not a zip file, or ends with a comment"
137
 
        endrec = struct.unpack(structEndArchive, endrec)
138
 
        if self.debug > 1:
139
 
            print endrec
140
 
        size_cd = endrec[5]             # bytes in central directory
141
 
        offset_cd = endrec[6]   # offset of central directory
142
 
        x = filesize - 22 - size_cd
143
 
        # "concat" is zero, unless zip was concatenated to another file
144
 
        concat = x - offset_cd
145
 
        if self.debug > 2:
146
 
            print "given, inferred, offset", offset_cd, x, concat
147
 
        # self.start_dir:  Position of start of central directory
148
 
        self.start_dir = offset_cd + concat
149
 
        fp.seek(self.start_dir, 0)
150
 
        total = 0
151
 
        while total < size_cd:
152
 
            centdir = fp.read(46)
153
 
            total = total + 46
154
 
            if centdir[0:4] != stringCentralDir:
155
 
                raise error, "Bad magic number for central directory"
156
 
            centdir = struct.unpack(structCentralDir, centdir)
157
 
            if self.debug > 2:
158
 
                print centdir
159
 
            filename = fp.read(centdir[12])
160
 
            # Create ZipInfo instance to store file information
161
 
            x = ZipInfo(filename)
162
 
            x.extra = fp.read(centdir[13])
163
 
            x.comment = fp.read(centdir[14])
164
 
            total = total + centdir[12] + centdir[13] + centdir[14]
165
 
            x.header_offset = centdir[18] + concat
166
 
            x.file_offset = x.header_offset + 30 + centdir[12] + centdir[13]
167
 
            (x.create_version, x.create_system, x.extract_version, x.reserved,
168
 
                x.flag_bits, x.compress_type, t, d,
169
 
                x.CRC, x.compress_size, x.file_size) = centdir[1:12]
170
 
            x.volume, x.internal_attr, x.external_attr = centdir[15:18]
171
 
            # Convert date/time code to (year, month, day, hour, min, sec)
172
 
            x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
173
 
                                     t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
174
 
            self.filelist.append(x)
175
 
            self.NameToInfo[x.filename] = x
176
 
            if self.debug > 2:
177
 
                print "total", total
178
 
        for data in self.filelist:
179
 
            fp.seek(data.header_offset, 0)
180
 
            fheader = fp.read(30)
181
 
            if fheader[0:4] != stringFileHeader:
182
 
                raise error, "Bad magic number for file header"
183
 
            fheader = struct.unpack(structFileHeader, fheader)
184
 
            fname = fp.read(fheader[10])
185
 
            if fname != data.filename:
186
 
                raise RuntimeError, \
187
 
 'File name in Central Directory "%s" and File Header "%s" differ.' % (
188
 
                             data.filename, fname)
189
 
 
190
 
    def namelist(self):
191
 
        "Return a list of file names in the archive"
192
 
        l = []
193
 
        for data in self.filelist:
194
 
            l.append(data.filename)
195
 
        return l
196
 
 
197
 
    def infolist(self):
198
 
        "Return a list of class ZipInfo instances for files in the archive"
199
 
        return self.filelist
200
 
 
201
 
    def printdir(self):
202
 
        "Print a table of contents for the zip file"
203
 
        print "%-46s %19s %12s" % ("File Name", "Modified    ", "Size")
204
 
        for zinfo in self.filelist:
205
 
            date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
206
 
            print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
207
 
 
208
 
    def testzip(self):
209
 
        "Read all the files and check the CRC"
210
 
        for zinfo in self.filelist:
211
 
            try:
212
 
                self.read(zinfo.filename)       # Check CRC-32
213
 
            except:
214
 
                return zinfo.filename
215
 
 
216
 
    def getinfo(self, name):
217
 
        'Return the instance of ZipInfo given "name"'
218
 
        return self.NameToInfo[name]
219
 
 
220
 
    def read(self, name):
221
 
        "Return file bytes (as a string) for name"
222
 
        if self.mode not in ("r", "a"):
223
 
            raise RuntimeError, 'read() requires mode "r" or "a"'
224
 
        if not self.fp:
225
 
            raise RuntimeError, \
226
 
            "Attempt to read ZIP archive that was already closed"
227
 
        zinfo = self.getinfo(name)
228
 
        filepos = self.fp.tell()
229
 
        self.fp.seek(zinfo.file_offset, 0)
230
 
        bytes = self.fp.read(zinfo.compress_size)
231
 
        self.fp.seek(filepos, 0)
232
 
        if zinfo.compress_type == ZIP_STORED:
233
 
            pass
234
 
        elif zinfo.compress_type == ZIP_DEFLATED:
235
 
            if not zlib:
236
 
                raise RuntimeError, \
237
 
                "De-compression requires the (missing) zlib module"
238
 
            # zlib compress/decompress code by Jeremy Hylton of CNRI
239
 
            dc = zlib.decompressobj(-15)
240
 
            bytes = dc.decompress(bytes)
241
 
            # need to feed in unused pad byte so that zlib won't choke
242
 
            ex = dc.decompress('Z') + dc.flush()
243
 
            if ex:
244
 
                bytes = bytes + ex
245
 
        else:
246
 
            raise error, \
247
 
            "Unsupported compression method %d for file %s" % \
248
 
            (zinfo.compress_type, name)
249
 
        crc = binascii.crc32(bytes)
250
 
        if crc != zinfo.CRC:
251
 
            raise error, "Bad CRC-32 for file %s" % name
252
 
        return bytes
253
 
 
254
 
    def _writecheck(self, zinfo):
255
 
        'Check for errors before writing a file to the archive'
256
 
        if self.NameToInfo.has_key(zinfo.filename):
257
 
            if self.debug:      # Warning for duplicate names
258
 
                print "Duplicate name:", zinfo.filename
259
 
        if self.mode not in ("w", "a"):
260
 
            raise RuntimeError, 'write() requires mode "w" or "a"'
261
 
        if not self.fp:
262
 
            raise RuntimeError, \
263
 
            "Attempt to write ZIP archive that was already closed"
264
 
        if zinfo.compress_type == ZIP_DEFLATED and not zlib:
265
 
            raise RuntimeError, \
266
 
            "Compression requires the (missing) zlib module"
267
 
        if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
268
 
            raise RuntimeError, \
269
 
            "That compression method is not supported"
270
 
 
271
 
    def write(self, filename, arcname=None, compress_type=None):
272
 
        'Put the bytes from filename into the archive under the name arcname.'
273
 
        st = os.stat(filename)
274
 
        mtime = time.localtime(st[8])
275
 
        date_time = mtime[0:6]
276
 
        # Create ZipInfo instance to store file information
277
 
        if arcname is None:
278
 
            zinfo = ZipInfo(filename, date_time)
279
 
        else:
280
 
            zinfo = ZipInfo(arcname, date_time)
281
 
        zinfo.external_attr = st[0] << 16       # Unix attributes
282
 
        if compress_type is None:
283
 
            zinfo.compress_type = self.compression
284
 
        else:
285
 
            zinfo.compress_type = compress_type
286
 
        self._writecheck(zinfo)
287
 
        fp = open(filename, "rb")
288
 
        zinfo.flag_bits = 0x08
289
 
        zinfo.header_offset = self.fp.tell()    # Start of header bytes
290
 
        self.fp.write(zinfo.FileHeader())
291
 
        zinfo.file_offset = self.fp.tell()      # Start of file bytes
292
 
        CRC = 0
293
 
        compress_size = 0
294
 
        file_size = 0
295
 
        if zinfo.compress_type == ZIP_DEFLATED:
296
 
            cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
297
 
                 zlib.DEFLATED, -15)
298
 
        else:
299
 
            cmpr = None
300
 
        while 1:
301
 
            buf = fp.read(1024 * 8)
302
 
            if not buf:
303
 
                break
304
 
            file_size = file_size + len(buf)
305
 
            CRC = binascii.crc32(buf, CRC)
306
 
            if cmpr:
307
 
                buf = cmpr.compress(buf)
308
 
                compress_size = compress_size + len(buf)
309
 
            self.fp.write(buf)
310
 
        fp.close()
311
 
        if cmpr:
312
 
            buf = cmpr.flush()
313
 
            compress_size = compress_size + len(buf)
314
 
            self.fp.write(buf)
315
 
            zinfo.compress_size = compress_size
316
 
        else:
317
 
            zinfo.compress_size = file_size
318
 
        zinfo.CRC = CRC
319
 
        zinfo.file_size = file_size
320
 
        # Write CRC and file sizes after the file data
321
 
        self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
322
 
              zinfo.file_size))
323
 
        self.filelist.append(zinfo)
324
 
        self.NameToInfo[zinfo.filename] = zinfo
325
 
 
326
 
    def writestr(self, zinfo, bytes):
327
 
        'Write a file into the archive.  The contents is the string "bytes"'
328
 
        self._writecheck(zinfo)
329
 
        zinfo.file_size = len(bytes)            # Uncompressed size
330
 
        zinfo.CRC = binascii.crc32(bytes)       # CRC-32 checksum
331
 
        if zinfo.compress_type == ZIP_DEFLATED:
332
 
            co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
333
 
                 zlib.DEFLATED, -15)
334
 
            bytes = co.compress(bytes) + co.flush()
335
 
            zinfo.compress_size = len(bytes)    # Compressed size
336
 
        else:
337
 
            zinfo.compress_size = zinfo.file_size
338
 
        zinfo.header_offset = self.fp.tell()    # Start of header bytes
339
 
        self.fp.write(zinfo.FileHeader())
340
 
        zinfo.file_offset = self.fp.tell()      # Start of file bytes
341
 
        self.fp.write(bytes)
342
 
        if zinfo.flag_bits & 0x08:
343
 
            # Write CRC and file sizes after the file data
344
 
            self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
345
 
                  zinfo.file_size))
346
 
        self.filelist.append(zinfo)
347
 
        self.NameToInfo[zinfo.filename] = zinfo
348
 
 
349
 
    def __del__(self):
350
 
        'Call the "close()" method in case the user forgot'
351
 
        if self.fp:
352
 
            self.fp.close()
353
 
            self.fp = None
354
 
 
355
 
    def close(self):
356
 
        'Close the file, and for mode "w" and "a" write the ending records'
357
 
        if self.mode in ("w", "a"):             # write ending records
358
 
            count = 0
359
 
            pos1 = self.fp.tell()
360
 
            for zinfo in self.filelist:         # write central directory
361
 
                count = count + 1
362
 
                dt = zinfo.date_time
363
 
                dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
364
 
                dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
365
 
                centdir = struct.pack(structCentralDir,
366
 
                  stringCentralDir, zinfo.create_version,
367
 
                  zinfo.create_system, zinfo.extract_version, zinfo.reserved,
368
 
                  zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
369
 
                  zinfo.CRC, zinfo.compress_size, zinfo.file_size,
370
 
                  len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
371
 
                  0, zinfo.internal_attr, zinfo.external_attr,
372
 
                  zinfo.header_offset)
373
 
                self.fp.write(centdir)
374
 
                self.fp.write(zinfo.filename)
375
 
                self.fp.write(zinfo.extra)
376
 
                self.fp.write(zinfo.comment)
377
 
            pos2 = self.fp.tell()
378
 
            # Write end-of-zip-archive record
379
 
            endrec = struct.pack(structEndArchive, stringEndArchive,
380
 
                     0, 0, count, count, pos2 - pos1, pos1, 0)
381
 
            self.fp.write(endrec)
382
 
        self.fp.close()
383
 
        self.fp = None
384
 
 
385
 
 
386
 
class PyZipFile(ZipFile):
387
 
    "Class to create ZIP archives with Python library files and packages"
388
 
    def writepy(self, pathname, basename = ""):
389
 
        """Add all files from "pathname" to the ZIP archive.
390
 
 
391
 
If pathname is a package directory, search the directory and all
392
 
package subdirectories recursively for all *.py and enter the modules into
393
 
the archive.  If pathname is a plain directory, listdir *.py and enter all
394
 
modules.  Else, pathname must be a Python *.py file and the module will be
395
 
put into the archive.  Added modules are always module.pyo or module.pyc.
396
 
This method will compile the module.py into module.pyc if necessary."""
397
 
        dir, name = os.path.split(pathname)
398
 
        if os.path.isdir(pathname):
399
 
            initname = os.path.join(pathname, "__init__.py")
400
 
            if os.path.isfile(initname):
401
 
                # This is a package directory, add it
402
 
                if basename:
403
 
                    basename = "%s/%s" % (basename, name)
404
 
                else:
405
 
                    basename = name
406
 
                if self.debug:
407
 
                    print "Adding package in", pathname, "as", basename
408
 
                fname, arcname = self._get_codename(initname[0:-3], basename)
409
 
                if self.debug:
410
 
                    print "Adding", arcname
411
 
                self.write(fname, arcname)
412
 
                dirlist = os.listdir(pathname)
413
 
                dirlist.remove("__init__.py")
414
 
                # Add all *.py files and package subdirectories
415
 
                for filename in dirlist:
416
 
                    path = os.path.join(pathname, filename)
417
 
                    root, ext = os.path.splitext(filename)
418
 
                    if os.path.isdir(path):
419
 
                        if os.path.isfile(os.path.join(path, "__init__.py")):
420
 
                            # This is a package directory, add it
421
 
                            self.writepy(path, basename)  # Recursive call
422
 
                    elif ext == ".py":
423
 
                        fname, arcname = self._get_codename(path[0:-3],
424
 
                                         basename)
425
 
                        if self.debug:
426
 
                            print "Adding", arcname
427
 
                        self.write(fname, arcname)
428
 
            else:
429
 
                # This is NOT a package directory, add its files at top level
430
 
                if self.debug:
431
 
                    print "Adding files from directory", pathname
432
 
                for filename in os.listdir(pathname):
433
 
                    path = os.path.join(pathname, filename)
434
 
                    root, ext = os.path.splitext(filename)
435
 
                    if ext == ".py":
436
 
                        fname, arcname = self._get_codename(path[0:-3],
437
 
                                         basename)
438
 
                        if self.debug:
439
 
                            print "Adding", arcname
440
 
                        self.write(fname, arcname)
441
 
        else:
442
 
            if pathname[-3:] != ".py":
443
 
                raise RuntimeError, \
444
 
                'Files added with writepy() must end with ".py"'
445
 
            fname, arcname = self._get_codename(pathname[0:-3], basename)
446
 
            if self.debug:
447
 
                print "Adding file", arcname
448
 
            self.write(fname, arcname)
449
 
 
450
 
    def _get_codename(self, pathname, basename):
451
 
        """Return (filename, archivename) for the path.
452
 
 
453
 
Given a module name path, return the correct file path and archive name,
454
 
compiling if necessary.  For example, given /python/lib/string,
455
 
return (/python/lib/string.pyc, string)"""
456
 
        file_py  = pathname + ".py"
457
 
        file_pyc = pathname + ".pyc"
458
 
        file_pyo = pathname + ".pyo"
459
 
        if os.path.isfile(file_pyo) and \
460
 
                            os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
461
 
            fname = file_pyo    # Use .pyo file
462
 
        elif not os.path.isfile(file_pyc) or \
463
 
                            os.stat(file_pyc)[8] < os.stat(file_py)[8]:
464
 
            if self.debug:
465
 
                print "Compiling", file_py
466
 
            py_compile.compile(file_py, file_pyc)
467
 
            fname = file_pyc
468
 
        else:
469
 
            fname = file_pyc
470
 
        archivename = os.path.split(fname)[1]
471
 
        if basename:
472
 
            archivename = "%s/%s" % (basename, archivename)
473
 
        return (fname, archivename)
 
1
"Read and write ZIP files"
 
2
# Written by James C. Ahlstrom jim@interet.com
 
3
# All rights transferred to CNRI pursuant to the Python contribution agreement
 
4
 
 
5
import struct, os, time
 
6
import binascii, py_compile
 
7
 
 
8
try:
 
9
    import zlib # We may need its compression method
 
10
    try:
 
11
        binascii.crc32
 
12
    except AttributeError:
 
13
        binascii.crc32 = zlib.crc32
 
14
except:
 
15
    zlib = None
 
16
 
 
17
class _BadZipfile(Exception):
 
18
    pass
 
19
error = _BadZipfile     # The exception raised by this module
 
20
 
 
21
# constants for Zip file compression methods
 
22
ZIP_STORED = 0
 
23
ZIP_DEFLATED = 8
 
24
# Other ZIP compression methods not supported
 
25
 
 
26
# Here are some struct module formats for reading headers
 
27
structEndArchive = "<4s4H2lH"     # 9 items, end of archive, 22 bytes
 
28
stringEndArchive = "PK\005\006"   # magic number for end of archive record
 
29
structCentralDir = "<4s4B4H3l5H2l"# 19 items, central directory, 46 bytes
 
30
stringCentralDir = "PK\001\002"   # magic number for central directory
 
31
structFileHeader = "<4s2B4H3l2H"  # 12 items, file header record, 30 bytes
 
32
stringFileHeader = "PK\003\004"   # magic number for file header
 
33
 
 
34
def is_zipfile(filename):
 
35
    """Quickly see if file is a ZIP file by checking the magic number.
 
36
 
 
37
Will not accept a ZIP archive with an ending comment."""
 
38
    try:
 
39
        fpin = open(filename, "rb")
 
40
        fpin.seek(-22, 2)               # Seek to end-of-file record
 
41
        endrec = fpin.read()
 
42
        fpin.close()
 
43
        if endrec[0:4] == "PK\005\006" and endrec[-2:] == "\000\000":
 
44
            return 1    # file has correct magic number
 
45
    except:
 
46
        pass
 
47
 
 
48
class ZipInfo:
 
49
    "Class with attributes describing each file in the ZIP archive"
 
50
    def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)):
 
51
        self.filename = filename        # Name of the file in the archive
 
52
        self.date_time = date_time      # year, month, day, hour, min, sec
 
53
        # Standard values:
 
54
        self.compress_type = ZIP_STORED # Type of compression for the file
 
55
        self.comment = ""               # Comment for each file
 
56
        self.extra = ""                 # ZIP extra data
 
57
        self.create_system = 0          # System which created ZIP archive
 
58
        self.create_version = 20        # Version which created ZIP archive
 
59
        self.extract_version = 20       # Version needed to extract archive
 
60
        self.reserved = 0               # Must be zero
 
61
        self.flag_bits = 0              # ZIP flag bits
 
62
        self.volume = 0                 # Volume number of file header
 
63
        self.internal_attr = 0          # Internal attributes
 
64
        self.external_attr = 0          # External file attributes
 
65
        # Other attributes are set by class ZipFile:
 
66
        # header_offset         Byte offset to the file header
 
67
        # file_offset           Byte offset to the start of the file data
 
68
        # CRC                   CRC-32 of the uncompressed file
 
69
        # compress_size         Size of the compressed file
 
70
        # file_size             Size of the uncompressed file
 
71
 
 
72
    def FileHeader(self):
 
73
        'Return the per-file header as a string'
 
74
        dt = self.date_time
 
75
        dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
 
76
        dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
 
77
        if self.flag_bits & 0x08:
 
78
            # Set these to zero because we write them after the file data
 
79
            CRC = compress_size = file_size = 0
 
80
        else:
 
81
            CRC = self.CRC
 
82
            compress_size = self.compress_size
 
83
            file_size = self.file_size
 
84
        header = struct.pack(structFileHeader, stringFileHeader,
 
85
                 self.extract_version, self.reserved, self.flag_bits,
 
86
                 self.compress_type, dostime, dosdate, CRC,
 
87
                 compress_size, file_size,
 
88
                 len(self.filename), len(self.extra))
 
89
        return header + self.filename + self.extra
 
90
 
 
91
 
 
92
class ZipFile:
 
93
    "Class with methods to open, read, write, close, list zip files"
 
94
    def __init__(self, filename, mode="r", compression=ZIP_STORED):
 
95
        'Open the ZIP file with mode read "r", write "w" or append "a".'
 
96
        if compression == ZIP_STORED:
 
97
            pass
 
98
        elif compression == ZIP_DEFLATED:
 
99
            if not zlib:
 
100
                raise RuntimeError,\
 
101
                "Compression requires the (missing) zlib module"
 
102
        else:
 
103
            raise RuntimeError, "That compression method is not supported"
 
104
        self.debug = 0  # Level of printing: 0 through 3
 
105
        self.NameToInfo = {}    # Find file info given name
 
106
        self.filelist = []      # List of ZipInfo instances for archive
 
107
        self.compression = compression  # Method of compression
 
108
        self.filename = filename
 
109
        self.mode = key = mode[0]
 
110
        if key == 'r':
 
111
            self.fp = open(filename, "rb")
 
112
            self._GetContents()
 
113
        elif key == 'w':
 
114
            self.fp = open(filename, "wb")
 
115
        elif key == 'a':
 
116
            fp = self.fp = open(filename, "r+b")
 
117
            fp.seek(-22, 2)             # Seek to end-of-file record
 
118
            endrec = fp.read()
 
119
            if endrec[0:4] == stringEndArchive and \
 
120
                       endrec[-2:] == "\000\000":
 
121
                self._GetContents()     # file is a zip file
 
122
                # seek to start of directory and overwrite
 
123
                fp.seek(self.start_dir, 0)
 
124
            else:               # file is not a zip file, just append
 
125
                fp.seek(0, 2)
 
126
        else:
 
127
            raise RuntimeError, 'Mode must be "r", "w" or "a"'
 
128
 
 
129
    def _GetContents(self):
 
130
        "Read in the table of contents for the zip file"
 
131
        fp = self.fp
 
132
        fp.seek(-22, 2)         # Start of end-of-archive record
 
133
        filesize = fp.tell() + 22       # Get file size
 
134
        endrec = fp.read(22)    # Archive must not end with a comment!
 
135
        if endrec[0:4] != stringEndArchive or endrec[-2:] != "\000\000":
 
136
            raise error, "File is not a zip file, or ends with a comment"
 
137
        endrec = struct.unpack(structEndArchive, endrec)
 
138
        if self.debug > 1:
 
139
            print endrec
 
140
        size_cd = endrec[5]             # bytes in central directory
 
141
        offset_cd = endrec[6]   # offset of central directory
 
142
        x = filesize - 22 - size_cd
 
143
        # "concat" is zero, unless zip was concatenated to another file
 
144
        concat = x - offset_cd
 
145
        if self.debug > 2:
 
146
            print "given, inferred, offset", offset_cd, x, concat
 
147
        # self.start_dir:  Position of start of central directory
 
148
        self.start_dir = offset_cd + concat
 
149
        fp.seek(self.start_dir, 0)
 
150
        total = 0
 
151
        while total < size_cd:
 
152
            centdir = fp.read(46)
 
153
            total = total + 46
 
154
            if centdir[0:4] != stringCentralDir:
 
155
                raise error, "Bad magic number for central directory"
 
156
            centdir = struct.unpack(structCentralDir, centdir)
 
157
            if self.debug > 2:
 
158
                print centdir
 
159
            filename = fp.read(centdir[12])
 
160
            # Create ZipInfo instance to store file information
 
161
            x = ZipInfo(filename)
 
162
            x.extra = fp.read(centdir[13])
 
163
            x.comment = fp.read(centdir[14])
 
164
            total = total + centdir[12] + centdir[13] + centdir[14]
 
165
            x.header_offset = centdir[18] + concat
 
166
            x.file_offset = x.header_offset + 30 + centdir[12] + centdir[13]
 
167
            (x.create_version, x.create_system, x.extract_version, x.reserved,
 
168
                x.flag_bits, x.compress_type, t, d,
 
169
                x.CRC, x.compress_size, x.file_size) = centdir[1:12]
 
170
            x.volume, x.internal_attr, x.external_attr = centdir[15:18]
 
171
            # Convert date/time code to (year, month, day, hour, min, sec)
 
172
            x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F,
 
173
                                     t>>11, (t>>5)&0x3F, (t&0x1F) * 2 )
 
174
            self.filelist.append(x)
 
175
            self.NameToInfo[x.filename] = x
 
176
            if self.debug > 2:
 
177
                print "total", total
 
178
        for data in self.filelist:
 
179
            fp.seek(data.header_offset, 0)
 
180
            fheader = fp.read(30)
 
181
            if fheader[0:4] != stringFileHeader:
 
182
                raise error, "Bad magic number for file header"
 
183
            fheader = struct.unpack(structFileHeader, fheader)
 
184
            fname = fp.read(fheader[10])
 
185
            if fname != data.filename:
 
186
                raise RuntimeError, \
 
187
 'File name in Central Directory "%s" and File Header "%s" differ.' % (
 
188
                             data.filename, fname)
 
189
 
 
190
    def namelist(self):
 
191
        "Return a list of file names in the archive"
 
192
        l = []
 
193
        for data in self.filelist:
 
194
            l.append(data.filename)
 
195
        return l
 
196
 
 
197
    def infolist(self):
 
198
        "Return a list of class ZipInfo instances for files in the archive"
 
199
        return self.filelist
 
200
 
 
201
    def printdir(self):
 
202
        "Print a table of contents for the zip file"
 
203
        print "%-46s %19s %12s" % ("File Name", "Modified    ", "Size")
 
204
        for zinfo in self.filelist:
 
205
            date = "%d-%02d-%02d %02d:%02d:%02d" % zinfo.date_time
 
206
            print "%-46s %s %12d" % (zinfo.filename, date, zinfo.file_size)
 
207
 
 
208
    def testzip(self):
 
209
        "Read all the files and check the CRC"
 
210
        for zinfo in self.filelist:
 
211
            try:
 
212
                self.read(zinfo.filename)       # Check CRC-32
 
213
            except:
 
214
                return zinfo.filename
 
215
 
 
216
    def getinfo(self, name):
 
217
        'Return the instance of ZipInfo given "name"'
 
218
        return self.NameToInfo[name]
 
219
 
 
220
    def read(self, name):
 
221
        "Return file bytes (as a string) for name"
 
222
        if self.mode not in ("r", "a"):
 
223
            raise RuntimeError, 'read() requires mode "r" or "a"'
 
224
        if not self.fp:
 
225
            raise RuntimeError, \
 
226
            "Attempt to read ZIP archive that was already closed"
 
227
        zinfo = self.getinfo(name)
 
228
        filepos = self.fp.tell()
 
229
        self.fp.seek(zinfo.file_offset, 0)
 
230
        bytes = self.fp.read(zinfo.compress_size)
 
231
        self.fp.seek(filepos, 0)
 
232
        if zinfo.compress_type == ZIP_STORED:
 
233
            pass
 
234
        elif zinfo.compress_type == ZIP_DEFLATED:
 
235
            if not zlib:
 
236
                raise RuntimeError, \
 
237
                "De-compression requires the (missing) zlib module"
 
238
            # zlib compress/decompress code by Jeremy Hylton of CNRI
 
239
            dc = zlib.decompressobj(-15)
 
240
            bytes = dc.decompress(bytes)
 
241
            # need to feed in unused pad byte so that zlib won't choke
 
242
            ex = dc.decompress('Z') + dc.flush()
 
243
            if ex:
 
244
                bytes = bytes + ex
 
245
        else:
 
246
            raise error, \
 
247
            "Unsupported compression method %d for file %s" % \
 
248
            (zinfo.compress_type, name)
 
249
        crc = binascii.crc32(bytes)
 
250
        if crc != zinfo.CRC:
 
251
            raise error, "Bad CRC-32 for file %s" % name
 
252
        return bytes
 
253
 
 
254
    def _writecheck(self, zinfo):
 
255
        'Check for errors before writing a file to the archive'
 
256
        if self.NameToInfo.has_key(zinfo.filename):
 
257
            if self.debug:      # Warning for duplicate names
 
258
                print "Duplicate name:", zinfo.filename
 
259
        if self.mode not in ("w", "a"):
 
260
            raise RuntimeError, 'write() requires mode "w" or "a"'
 
261
        if not self.fp:
 
262
            raise RuntimeError, \
 
263
            "Attempt to write ZIP archive that was already closed"
 
264
        if zinfo.compress_type == ZIP_DEFLATED and not zlib:
 
265
            raise RuntimeError, \
 
266
            "Compression requires the (missing) zlib module"
 
267
        if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED):
 
268
            raise RuntimeError, \
 
269
            "That compression method is not supported"
 
270
 
 
271
    def write(self, filename, arcname=None, compress_type=None):
 
272
        'Put the bytes from filename into the archive under the name arcname.'
 
273
        st = os.stat(filename)
 
274
        mtime = time.localtime(st[8])
 
275
        date_time = mtime[0:6]
 
276
        # Create ZipInfo instance to store file information
 
277
        if arcname is None:
 
278
            zinfo = ZipInfo(filename, date_time)
 
279
        else:
 
280
            zinfo = ZipInfo(arcname, date_time)
 
281
        zinfo.external_attr = st[0] << 16       # Unix attributes
 
282
        if compress_type is None:
 
283
            zinfo.compress_type = self.compression
 
284
        else:
 
285
            zinfo.compress_type = compress_type
 
286
        self._writecheck(zinfo)
 
287
        fp = open(filename, "rb")
 
288
        zinfo.flag_bits = 0x08
 
289
        zinfo.header_offset = self.fp.tell()    # Start of header bytes
 
290
        self.fp.write(zinfo.FileHeader())
 
291
        zinfo.file_offset = self.fp.tell()      # Start of file bytes
 
292
        CRC = 0
 
293
        compress_size = 0
 
294
        file_size = 0
 
295
        if zinfo.compress_type == ZIP_DEFLATED:
 
296
            cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
 
297
                 zlib.DEFLATED, -15)
 
298
        else:
 
299
            cmpr = None
 
300
        while 1:
 
301
            buf = fp.read(1024 * 8)
 
302
            if not buf:
 
303
                break
 
304
            file_size = file_size + len(buf)
 
305
            CRC = binascii.crc32(buf, CRC)
 
306
            if cmpr:
 
307
                buf = cmpr.compress(buf)
 
308
                compress_size = compress_size + len(buf)
 
309
            self.fp.write(buf)
 
310
        fp.close()
 
311
        if cmpr:
 
312
            buf = cmpr.flush()
 
313
            compress_size = compress_size + len(buf)
 
314
            self.fp.write(buf)
 
315
            zinfo.compress_size = compress_size
 
316
        else:
 
317
            zinfo.compress_size = file_size
 
318
        zinfo.CRC = CRC
 
319
        zinfo.file_size = file_size
 
320
        # Write CRC and file sizes after the file data
 
321
        self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
 
322
              zinfo.file_size))
 
323
        self.filelist.append(zinfo)
 
324
        self.NameToInfo[zinfo.filename] = zinfo
 
325
 
 
326
    def writestr(self, zinfo, bytes):
 
327
        'Write a file into the archive.  The contents is the string "bytes"'
 
328
        self._writecheck(zinfo)
 
329
        zinfo.file_size = len(bytes)            # Uncompressed size
 
330
        zinfo.CRC = binascii.crc32(bytes)       # CRC-32 checksum
 
331
        if zinfo.compress_type == ZIP_DEFLATED:
 
332
            co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION,
 
333
                 zlib.DEFLATED, -15)
 
334
            bytes = co.compress(bytes) + co.flush()
 
335
            zinfo.compress_size = len(bytes)    # Compressed size
 
336
        else:
 
337
            zinfo.compress_size = zinfo.file_size
 
338
        zinfo.header_offset = self.fp.tell()    # Start of header bytes
 
339
        self.fp.write(zinfo.FileHeader())
 
340
        zinfo.file_offset = self.fp.tell()      # Start of file bytes
 
341
        self.fp.write(bytes)
 
342
        if zinfo.flag_bits & 0x08:
 
343
            # Write CRC and file sizes after the file data
 
344
            self.fp.write(struct.pack("<lll", zinfo.CRC, zinfo.compress_size,
 
345
                  zinfo.file_size))
 
346
        self.filelist.append(zinfo)
 
347
        self.NameToInfo[zinfo.filename] = zinfo
 
348
 
 
349
    def __del__(self):
 
350
        'Call the "close()" method in case the user forgot'
 
351
        if self.fp:
 
352
            self.fp.close()
 
353
            self.fp = None
 
354
 
 
355
    def close(self):
 
356
        'Close the file, and for mode "w" and "a" write the ending records'
 
357
        if self.mode in ("w", "a"):             # write ending records
 
358
            count = 0
 
359
            pos1 = self.fp.tell()
 
360
            for zinfo in self.filelist:         # write central directory
 
361
                count = count + 1
 
362
                dt = zinfo.date_time
 
363
                dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2]
 
364
                dostime = dt[3] << 11 | dt[4] << 5 | dt[5] / 2
 
365
                centdir = struct.pack(structCentralDir,
 
366
                  stringCentralDir, zinfo.create_version,
 
367
                  zinfo.create_system, zinfo.extract_version, zinfo.reserved,
 
368
                  zinfo.flag_bits, zinfo.compress_type, dostime, dosdate,
 
369
                  zinfo.CRC, zinfo.compress_size, zinfo.file_size,
 
370
                  len(zinfo.filename), len(zinfo.extra), len(zinfo.comment),
 
371
                  0, zinfo.internal_attr, zinfo.external_attr,
 
372
                  zinfo.header_offset)
 
373
                self.fp.write(centdir)
 
374
                self.fp.write(zinfo.filename)
 
375
                self.fp.write(zinfo.extra)
 
376
                self.fp.write(zinfo.comment)
 
377
            pos2 = self.fp.tell()
 
378
            # Write end-of-zip-archive record
 
379
            endrec = struct.pack(structEndArchive, stringEndArchive,
 
380
                     0, 0, count, count, pos2 - pos1, pos1, 0)
 
381
            self.fp.write(endrec)
 
382
        self.fp.close()
 
383
        self.fp = None
 
384
 
 
385
 
 
386
class PyZipFile(ZipFile):
 
387
    "Class to create ZIP archives with Python library files and packages"
 
388
    def writepy(self, pathname, basename = ""):
 
389
        """Add all files from "pathname" to the ZIP archive.
 
390
 
 
391
If pathname is a package directory, search the directory and all
 
392
package subdirectories recursively for all *.py and enter the modules into
 
393
the archive.  If pathname is a plain directory, listdir *.py and enter all
 
394
modules.  Else, pathname must be a Python *.py file and the module will be
 
395
put into the archive.  Added modules are always module.pyo or module.pyc.
 
396
This method will compile the module.py into module.pyc if necessary."""
 
397
        dir, name = os.path.split(pathname)
 
398
        if os.path.isdir(pathname):
 
399
            initname = os.path.join(pathname, "__init__.py")
 
400
            if os.path.isfile(initname):
 
401
                # This is a package directory, add it
 
402
                if basename:
 
403
                    basename = "%s/%s" % (basename, name)
 
404
                else:
 
405
                    basename = name
 
406
                if self.debug:
 
407
                    print "Adding package in", pathname, "as", basename
 
408
                fname, arcname = self._get_codename(initname[0:-3], basename)
 
409
                if self.debug:
 
410
                    print "Adding", arcname
 
411
                self.write(fname, arcname)
 
412
                dirlist = os.listdir(pathname)
 
413
                dirlist.remove("__init__.py")
 
414
                # Add all *.py files and package subdirectories
 
415
                for filename in dirlist:
 
416
                    path = os.path.join(pathname, filename)
 
417
                    root, ext = os.path.splitext(filename)
 
418
                    if os.path.isdir(path):
 
419
                        if os.path.isfile(os.path.join(path, "__init__.py")):
 
420
                            # This is a package directory, add it
 
421
                            self.writepy(path, basename)  # Recursive call
 
422
                    elif ext == ".py":
 
423
                        fname, arcname = self._get_codename(path[0:-3],
 
424
                                         basename)
 
425
                        if self.debug:
 
426
                            print "Adding", arcname
 
427
                        self.write(fname, arcname)
 
428
            else:
 
429
                # This is NOT a package directory, add its files at top level
 
430
                if self.debug:
 
431
                    print "Adding files from directory", pathname
 
432
                for filename in os.listdir(pathname):
 
433
                    path = os.path.join(pathname, filename)
 
434
                    root, ext = os.path.splitext(filename)
 
435
                    if ext == ".py":
 
436
                        fname, arcname = self._get_codename(path[0:-3],
 
437
                                         basename)
 
438
                        if self.debug:
 
439
                            print "Adding", arcname
 
440
                        self.write(fname, arcname)
 
441
        else:
 
442
            if pathname[-3:] != ".py":
 
443
                raise RuntimeError, \
 
444
                'Files added with writepy() must end with ".py"'
 
445
            fname, arcname = self._get_codename(pathname[0:-3], basename)
 
446
            if self.debug:
 
447
                print "Adding file", arcname
 
448
            self.write(fname, arcname)
 
449
 
 
450
    def _get_codename(self, pathname, basename):
 
451
        """Return (filename, archivename) for the path.
 
452
 
 
453
Given a module name path, return the correct file path and archive name,
 
454
compiling if necessary.  For example, given /python/lib/string,
 
455
return (/python/lib/string.pyc, string)"""
 
456
        file_py  = pathname + ".py"
 
457
        file_pyc = pathname + ".pyc"
 
458
        file_pyo = pathname + ".pyo"
 
459
        if os.path.isfile(file_pyo) and \
 
460
                            os.stat(file_pyo)[8] >= os.stat(file_py)[8]:
 
461
            fname = file_pyo    # Use .pyo file
 
462
        elif not os.path.isfile(file_pyc) or \
 
463
                            os.stat(file_pyc)[8] < os.stat(file_py)[8]:
 
464
            if self.debug:
 
465
                print "Compiling", file_py
 
466
            py_compile.compile(file_py, file_pyc)
 
467
            fname = file_pyc
 
468
        else:
 
469
            fname = file_pyc
 
470
        archivename = os.path.split(fname)[1]
 
471
        if basename:
 
472
            archivename = "%s/%s" % (basename, archivename)
 
473
        return (fname, archivename)