| 1 | r"""Routines to decode AppleSingle files | 
|---|
| 2 | """ | 
|---|
| 3 |  | 
|---|
| 4 | from warnings import warnpy3k | 
|---|
| 5 | warnpy3k("In 3.x, the applesingle module is removed.", stacklevel=2) | 
|---|
| 6 |  | 
|---|
| 7 | import struct | 
|---|
| 8 | import sys | 
|---|
| 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 | 
|---|
| 25 |  | 
|---|
| 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 | 
|---|
| 30 |  | 
|---|
| 31 | # File header format: magic, version, unused, number of entries | 
|---|
| 32 | AS_HEADER_FORMAT=">LL16sh" | 
|---|
| 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 | 
|---|
| 39 | AS_ENTRY_FORMAT=">lll" | 
|---|
| 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 |  | 
|---|
| 47 | class AppleSingle(object): | 
|---|
| 48 | datafork = None | 
|---|
| 49 | resourcefork = None | 
|---|
| 50 |  | 
|---|
| 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 | 
|---|
| 85 |  | 
|---|
| 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() | 
|---|
| 106 |  | 
|---|
| 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. | 
|---|
| 111 | If resonly is True, then it will create a regular file at | 
|---|
| 112 | outpath containing only the resource fork from infile. | 
|---|
| 113 | Otherwise it will create an AppleDouble file at outpath | 
|---|
| 114 | with the data and resource forks from infile.  On platforms | 
|---|
| 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] | 
|---|
| 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() | 
|---|
| 129 | infile = open(infile, 'rb') | 
|---|
| 130 |  | 
|---|
| 131 | asfile = AppleSingle(infile, verbose=verbose) | 
|---|
| 132 | asfile.tofile(outpath, resonly=resonly) | 
|---|
| 133 |  | 
|---|
| 134 | def _test(): | 
|---|
| 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': | 
|---|
| 139 | resonly = True | 
|---|
| 140 | del sys.argv[1] | 
|---|
| 141 | else: | 
|---|
| 142 | resonly = False | 
|---|
| 143 | decode(sys.argv[1], sys.argv[2], resonly=resonly) | 
|---|
| 144 |  | 
|---|
| 145 | if __name__ == '__main__': | 
|---|
| 146 | _test() | 
|---|