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