~ubuntu-branches/ubuntu/wily/python-imaging/wily

« back to all changes in this revision

Viewing changes to .pc/git-updates.diff/PIL/OleFileIO.py

  • Committer: Package Import Robot
  • Author(s): Matthias Klose
  • Date: 2013-01-31 20:49:20 UTC
  • mfrom: (27.1.1 raring-proposed)
  • Revision ID: package-import@ubuntu.com-20130131204920-b5zshy6vgfvdionl
Tags: 1.1.7+1.7.8-1ubuntu1
Rewrite build dependencies to allow cross builds.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# THIS IS WORK IN PROGRESS
 
3
#
 
4
# The Python Imaging Library
 
5
# $Id$
 
6
#
 
7
# stuff to deal with OLE2 Structured Storage files.  this module is
 
8
# used by PIL to read Image Composer and FlashPix files, but can also
 
9
# be used to read other files of this type.
 
10
#
 
11
# History:
 
12
# 1997-01-20 fl   Created
 
13
# 1997-01-22 fl   Fixed 64-bit portability quirk
 
14
# 2003-09-09 fl   Fixed typo in OleFileIO.loadfat (noted by Daniel Haertle)
 
15
# 2004-02-29 fl   Changed long hex constants to signed integers
 
16
#
 
17
# Notes:
 
18
# FIXME: sort out sign problem (eliminate long hex constants)
 
19
# FIXME: change filename to use "a/b/c" instead of ["a", "b", "c"]
 
20
# FIXME: provide a glob mechanism function (using fnmatchcase)
 
21
#
 
22
# Literature:
 
23
#
 
24
# "FlashPix Format Specification, Appendix A", Kodak and Microsoft,
 
25
#  September 1996.
 
26
#
 
27
# Quotes:
 
28
#
 
29
# "If this document and functionality of the Software conflict,
 
30
#  the actual functionality of the Software represents the correct
 
31
#  functionality" -- Microsoft, in the OLE format specification
 
32
#
 
33
# Copyright (c) Secret Labs AB 1997.
 
34
# Copyright (c) Fredrik Lundh 1997.
 
35
#
 
36
# See the README file for information on usage and redistribution.
 
37
#
 
38
 
 
39
import string, StringIO
 
40
 
 
41
 
 
42
def i16(c, o = 0):
 
43
    return ord(c[o])+(ord(c[o+1])<<8)
 
44
 
 
45
def i32(c, o = 0):
 
46
    return ord(c[o])+(ord(c[o+1])<<8)+(ord(c[o+2])<<16)+(ord(c[o+3])<<24)
 
47
 
 
48
 
 
49
MAGIC = '\320\317\021\340\241\261\032\341'
 
50
 
 
51
#
 
52
# --------------------------------------------------------------------
 
53
# property types
 
54
 
 
55
VT_EMPTY=0; VT_NULL=1; VT_I2=2; VT_I4=3; VT_R4=4; VT_R8=5; VT_CY=6;
 
56
VT_DATE=7; VT_BSTR=8; VT_DISPATCH=9; VT_ERROR=10; VT_BOOL=11;
 
57
VT_VARIANT=12; VT_UNKNOWN=13; VT_DECIMAL=14; VT_I1=16; VT_UI1=17;
 
58
VT_UI2=18; VT_UI4=19; VT_I8=20; VT_UI8=21; VT_INT=22; VT_UINT=23;
 
59
VT_VOID=24; VT_HRESULT=25; VT_PTR=26; VT_SAFEARRAY=27; VT_CARRAY=28;
 
60
VT_USERDEFINED=29; VT_LPSTR=30; VT_LPWSTR=31; VT_FILETIME=64;
 
61
VT_BLOB=65; VT_STREAM=66; VT_STORAGE=67; VT_STREAMED_OBJECT=68;
 
62
VT_STORED_OBJECT=69; VT_BLOB_OBJECT=70; VT_CF=71; VT_CLSID=72;
 
63
VT_VECTOR=0x1000;
 
64
 
 
65
# map property id to name (for debugging purposes)
 
66
 
 
67
VT = {}
 
68
for k, v in vars().items():
 
69
    if k[:3] == "VT_":
 
70
        VT[v] = k
 
71
 
 
72
#
 
73
# --------------------------------------------------------------------
 
74
# Some common document types (root.clsid fields)
 
75
 
 
76
WORD_CLSID = "00020900-0000-0000-C000-000000000046"
 
77
 
 
78
 
 
79
#
 
80
# --------------------------------------------------------------------
 
81
 
 
82
class _OleStream(StringIO.StringIO):
 
83
 
 
84
    """OLE2 Stream
 
85
 
 
86
    Returns a read-only file object which can be used to read
 
87
    the contents of a OLE stream.  To open a stream, use the
 
88
    openstream method in the OleFile class.
 
89
 
 
90
    This function can be used with either ordinary streams,
 
91
    or ministreams, depending on the offset, sectorsize, and
 
92
    fat table arguments.
 
93
    """
 
94
 
 
95
    # FIXME: should store the list of sects obtained by following
 
96
    # the fat chain, and load new sectors on demand instead of
 
97
    # loading it all in one go.
 
98
 
 
99
    def __init__(self, fp, sect, size, offset, sectorsize, fat):
 
100
 
 
101
        data = []
 
102
 
 
103
        while sect != -2: # 0xFFFFFFFEL:
 
104
            fp.seek(offset + sectorsize * sect)
 
105
            data.append(fp.read(sectorsize))
 
106
            sect = fat[sect]
 
107
 
 
108
        data = string.join(data, "")
 
109
 
 
110
        # print len(data), size
 
111
 
 
112
        StringIO.StringIO.__init__(self, data[:size])
 
113
 
 
114
#
 
115
# --------------------------------------------------------------------
 
116
 
 
117
# FIXME: should add a counter in here to avoid looping forever
 
118
# if the tree is broken.
 
119
 
 
120
class _OleDirectoryEntry:
 
121
 
 
122
    """OLE2 Directory Entry
 
123
 
 
124
    Encapsulates a stream directory entry.  Note that the
 
125
    constructor builds a tree of all subentries, so we only
 
126
    have to call it with the root object.
 
127
    """
 
128
 
 
129
    def __init__(self, sidlist, sid):
 
130
 
 
131
        # store directory parameters.  the caller provides
 
132
        # a complete list of directory entries, as read from
 
133
        # the directory stream.
 
134
 
 
135
        name, type, sect, size, sids, clsid = sidlist[sid]
 
136
 
 
137
        self.sid  = sid
 
138
        self.name = name
 
139
        self.type = type # 1=storage 2=stream
 
140
        self.sect = sect
 
141
        self.size = size
 
142
        self.clsid = clsid
 
143
 
 
144
        # process child nodes, if any
 
145
 
 
146
        self.kids = []
 
147
 
 
148
        sid = sidlist[sid][4][2]
 
149
 
 
150
        if sid != -1:
 
151
 
 
152
            # the directory entries are organized as a red-black tree.
 
153
            # the following piece of code does an ordered traversal of
 
154
            # such a tree (at least that's what I hope ;-)
 
155
 
 
156
            stack = [self.sid]
 
157
 
 
158
            # start at leftmost position
 
159
 
 
160
            left, right, child = sidlist[sid][4]
 
161
 
 
162
            while left != -1: # 0xFFFFFFFFL:
 
163
                stack.append(sid)
 
164
                sid = left
 
165
                left, right, child = sidlist[sid][4]
 
166
 
 
167
            while sid != self.sid:
 
168
 
 
169
                self.kids.append(_OleDirectoryEntry(sidlist, sid))
 
170
 
 
171
                # try to move right
 
172
                left, right, child = sidlist[sid][4]
 
173
                if right != -1: # 0xFFFFFFFFL:
 
174
                    # and then back to the left
 
175
                    sid = right
 
176
                    while 1:
 
177
                        left, right, child = sidlist[sid][4]
 
178
                        if left == -1: # 0xFFFFFFFFL:
 
179
                            break
 
180
                        stack.append(sid)
 
181
                        sid = left
 
182
                else:
 
183
                    # couldn't move right; move up instead
 
184
                    while 1:
 
185
                        ptr = stack[-1]
 
186
                        del stack[-1]
 
187
                        left, right, child = sidlist[ptr][4]
 
188
                        if right != sid:
 
189
                            break
 
190
                        sid = right
 
191
                    left, right, child = sidlist[sid][4]
 
192
                    if right != ptr:
 
193
                        sid = ptr
 
194
 
 
195
            # in the OLE file, entries are sorted on (length, name).
 
196
            # for convenience, we sort them on name instead.
 
197
 
 
198
            self.kids.sort()
 
199
 
 
200
    def __cmp__(self, other):
 
201
        "Compare entries by name"
 
202
 
 
203
        return cmp(self.name, other.name)
 
204
 
 
205
    def dump(self, tab = 0):
 
206
        "Dump this entry, and all its subentries (for debug purposes only)"
 
207
 
 
208
        TYPES = ["(invalid)", "(storage)", "(stream)", "(lockbytes)",
 
209
                 "(property)", "(root)"]
 
210
 
 
211
        print " "*tab + repr(self.name), TYPES[self.type],
 
212
        if self.type in (2, 5):
 
213
            print self.size, "bytes",
 
214
        print
 
215
        if self.type in (1, 5) and self.clsid:
 
216
            print " "*tab + "{%s}" % self.clsid
 
217
 
 
218
        for kid in self.kids:
 
219
            kid.dump(tab + 2)
 
220
 
 
221
#
 
222
# --------------------------------------------------------------------
 
223
 
 
224
##
 
225
# This class encapsulates the interface to an OLE 2 structured
 
226
# storage file.  Use the {@link listdir} and {@link openstream}
 
227
# methods to access the contents of this file.
 
228
 
 
229
class OleFileIO:
 
230
    """OLE container object
 
231
 
 
232
    This class encapsulates the interface to an OLE 2 structured
 
233
    storage file.  Use the listdir and openstream methods to access
 
234
    the contents of this file.
 
235
 
 
236
    Object names are given as a list of strings, one for each subentry
 
237
    level.  The root entry should be omitted.  For example, the following
 
238
    code extracts all image streams from a Microsoft Image Composer file:
 
239
 
 
240
        ole = OleFileIO("fan.mic")
 
241
 
 
242
        for entry in ole.listdir():
 
243
            if entry[1:2] == "Image":
 
244
                fin = ole.openstream(entry)
 
245
                fout = open(entry[0:1], "wb")
 
246
                while 1:
 
247
                    s = fin.read(8192)
 
248
                    if not s:
 
249
                        break
 
250
                    fout.write(s)
 
251
 
 
252
    You can use the viewer application provided with the Python Imaging
 
253
    Library to view the resulting files (which happens to be standard
 
254
    TIFF files).
 
255
    """
 
256
 
 
257
    def __init__(self, filename = None):
 
258
 
 
259
        if filename:
 
260
            self.open(filename)
 
261
 
 
262
    ##
 
263
    # Open an OLE2 file.
 
264
 
 
265
    def open(self, filename):
 
266
        """Open an OLE2 file"""
 
267
 
 
268
        if type(filename) == type(""):
 
269
            self.fp = open(filename, "rb")
 
270
        else:
 
271
            self.fp = filename
 
272
 
 
273
        header = self.fp.read(512)
 
274
 
 
275
        if len(header) != 512 or header[:8] != MAGIC:
 
276
            raise IOError, "not an OLE2 structured storage file"
 
277
 
 
278
        # file clsid (probably never used, so we don't store it)
 
279
        clsid = self._clsid(header[8:24])
 
280
 
 
281
        # FIXME: could check version and byte order fields
 
282
 
 
283
        self.sectorsize = 1 << i16(header, 30)
 
284
        self.minisectorsize = 1 << i16(header, 32)
 
285
 
 
286
        self.minisectorcutoff = i32(header, 56)
 
287
 
 
288
        # Load file allocation tables
 
289
        self.loadfat(header)
 
290
 
 
291
        # Load direcory.  This sets both the sidlist (ordered by id)
 
292
        # and the root (ordered by hierarchy) members.
 
293
        self.loaddirectory(i32(header, 48))
 
294
 
 
295
        self.ministream = None
 
296
        self.minifatsect = i32(header, 60)
 
297
 
 
298
    def loadfat(self, header):
 
299
        # Load the FAT table.  The header contains a sector numbers
 
300
        # for the first 109 FAT sectors.  Additional sectors are
 
301
        # described by DIF blocks (FIXME: not yet implemented)
 
302
 
 
303
        sect = header[76:512]
 
304
        fat = []
 
305
        for i in range(0, len(sect), 4):
 
306
            ix = i32(sect, i)
 
307
            if ix == -2 or ix == -1: # ix == 0xFFFFFFFEL or ix == 0xFFFFFFFFL:
 
308
                break
 
309
            s = self.getsect(ix)
 
310
            fat = fat + map(lambda i, s=s: i32(s, i), range(0, len(s), 4))
 
311
        self.fat = fat
 
312
 
 
313
    def loadminifat(self):
 
314
        # Load the MINIFAT table.  This is stored in a standard sub-
 
315
        # stream, pointed to by a header field.
 
316
 
 
317
        s = self._open(self.minifatsect).read()
 
318
 
 
319
        self.minifat = map(lambda i, s=s: i32(s, i), range(0, len(s), 4))
 
320
 
 
321
    def getsect(self, sect):
 
322
        # Read given sector
 
323
 
 
324
        self.fp.seek(512 + self.sectorsize * sect)
 
325
        return self.fp.read(self.sectorsize)
 
326
 
 
327
    def _unicode(self, s):
 
328
        # Map unicode string to Latin 1
 
329
 
 
330
        # FIXME: some day, Python will provide an official way to handle
 
331
        # Unicode strings, but until then, this will have to do...
 
332
        return filter(ord, s)
 
333
 
 
334
    def loaddirectory(self, sect):
 
335
        # Load the directory.  The directory is stored in a standard
 
336
        # substream, independent of its size.
 
337
 
 
338
        # read directory stream
 
339
        fp = self._open(sect)
 
340
 
 
341
        # create list of sid entries
 
342
        self.sidlist = []
 
343
        while 1:
 
344
            entry = fp.read(128)
 
345
            if not entry:
 
346
                break
 
347
            type = ord(entry[66])
 
348
            name = self._unicode(entry[0:0+i16(entry, 64)])
 
349
            ptrs = i32(entry, 68), i32(entry, 72), i32(entry, 76)
 
350
            sect, size = i32(entry, 116), i32(entry, 120)
 
351
            clsid = self._clsid(entry[80:96])
 
352
            self.sidlist.append((name, type, sect, size, ptrs, clsid))
 
353
 
 
354
        # create hierarchical list of directory entries
 
355
        self.root = _OleDirectoryEntry(self.sidlist, 0)
 
356
 
 
357
    def dumpdirectory(self):
 
358
        # Dump directory (for debugging only)
 
359
 
 
360
        self.root.dump()
 
361
 
 
362
    def _clsid(self, clsid):
 
363
        if clsid == "\0" * len(clsid):
 
364
            return ""
 
365
        return (("%08X-%04X-%04X-%02X%02X-" + "%02X" * 6) %
 
366
                ((i32(clsid, 0), i16(clsid, 4), i16(clsid, 6)) +
 
367
                tuple(map(ord, clsid[8:16]))))
 
368
 
 
369
    def _list(self, files, prefix, node):
 
370
        # listdir helper
 
371
 
 
372
        prefix = prefix + [node.name]
 
373
        for entry in node.kids:
 
374
            if entry.kids:
 
375
                self._list(files, prefix, entry)
 
376
            else:
 
377
                files.append(prefix[1:] + [entry.name])
 
378
 
 
379
    def _find(self, filename):
 
380
        # openstream helper
 
381
 
 
382
        node = self.root
 
383
        for name in filename:
 
384
            for kid in node.kids:
 
385
                if kid.name == name:
 
386
                    break
 
387
            else:
 
388
                raise IOError, "file not found"
 
389
            node = kid
 
390
        return node.sid
 
391
 
 
392
    def _open(self, start, size = 0x7FFFFFFF):
 
393
        # openstream helper.
 
394
 
 
395
        if size < self.minisectorcutoff:
 
396
            # ministream object
 
397
            if not self.ministream:
 
398
                self.loadminifat()
 
399
                self.ministream = self._open(self.sidlist[0][2])
 
400
            return _OleStream(self.ministream, start, size, 0,
 
401
                              self.minisectorsize, self.minifat)
 
402
 
 
403
        # standard stream
 
404
        return _OleStream(self.fp, start, size, 512,
 
405
                          self.sectorsize, self.fat)
 
406
 
 
407
    ##
 
408
    # Returns a list of streams stored in this file.
 
409
 
 
410
    def listdir(self):
 
411
        """Return a list of streams stored in this file"""
 
412
 
 
413
        files = []
 
414
        self._list(files, [], self.root)
 
415
        return files
 
416
 
 
417
    ##
 
418
    # Opens a stream as a read-only file object.
 
419
 
 
420
    def openstream(self, filename):
 
421
        """Open a stream as a read-only file object"""
 
422
 
 
423
        slot = self._find(filename)
 
424
        name, type, sect, size, sids, clsid = self.sidlist[slot]
 
425
        if type != 2:
 
426
            raise IOError, "this file is not a stream"
 
427
        return self._open(sect, size)
 
428
 
 
429
    ##
 
430
    # Gets a list of properties described in substream.
 
431
 
 
432
    def getproperties(self, filename):
 
433
        """Return properties described in substream"""
 
434
 
 
435
        fp = self.openstream(filename)
 
436
 
 
437
        data = {}
 
438
 
 
439
        # header
 
440
        s = fp.read(28)
 
441
        clsid = self._clsid(s[8:24])
 
442
 
 
443
        # format id
 
444
        s = fp.read(20)
 
445
        fmtid = self._clsid(s[:16])
 
446
        fp.seek(i32(s, 16))
 
447
 
 
448
        # get section
 
449
        s = "****" + fp.read(i32(fp.read(4))-4)
 
450
 
 
451
        for i in range(i32(s, 4)):
 
452
 
 
453
            id = i32(s, 8+i*8)
 
454
            offset = i32(s, 12+i*8)
 
455
            type = i32(s, offset)
 
456
 
 
457
            # test for common types first (should perhaps use
 
458
            # a dictionary instead?)
 
459
 
 
460
            if type == VT_I2:
 
461
                value = i16(s, offset+4)
 
462
                if value >= 32768:
 
463
                    value = value - 65536
 
464
            elif type == VT_UI2:
 
465
                value = i16(s, offset+4)
 
466
            elif type in (VT_I4, VT_ERROR):
 
467
                value = i32(s, offset+4)
 
468
            elif type == VT_UI4:
 
469
                value = i32(s, offset+4) # FIXME
 
470
            elif type in (VT_BSTR, VT_LPSTR):
 
471
                count = i32(s, offset+4)
 
472
                value = s[offset+8:offset+8+count-1]
 
473
            elif type == VT_BLOB:
 
474
                count = i32(s, offset+4)
 
475
                value = s[offset+8:offset+8+count]
 
476
            elif type == VT_LPWSTR:
 
477
                count = i32(s, offset+4)
 
478
                value = self._unicode(s[offset+8:offset+8+count*2])
 
479
            elif type == VT_FILETIME:
 
480
                value = long(i32(s, offset+4)) + (long(i32(s, offset+8))<<32)
 
481
                # FIXME: this is a 64-bit int: "number of 100ns periods
 
482
                # since Jan 1,1601".  Should map this to Python time
 
483
                value = value / 10000000L # seconds
 
484
            elif type == VT_UI1:
 
485
                value = ord(s[offset+4])
 
486
            elif type == VT_CLSID:
 
487
                value = self._clsid(s[offset+4:offset+20])
 
488
            elif type == VT_CF:
 
489
                count = i32(s, offset+4)
 
490
                value = s[offset+8:offset+8+count]
 
491
            else:
 
492
                value = None # everything else yields "None"
 
493
 
 
494
            # FIXME: add support for VT_VECTOR
 
495
 
 
496
            #print "%08x" % id, repr(value),
 
497
            #print "(%s)" % VT[i32(s, offset) & 0xFFF]
 
498
 
 
499
            data[id] = value
 
500
 
 
501
        return data
 
502
 
 
503
#
 
504
# --------------------------------------------------------------------
 
505
# This script can be used to dump the directory of any OLE2 structured
 
506
# storage file.
 
507
 
 
508
if __name__ == "__main__":
 
509
 
 
510
    import sys
 
511
 
 
512
    for file in sys.argv[1:]:
 
513
        try:
 
514
            ole = OleFileIO(file)
 
515
            print "-" * 68
 
516
            print file
 
517
            print "-" * 68
 
518
            ole.dumpdirectory()
 
519
            for file in ole.listdir():
 
520
                if file[-1][0] == "\005":
 
521
                    print file
 
522
                    props = ole.getproperties(file)
 
523
                    props = props.items()
 
524
                    props.sort()
 
525
                    for k, v in props:
 
526
                        print "   ", k, v
 
527
        except IOError, v:
 
528
            print "***", "cannot read", file, "-", v