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