| 1 | n/a | r"""Routines to decode AppleSingle files |
|---|
| 2 | n/a | """ |
|---|
| 3 | n/a | |
|---|
| 4 | n/a | from warnings import warnpy3k |
|---|
| 5 | n/a | warnpy3k("In 3.x, the applesingle module is removed.", stacklevel=2) |
|---|
| 6 | n/a | |
|---|
| 7 | n/a | import struct |
|---|
| 8 | n/a | import sys |
|---|
| 9 | n/a | try: |
|---|
| 10 | n/a | import MacOS |
|---|
| 11 | n/a | import Carbon.File |
|---|
| 12 | n/a | except: |
|---|
| 13 | n/a | class MacOS: |
|---|
| 14 | n/a | def openrf(path, mode): |
|---|
| 15 | n/a | return open(path + '.rsrc', mode) |
|---|
| 16 | n/a | openrf = classmethod(openrf) |
|---|
| 17 | n/a | class Carbon: |
|---|
| 18 | n/a | class File: |
|---|
| 19 | n/a | class FSSpec: |
|---|
| 20 | n/a | pass |
|---|
| 21 | n/a | class FSRef: |
|---|
| 22 | n/a | pass |
|---|
| 23 | n/a | class Alias: |
|---|
| 24 | n/a | pass |
|---|
| 25 | n/a | |
|---|
| 26 | n/a | # all of the errors in this module are really errors in the input |
|---|
| 27 | n/a | # so I think it should test positive against ValueError. |
|---|
| 28 | n/a | class Error(ValueError): |
|---|
| 29 | n/a | pass |
|---|
| 30 | n/a | |
|---|
| 31 | n/a | # File header format: magic, version, unused, number of entries |
|---|
| 32 | n/a | AS_HEADER_FORMAT=">LL16sh" |
|---|
| 33 | n/a | AS_HEADER_LENGTH=26 |
|---|
| 34 | n/a | # The flag words for AppleSingle |
|---|
| 35 | n/a | AS_MAGIC=0x00051600 |
|---|
| 36 | n/a | AS_VERSION=0x00020000 |
|---|
| 37 | n/a | |
|---|
| 38 | n/a | # Entry header format: id, offset, length |
|---|
| 39 | n/a | AS_ENTRY_FORMAT=">lll" |
|---|
| 40 | n/a | AS_ENTRY_LENGTH=12 |
|---|
| 41 | n/a | |
|---|
| 42 | n/a | # The id values |
|---|
| 43 | n/a | AS_DATAFORK=1 |
|---|
| 44 | n/a | AS_RESOURCEFORK=2 |
|---|
| 45 | n/a | AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15) |
|---|
| 46 | n/a | |
|---|
| 47 | n/a | class AppleSingle(object): |
|---|
| 48 | n/a | datafork = None |
|---|
| 49 | n/a | resourcefork = None |
|---|
| 50 | n/a | |
|---|
| 51 | n/a | def __init__(self, fileobj, verbose=False): |
|---|
| 52 | n/a | header = fileobj.read(AS_HEADER_LENGTH) |
|---|
| 53 | n/a | try: |
|---|
| 54 | n/a | magic, version, ig, nentry = struct.unpack(AS_HEADER_FORMAT, header) |
|---|
| 55 | n/a | except ValueError, arg: |
|---|
| 56 | n/a | raise Error, "Unpack header error: %s" % (arg,) |
|---|
| 57 | n/a | if verbose: |
|---|
| 58 | n/a | print 'Magic: 0x%8.8x' % (magic,) |
|---|
| 59 | n/a | print 'Version: 0x%8.8x' % (version,) |
|---|
| 60 | n/a | print 'Entries: %d' % (nentry,) |
|---|
| 61 | n/a | if magic != AS_MAGIC: |
|---|
| 62 | n/a | raise Error, "Unknown AppleSingle magic number 0x%8.8x" % (magic,) |
|---|
| 63 | n/a | if version != AS_VERSION: |
|---|
| 64 | n/a | raise Error, "Unknown AppleSingle version number 0x%8.8x" % (version,) |
|---|
| 65 | n/a | if nentry <= 0: |
|---|
| 66 | n/a | raise Error, "AppleSingle file contains no forks" |
|---|
| 67 | n/a | headers = [fileobj.read(AS_ENTRY_LENGTH) for i in xrange(nentry)] |
|---|
| 68 | n/a | self.forks = [] |
|---|
| 69 | n/a | for hdr in headers: |
|---|
| 70 | n/a | try: |
|---|
| 71 | n/a | restype, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr) |
|---|
| 72 | n/a | except ValueError, arg: |
|---|
| 73 | n/a | raise Error, "Unpack entry error: %s" % (arg,) |
|---|
| 74 | n/a | if verbose: |
|---|
| 75 | n/a | print "Fork %d, offset %d, length %d" % (restype, offset, length) |
|---|
| 76 | n/a | fileobj.seek(offset) |
|---|
| 77 | n/a | data = fileobj.read(length) |
|---|
| 78 | n/a | if len(data) != length: |
|---|
| 79 | n/a | raise Error, "Short read: expected %d bytes got %d" % (length, len(data)) |
|---|
| 80 | n/a | self.forks.append((restype, data)) |
|---|
| 81 | n/a | if restype == AS_DATAFORK: |
|---|
| 82 | n/a | self.datafork = data |
|---|
| 83 | n/a | elif restype == AS_RESOURCEFORK: |
|---|
| 84 | n/a | self.resourcefork = data |
|---|
| 85 | n/a | |
|---|
| 86 | n/a | def tofile(self, path, resonly=False): |
|---|
| 87 | n/a | outfile = open(path, 'wb') |
|---|
| 88 | n/a | data = False |
|---|
| 89 | n/a | if resonly: |
|---|
| 90 | n/a | if self.resourcefork is None: |
|---|
| 91 | n/a | raise Error, "No resource fork found" |
|---|
| 92 | n/a | fp = open(path, 'wb') |
|---|
| 93 | n/a | fp.write(self.resourcefork) |
|---|
| 94 | n/a | fp.close() |
|---|
| 95 | n/a | elif (self.resourcefork is None and self.datafork is None): |
|---|
| 96 | n/a | raise Error, "No useful forks found" |
|---|
| 97 | n/a | else: |
|---|
| 98 | n/a | if self.datafork is not None: |
|---|
| 99 | n/a | fp = open(path, 'wb') |
|---|
| 100 | n/a | fp.write(self.datafork) |
|---|
| 101 | n/a | fp.close() |
|---|
| 102 | n/a | if self.resourcefork is not None: |
|---|
| 103 | n/a | fp = MacOS.openrf(path, '*wb') |
|---|
| 104 | n/a | fp.write(self.resourcefork) |
|---|
| 105 | n/a | fp.close() |
|---|
| 106 | n/a | |
|---|
| 107 | n/a | def decode(infile, outpath, resonly=False, verbose=False): |
|---|
| 108 | n/a | """decode(infile, outpath [, resonly=False, verbose=False]) |
|---|
| 109 | n/a | |
|---|
| 110 | n/a | Creates a decoded file from an AppleSingle encoded file. |
|---|
| 111 | n/a | If resonly is True, then it will create a regular file at |
|---|
| 112 | n/a | outpath containing only the resource fork from infile. |
|---|
| 113 | n/a | Otherwise it will create an AppleDouble file at outpath |
|---|
| 114 | n/a | with the data and resource forks from infile. On platforms |
|---|
| 115 | n/a | without the MacOS module, it will create inpath and inpath+'.rsrc' |
|---|
| 116 | n/a | with the data and resource forks respectively. |
|---|
| 117 | n/a | |
|---|
| 118 | n/a | """ |
|---|
| 119 | n/a | if not hasattr(infile, 'read'): |
|---|
| 120 | n/a | if isinstance(infile, Carbon.File.Alias): |
|---|
| 121 | n/a | infile = infile.ResolveAlias()[0] |
|---|
| 122 | n/a | |
|---|
| 123 | n/a | if hasattr(Carbon.File, "FSSpec"): |
|---|
| 124 | n/a | if isinstance(infile, (Carbon.File.FSSpec, Carbon.File.FSRef)): |
|---|
| 125 | n/a | infile = infile.as_pathname() |
|---|
| 126 | n/a | else: |
|---|
| 127 | n/a | if isinstance(infile, Carbon.File.FSRef): |
|---|
| 128 | n/a | infile = infile.as_pathname() |
|---|
| 129 | n/a | infile = open(infile, 'rb') |
|---|
| 130 | n/a | |
|---|
| 131 | n/a | asfile = AppleSingle(infile, verbose=verbose) |
|---|
| 132 | n/a | asfile.tofile(outpath, resonly=resonly) |
|---|
| 133 | n/a | |
|---|
| 134 | n/a | def _test(): |
|---|
| 135 | n/a | if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4: |
|---|
| 136 | n/a | print 'Usage: applesingle.py [-r] applesinglefile decodedfile' |
|---|
| 137 | n/a | sys.exit(1) |
|---|
| 138 | n/a | if sys.argv[1] == '-r': |
|---|
| 139 | n/a | resonly = True |
|---|
| 140 | n/a | del sys.argv[1] |
|---|
| 141 | n/a | else: |
|---|
| 142 | n/a | resonly = False |
|---|
| 143 | n/a | decode(sys.argv[1], sys.argv[2], resonly=resonly) |
|---|
| 144 | n/a | |
|---|
| 145 | n/a | if __name__ == '__main__': |
|---|
| 146 | n/a | _test() |
|---|