0001 """Macintosh binhex compression/decompression. 0002 0003 easy interface: 0004 binhex(inputfilename, outputfilename) 0005 hexbin(inputfilename, outputfilename) 0006 """ 0007 0008 # 0009 # Jack Jansen, CWI, August 1995. 0010 # 0011 # The module is supposed to be as compatible as possible. Especially the 0012 # easy interface should work "as expected" on any platform. 0013 # XXXX Note: currently, textfiles appear in mac-form on all platforms. 0014 # We seem to lack a simple character-translate in python. 0015 # (we should probably use ISO-Latin-1 on all but the mac platform). 0016 # XXXX The simple routines are too simple: they expect to hold the complete 0017 # files in-core. Should be fixed. 0018 # XXXX It would be nice to handle AppleDouble format on unix 0019 # (for servers serving macs). 0020 # XXXX I don't understand what happens when you get 0x90 times the same byte on 0021 # input. The resulting code (xx 90 90) would appear to be interpreted as an 0022 # escaped *value* of 0x90. All coders I've seen appear to ignore this nicety... 0023 # 0024 import sys 0025 import os 0026 import struct 0027 import binascii 0028 0029 __all__ = ["binhex","hexbin","Error"] 0030 0031 class Error(Exception): 0032 pass 0033 0034 # States (what have we written) 0035 [_DID_HEADER, _DID_DATA, _DID_RSRC] = range(3) 0036 0037 # Various constants 0038 REASONABLY_LARGE=32768 # Minimal amount we pass the rle-coder 0039 LINELEN=64 0040 RUNCHAR=chr(0x90) # run-length introducer 0041 0042 # 0043 # This code is no longer byte-order dependent 0044 0045 # 0046 # Workarounds for non-mac machines. 0047 if os.name == 'mac': 0048 import macfs 0049 import MacOS 0050 try: 0051 openrf = MacOS.openrf 0052 except AttributeError: 0053 # Backward compatibility 0054 openrf = open 0055 0056 def FInfo(): 0057 return macfs.FInfo() 0058 0059 def getfileinfo(name): 0060 finfo = macfs.FSSpec(name).GetFInfo() 0061 dir, file = os.path.split(name) 0062 # XXXX Get resource/data sizes 0063 fp = open(name, 'rb') 0064 fp.seek(0, 2) 0065 dlen = fp.tell() 0066 fp = openrf(name, '*rb') 0067 fp.seek(0, 2) 0068 rlen = fp.tell() 0069 return file, finfo, dlen, rlen 0070 0071 def openrsrc(name, *mode): 0072 if not mode: 0073 mode = '*rb' 0074 else: 0075 mode = '*' + mode[0] 0076 return openrf(name, mode) 0077 0078 else: 0079 # 0080 # Glue code for non-macintosh usage 0081 # 0082 0083 class FInfo: 0084 def __init__(self): 0085 self.Type = '????' 0086 self.Creator = '????' 0087 self.Flags = 0 0088 0089 def getfileinfo(name): 0090 finfo = FInfo() 0091 # Quick check for textfile 0092 fp = open(name) 0093 data = open(name).read(256) 0094 for c in data: 0095 if not c.isspace() and (c<' ' or ord(c) > 0x7f): 0096 break 0097 else: 0098 finfo.Type = 'TEXT' 0099 fp.seek(0, 2) 0100 dsize = fp.tell() 0101 fp.close() 0102 dir, file = os.path.split(name) 0103 file = file.replace(':', '-', 1) 0104 return file, finfo, dsize, 0 0105 0106 class openrsrc: 0107 def __init__(self, *args): 0108 pass 0109 0110 def read(self, *args): 0111 return '' 0112 0113 def write(self, *args): 0114 pass 0115 0116 def close(self): 0117 pass 0118 0119 class _Hqxcoderengine: 0120 """Write data to the coder in 3-byte chunks""" 0121 0122 def __init__(self, ofp): 0123 self.ofp = ofp 0124 self.data = '' 0125 self.hqxdata = '' 0126 self.linelen = LINELEN-1 0127 0128 def write(self, data): 0129 self.data = self.data + data 0130 datalen = len(self.data) 0131 todo = (datalen//3)*3 0132 data = self.data[:todo] 0133 self.data = self.data[todo:] 0134 if not data: 0135 return 0136 self.hqxdata = self.hqxdata + binascii.b2a_hqx(data) 0137 self._flush(0) 0138 0139 def _flush(self, force): 0140 first = 0 0141 while first <= len(self.hqxdata)-self.linelen: 0142 last = first + self.linelen 0143 self.ofp.write(self.hqxdata[first:last]+'\n') 0144 self.linelen = LINELEN 0145 first = last 0146 self.hqxdata = self.hqxdata[first:] 0147 if force: 0148 self.ofp.write(self.hqxdata + ':\n') 0149 0150 def close(self): 0151 if self.data: 0152 self.hqxdata = \ 0153 self.hqxdata + binascii.b2a_hqx(self.data) 0154 self._flush(1) 0155 self.ofp.close() 0156 del self.ofp 0157 0158 class _Rlecoderengine: 0159 """Write data to the RLE-coder in suitably large chunks""" 0160 0161 def __init__(self, ofp): 0162 self.ofp = ofp 0163 self.data = '' 0164 0165 def write(self, data): 0166 self.data = self.data + data 0167 if len(self.data) < REASONABLY_LARGE: 0168 return 0169 rledata = binascii.rlecode_hqx(self.data) 0170 self.ofp.write(rledata) 0171 self.data = '' 0172 0173 def close(self): 0174 if self.data: 0175 rledata = binascii.rlecode_hqx(self.data) 0176 self.ofp.write(rledata) 0177 self.ofp.close() 0178 del self.ofp 0179 0180 class BinHex: 0181 def __init__(self, (name, finfo, dlen, rlen), ofp): 0182 if type(ofp) == type(''): 0183 ofname = ofp 0184 ofp = open(ofname, 'w') 0185 if os.name == 'mac': 0186 fss = macfs.FSSpec(ofname) 0187 fss.SetCreatorType('BnHq', 'TEXT') 0188 ofp.write('(This file must be converted with BinHex 4.0)\n\n:') 0189 hqxer = _Hqxcoderengine(ofp) 0190 self.ofp = _Rlecoderengine(hqxer) 0191 self.crc = 0 0192 if finfo is None: 0193 finfo = FInfo() 0194 self.dlen = dlen 0195 self.rlen = rlen 0196 self._writeinfo(name, finfo) 0197 self.state = _DID_HEADER 0198 0199 def _writeinfo(self, name, finfo): 0200 nl = len(name) 0201 if nl > 63: 0202 raise Error, 'Filename too long' 0203 d = chr(nl) + name + '\0' 0204 d2 = finfo.Type + finfo.Creator 0205 0206 # Force all structs to be packed with big-endian 0207 d3 = struct.pack('>h', finfo.Flags) 0208 d4 = struct.pack('>ii', self.dlen, self.rlen) 0209 info = d + d2 + d3 + d4 0210 self._write(info) 0211 self._writecrc() 0212 0213 def _write(self, data): 0214 self.crc = binascii.crc_hqx(data, self.crc) 0215 self.ofp.write(data) 0216 0217 def _writecrc(self): 0218 # XXXX Should this be here?? 0219 # self.crc = binascii.crc_hqx('\0\0', self.crc) 0220 self.ofp.write(struct.pack('>h', self.crc)) 0221 self.crc = 0 0222 0223 def write(self, data): 0224 if self.state != _DID_HEADER: 0225 raise Error, 'Writing data at the wrong time' 0226 self.dlen = self.dlen - len(data) 0227 self._write(data) 0228 0229 def close_data(self): 0230 if self.dlen != 0: 0231 raise Error, 'Incorrect data size, diff=%r' % (self.rlen,) 0232 self._writecrc() 0233 self.state = _DID_DATA 0234 0235 def write_rsrc(self, data): 0236 if self.state < _DID_DATA: 0237 self.close_data() 0238 if self.state != _DID_DATA: 0239 raise Error, 'Writing resource data at the wrong time' 0240 self.rlen = self.rlen - len(data) 0241 self._write(data) 0242 0243 def close(self): 0244 if self.state < _DID_DATA: 0245 self.close_data() 0246 if self.state != _DID_DATA: 0247 raise Error, 'Close at the wrong time' 0248 if self.rlen != 0: 0249 raise Error, \ 0250 "Incorrect resource-datasize, diff=%r" % (self.rlen,) 0251 self._writecrc() 0252 self.ofp.close() 0253 self.state = None 0254 del self.ofp 0255 0256 def binhex(inp, out): 0257 """(infilename, outfilename) - Create binhex-encoded copy of a file""" 0258 finfo = getfileinfo(inp) 0259 ofp = BinHex(finfo, out) 0260 0261 ifp = open(inp, 'rb') 0262 # XXXX Do textfile translation on non-mac systems 0263 while 1: 0264 d = ifp.read(128000) 0265 if not d: break 0266 ofp.write(d) 0267 ofp.close_data() 0268 ifp.close() 0269 0270 ifp = openrsrc(inp, 'rb') 0271 while 1: 0272 d = ifp.read(128000) 0273 if not d: break 0274 ofp.write_rsrc(d) 0275 ofp.close() 0276 ifp.close() 0277 0278 class _Hqxdecoderengine: 0279 """Read data via the decoder in 4-byte chunks""" 0280 0281 def __init__(self, ifp): 0282 self.ifp = ifp 0283 self.eof = 0 0284 0285 def read(self, totalwtd): 0286 """Read at least wtd bytes (or until EOF)""" 0287 decdata = '' 0288 wtd = totalwtd 0289 # 0290 # The loop here is convoluted, since we don't really now how 0291 # much to decode: there may be newlines in the incoming data. 0292 while wtd > 0: 0293 if self.eof: return decdata 0294 wtd = ((wtd+2)//3)*4 0295 data = self.ifp.read(wtd) 0296 # 0297 # Next problem: there may not be a complete number of 0298 # bytes in what we pass to a2b. Solve by yet another 0299 # loop. 0300 # 0301 while 1: 0302 try: 0303 decdatacur, self.eof = \ 0304 binascii.a2b_hqx(data) 0305 break 0306 except binascii.Incomplete: 0307 pass 0308 newdata = self.ifp.read(1) 0309 if not newdata: 0310 raise Error, \ 0311 'Premature EOF on binhex file' 0312 data = data + newdata 0313 decdata = decdata + decdatacur 0314 wtd = totalwtd - len(decdata) 0315 if not decdata and not self.eof: 0316 raise Error, 'Premature EOF on binhex file' 0317 return decdata 0318 0319 def close(self): 0320 self.ifp.close() 0321 0322 class _Rledecoderengine: 0323 """Read data via the RLE-coder""" 0324 0325 def __init__(self, ifp): 0326 self.ifp = ifp 0327 self.pre_buffer = '' 0328 self.post_buffer = '' 0329 self.eof = 0 0330 0331 def read(self, wtd): 0332 if wtd > len(self.post_buffer): 0333 self._fill(wtd-len(self.post_buffer)) 0334 rv = self.post_buffer[:wtd] 0335 self.post_buffer = self.post_buffer[wtd:] 0336 return rv 0337 0338 def _fill(self, wtd): 0339 self.pre_buffer = self.pre_buffer + self.ifp.read(wtd+4) 0340 if self.ifp.eof: 0341 self.post_buffer = self.post_buffer + \ 0342 binascii.rledecode_hqx(self.pre_buffer) 0343 self.pre_buffer = '' 0344 return 0345 0346 # 0347 # Obfuscated code ahead. We have to take care that we don't 0348 # end up with an orphaned RUNCHAR later on. So, we keep a couple 0349 # of bytes in the buffer, depending on what the end of 0350 # the buffer looks like: 0351 # '\220\0\220' - Keep 3 bytes: repeated \220 (escaped as \220\0) 0352 # '?\220' - Keep 2 bytes: repeated something-else 0353 # '\220\0' - Escaped \220: Keep 2 bytes. 0354 # '?\220?' - Complete repeat sequence: decode all 0355 # otherwise: keep 1 byte. 0356 # 0357 mark = len(self.pre_buffer) 0358 if self.pre_buffer[-3:] == RUNCHAR + '\0' + RUNCHAR: 0359 mark = mark - 3 0360 elif self.pre_buffer[-1] == RUNCHAR: 0361 mark = mark - 2 0362 elif self.pre_buffer[-2:] == RUNCHAR + '\0': 0363 mark = mark - 2 0364 elif self.pre_buffer[-2] == RUNCHAR: 0365 pass # Decode all 0366 else: 0367 mark = mark - 1 0368 0369 self.post_buffer = self.post_buffer + \ 0370 binascii.rledecode_hqx(self.pre_buffer[:mark]) 0371 self.pre_buffer = self.pre_buffer[mark:] 0372 0373 def close(self): 0374 self.ifp.close() 0375 0376 class HexBin: 0377 def __init__(self, ifp): 0378 if type(ifp) == type(''): 0379 ifp = open(ifp) 0380 # 0381 # Find initial colon. 0382 # 0383 while 1: 0384 ch = ifp.read(1) 0385 if not ch: 0386 raise Error, "No binhex data found" 0387 # Cater for \r\n terminated lines (which show up as \n\r, hence 0388 # all lines start with \r) 0389 if ch == '\r': 0390 continue 0391 if ch == ':': 0392 break 0393 if ch != '\n': 0394 dummy = ifp.readline() 0395 0396 hqxifp = _Hqxdecoderengine(ifp) 0397 self.ifp = _Rledecoderengine(hqxifp) 0398 self.crc = 0 0399 self._readheader() 0400 0401 def _read(self, len): 0402 data = self.ifp.read(len) 0403 self.crc = binascii.crc_hqx(data, self.crc) 0404 return data 0405 0406 def _checkcrc(self): 0407 filecrc = struct.unpack('>h', self.ifp.read(2))[0] & 0xffff 0408 #self.crc = binascii.crc_hqx('\0\0', self.crc) 0409 # XXXX Is this needed?? 0410 self.crc = self.crc & 0xffff 0411 if filecrc != self.crc: 0412 raise Error, 'CRC error, computed %x, read %x' \ 0413 %(self.crc, filecrc) 0414 self.crc = 0 0415 0416 def _readheader(self): 0417 len = self._read(1) 0418 fname = self._read(ord(len)) 0419 rest = self._read(1+4+4+2+4+4) 0420 self._checkcrc() 0421 0422 type = rest[1:5] 0423 creator = rest[5:9] 0424 flags = struct.unpack('>h', rest[9:11])[0] 0425 self.dlen = struct.unpack('>l', rest[11:15])[0] 0426 self.rlen = struct.unpack('>l', rest[15:19])[0] 0427 0428 self.FName = fname 0429 self.FInfo = FInfo() 0430 self.FInfo.Creator = creator 0431 self.FInfo.Type = type 0432 self.FInfo.Flags = flags 0433 0434 self.state = _DID_HEADER 0435 0436 def read(self, *n): 0437 if self.state != _DID_HEADER: 0438 raise Error, 'Read data at wrong time' 0439 if n: 0440 n = n[0] 0441 n = min(n, self.dlen) 0442 else: 0443 n = self.dlen 0444 rv = '' 0445 while len(rv) < n: 0446 rv = rv + self._read(n-len(rv)) 0447 self.dlen = self.dlen - n 0448 return rv 0449 0450 def close_data(self): 0451 if self.state != _DID_HEADER: 0452 raise Error, 'close_data at wrong time' 0453 if self.dlen: 0454 dummy = self._read(self.dlen) 0455 self._checkcrc() 0456 self.state = _DID_DATA 0457 0458 def read_rsrc(self, *n): 0459 if self.state == _DID_HEADER: 0460 self.close_data() 0461 if self.state != _DID_DATA: 0462 raise Error, 'Read resource data at wrong time' 0463 if n: 0464 n = n[0] 0465 n = min(n, self.rlen) 0466 else: 0467 n = self.rlen 0468 self.rlen = self.rlen - n 0469 return self._read(n) 0470 0471 def close(self): 0472 if self.rlen: 0473 dummy = self.read_rsrc(self.rlen) 0474 self._checkcrc() 0475 self.state = _DID_RSRC 0476 self.ifp.close() 0477 0478 def hexbin(inp, out): 0479 """(infilename, outfilename) - Decode binhexed file""" 0480 ifp = HexBin(inp) 0481 finfo = ifp.FInfo 0482 if not out: 0483 out = ifp.FName 0484 if os.name == 'mac': 0485 ofss = macfs.FSSpec(out) 0486 out = ofss.as_pathname() 0487 0488 ofp = open(out, 'wb') 0489 # XXXX Do translation on non-mac systems 0490 while 1: 0491 d = ifp.read(128000) 0492 if not d: break 0493 ofp.write(d) 0494 ofp.close() 0495 ifp.close_data() 0496 0497 d = ifp.read_rsrc(128000) 0498 if d: 0499 ofp = openrsrc(out, 'wb') 0500 ofp.write(d) 0501 while 1: 0502 d = ifp.read_rsrc(128000) 0503 if not d: break 0504 ofp.write(d) 0505 ofp.close() 0506 0507 if os.name == 'mac': 0508 nfinfo = ofss.GetFInfo() 0509 nfinfo.Creator = finfo.Creator 0510 nfinfo.Type = finfo.Type 0511 nfinfo.Flags = finfo.Flags 0512 ofss.SetFInfo(nfinfo) 0513 0514 ifp.close() 0515 0516 def _test(): 0517 if os.name == 'mac': 0518 fss, ok = macfs.PromptGetFile('File to convert:') 0519 if not ok: 0520 sys.exit(0) 0521 fname = fss.as_pathname() 0522 else: 0523 fname = sys.argv[1] 0524 binhex(fname, fname+'.hqx') 0525 hexbin(fname+'.hqx', fname+'.viahqx') 0526 #hexbin(fname, fname+'.unpacked') 0527 sys.exit(1) 0528 0529 if __name__ == '__main__': 0530 _test() 0531
Generated by PyXR 0.9.4