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() 147