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