| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  | r"""Routines to decode AppleSingle files
 | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | import struct | 
					
						
							|  |  |  | import sys | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  | try: | 
					
						
							|  |  |  |     import MacOS | 
					
						
							|  |  |  |     import Carbon.File | 
					
						
							|  |  |  | except: | 
					
						
							|  |  |  |     class MacOS: | 
					
						
							|  |  |  |         def openrf(path, mode): | 
					
						
							|  |  |  |             return open(path + '.rsrc', mode) | 
					
						
							|  |  |  |         openrf = classmethod(openrf) | 
					
						
							|  |  |  |     class Carbon: | 
					
						
							|  |  |  |         class File: | 
					
						
							|  |  |  |             class FSSpec: | 
					
						
							|  |  |  |                 pass | 
					
						
							|  |  |  |             class FSRef: | 
					
						
							|  |  |  |                 pass | 
					
						
							|  |  |  |             class Alias: | 
					
						
							|  |  |  |                 pass | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  | # all of the errors in this module are really errors in the input | 
					
						
							|  |  |  | # so I think it should test positive against ValueError. | 
					
						
							|  |  |  | class Error(ValueError): | 
					
						
							|  |  |  |     pass | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | # File header format: magic, version, unused, number of entries | 
					
						
							| 
									
										
										
										
											2006-02-23 14:54:30 +00:00
										 |  |  | AS_HEADER_FORMAT=">ll16sh" | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | AS_HEADER_LENGTH=26 | 
					
						
							|  |  |  | # The flag words for AppleSingle | 
					
						
							|  |  |  | AS_MAGIC=0x00051600 | 
					
						
							|  |  |  | AS_VERSION=0x00020000 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Entry header format: id, offset, length | 
					
						
							| 
									
										
										
										
											2006-02-23 14:54:30 +00:00
										 |  |  | AS_ENTRY_FORMAT=">lll" | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | AS_ENTRY_LENGTH=12 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # The id values | 
					
						
							|  |  |  | AS_DATAFORK=1 | 
					
						
							|  |  |  | AS_RESOURCEFORK=2 | 
					
						
							|  |  |  | AS_IGNORE=(3,4,5,6,8,9,10,11,12,13,14,15) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  | class AppleSingle(object): | 
					
						
							|  |  |  |     datafork = None | 
					
						
							|  |  |  |     resourcefork = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, fileobj, verbose=False): | 
					
						
							|  |  |  |         header = fileobj.read(AS_HEADER_LENGTH) | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |         try: | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |             magic, version, ig, nentry = struct.unpack(AS_HEADER_FORMAT, header) | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |         except ValueError, arg: | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |             raise Error, "Unpack header error: %s" % (arg,) | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |         if verbose: | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |             print 'Magic:   0x%8.8x' % (magic,) | 
					
						
							|  |  |  |             print 'Version: 0x%8.8x' % (version,) | 
					
						
							|  |  |  |             print 'Entries: %d' % (nentry,) | 
					
						
							|  |  |  |         if magic != AS_MAGIC: | 
					
						
							|  |  |  |             raise Error, "Unknown AppleSingle magic number 0x%8.8x" % (magic,) | 
					
						
							|  |  |  |         if version != AS_VERSION: | 
					
						
							|  |  |  |             raise Error, "Unknown AppleSingle version number 0x%8.8x" % (version,) | 
					
						
							|  |  |  |         if nentry <= 0: | 
					
						
							|  |  |  |             raise Error, "AppleSingle file contains no forks" | 
					
						
							|  |  |  |         headers = [fileobj.read(AS_ENTRY_LENGTH) for i in xrange(nentry)] | 
					
						
							|  |  |  |         self.forks = [] | 
					
						
							|  |  |  |         for hdr in headers: | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 restype, offset, length = struct.unpack(AS_ENTRY_FORMAT, hdr) | 
					
						
							|  |  |  |             except ValueError, arg: | 
					
						
							|  |  |  |                 raise Error, "Unpack entry error: %s" % (arg,) | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |             if verbose: | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |                 print "Fork %d, offset %d, length %d" % (restype, offset, length) | 
					
						
							|  |  |  |             fileobj.seek(offset) | 
					
						
							|  |  |  |             data = fileobj.read(length) | 
					
						
							|  |  |  |             if len(data) != length: | 
					
						
							|  |  |  |                 raise Error, "Short read: expected %d bytes got %d" % (length, len(data)) | 
					
						
							|  |  |  |             self.forks.append((restype, data)) | 
					
						
							|  |  |  |             if restype == AS_DATAFORK: | 
					
						
							|  |  |  |                 self.datafork = data | 
					
						
							|  |  |  |             elif restype == AS_RESOURCEFORK: | 
					
						
							|  |  |  |                 self.resourcefork = data | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |     def tofile(self, path, resonly=False): | 
					
						
							|  |  |  |         outfile = open(path, 'wb') | 
					
						
							|  |  |  |         data = False | 
					
						
							|  |  |  |         if resonly: | 
					
						
							|  |  |  |             if self.resourcefork is None: | 
					
						
							|  |  |  |                 raise Error, "No resource fork found" | 
					
						
							|  |  |  |             fp = open(path, 'wb') | 
					
						
							|  |  |  |             fp.write(self.resourcefork) | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |             fp.close() | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |         elif (self.resourcefork is None and self.datafork is None): | 
					
						
							|  |  |  |             raise Error, "No useful forks found" | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |         else: | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |             if self.datafork is not None: | 
					
						
							|  |  |  |                 fp = open(path, 'wb') | 
					
						
							|  |  |  |                 fp.write(self.datafork) | 
					
						
							|  |  |  |                 fp.close() | 
					
						
							|  |  |  |             if self.resourcefork is not None: | 
					
						
							|  |  |  |                 fp = MacOS.openrf(path, '*wb') | 
					
						
							|  |  |  |                 fp.write(self.resourcefork) | 
					
						
							|  |  |  |                 fp.close() | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  | def decode(infile, outpath, resonly=False, verbose=False): | 
					
						
							|  |  |  |     """decode(infile, outpath [, resonly=False, verbose=False])
 | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |     Creates a decoded file from an AppleSingle encoded file. | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  |     If resonly is True, then it will create a regular file at | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |     outpath containing only the resource fork from infile. | 
					
						
							|  |  |  |     Otherwise it will create an AppleDouble file at outpath | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  |     with the data and resource forks from infile.  On platforms | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |     without the MacOS module, it will create inpath and inpath+'.rsrc' | 
					
						
							|  |  |  |     with the data and resource forks respectively. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if not hasattr(infile, 'read'): | 
					
						
							|  |  |  |         if isinstance(infile, Carbon.File.Alias): | 
					
						
							|  |  |  |             infile = infile.ResolveAlias()[0] | 
					
						
							|  |  |  |         if isinstance(infile, (Carbon.File.FSSpec, Carbon.File.FSRef)): | 
					
						
							|  |  |  |             infile = infile.as_pathname() | 
					
						
							|  |  |  |         infile = open(infile, 'rb') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     as = AppleSingle(infile, verbose=verbose) | 
					
						
							|  |  |  |     as.tofile(outpath, resonly=resonly) | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | def _test(): | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |     if len(sys.argv) < 3 or sys.argv[1] == '-r' and len(sys.argv) != 4: | 
					
						
							|  |  |  |         print 'Usage: applesingle.py [-r] applesinglefile decodedfile' | 
					
						
							|  |  |  |         sys.exit(1) | 
					
						
							|  |  |  |     if sys.argv[1] == '-r': | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |         resonly = True | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |         del sys.argv[1] | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2003-11-18 23:09:19 +00:00
										 |  |  |         resonly = False | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |     decode(sys.argv[1], sys.argv[2], resonly=resonly) | 
					
						
							| 
									
										
										
										
											2004-07-18 06:16:08 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2001-09-09 00:35:19 +00:00
										 |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2003-04-09 13:25:43 +00:00
										 |  |  |     _test() |