+++ /dev/null
-"""Macintosh binhex compression/decompression.\r
-\r
-easy interface:\r
-binhex(inputfilename, outputfilename)\r
-hexbin(inputfilename, outputfilename)\r
-"""\r
-\r
-#\r
-# Jack Jansen, CWI, August 1995.\r
-#\r
-# The module is supposed to be as compatible as possible. Especially the\r
-# easy interface should work "as expected" on any platform.\r
-# XXXX Note: currently, textfiles appear in mac-form on all platforms.\r
-# We seem to lack a simple character-translate in python.\r
-# (we should probably use ISO-Latin-1 on all but the mac platform).\r
-# XXXX The simple routines are too simple: they expect to hold the complete\r
-# files in-core. Should be fixed.\r
-# XXXX It would be nice to handle AppleDouble format on unix\r
-# (for servers serving macs).\r
-# XXXX I don't understand what happens when you get 0x90 times the same byte on\r
-# input. The resulting code (xx 90 90) would appear to be interpreted as an\r
-# escaped *value* of 0x90. All coders I've seen appear to ignore this nicety...\r
-#\r
-import sys\r
-import os\r
-import struct\r
-import binascii\r
-\r
-__all__ = ["binhex","hexbin","Error"]\r
-\r
-class Error(Exception):\r
- pass\r
-\r
-# States (what have we written)\r
-[_DID_HEADER, _DID_DATA, _DID_RSRC] = range(3)\r
-\r
-# Various constants\r
-REASONABLY_LARGE=32768 # Minimal amount we pass the rle-coder\r
-LINELEN=64\r
-RUNCHAR=chr(0x90) # run-length introducer\r
-\r
-#\r
-# This code is no longer byte-order dependent\r
-\r
-#\r
-# Workarounds for non-mac machines.\r
-try:\r
- from Carbon.File import FSSpec, FInfo\r
- from MacOS import openrf\r
-\r
- def getfileinfo(name):\r
- finfo = FSSpec(name).FSpGetFInfo()\r
- dir, file = os.path.split(name)\r
- # XXX Get resource/data sizes\r
- fp = open(name, 'rb')\r
- fp.seek(0, 2)\r
- dlen = fp.tell()\r
- fp = openrf(name, '*rb')\r
- fp.seek(0, 2)\r
- rlen = fp.tell()\r
- return file, finfo, dlen, rlen\r
-\r
- def openrsrc(name, *mode):\r
- if not mode:\r
- mode = '*rb'\r
- else:\r
- mode = '*' + mode[0]\r
- return openrf(name, mode)\r
-\r
-except ImportError:\r
- #\r
- # Glue code for non-macintosh usage\r
- #\r
-\r
- class FInfo:\r
- def __init__(self):\r
- self.Type = '????'\r
- self.Creator = '????'\r
- self.Flags = 0\r
-\r
- def getfileinfo(name):\r
- finfo = FInfo()\r
- # Quick check for textfile\r
- fp = open(name)\r
- data = open(name).read(256)\r
- for c in data:\r
- if not c.isspace() and (c<' ' or ord(c) > 0x7f):\r
- break\r
- else:\r
- finfo.Type = 'TEXT'\r
- fp.seek(0, 2)\r
- dsize = fp.tell()\r
- fp.close()\r
- dir, file = os.path.split(name)\r
- file = file.replace(':', '-', 1)\r
- return file, finfo, dsize, 0\r
-\r
- class openrsrc:\r
- def __init__(self, *args):\r
- pass\r
-\r
- def read(self, *args):\r
- return ''\r
-\r
- def write(self, *args):\r
- pass\r
-\r
- def close(self):\r
- pass\r
-\r
-class _Hqxcoderengine:\r
- """Write data to the coder in 3-byte chunks"""\r
-\r
- def __init__(self, ofp):\r
- self.ofp = ofp\r
- self.data = ''\r
- self.hqxdata = ''\r
- self.linelen = LINELEN-1\r
-\r
- def write(self, data):\r
- self.data = self.data + data\r
- datalen = len(self.data)\r
- todo = (datalen//3)*3\r
- data = self.data[:todo]\r
- self.data = self.data[todo:]\r
- if not data:\r
- return\r
- self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)\r
- self._flush(0)\r
-\r
- def _flush(self, force):\r
- first = 0\r
- while first <= len(self.hqxdata)-self.linelen:\r
- last = first + self.linelen\r
- self.ofp.write(self.hqxdata[first:last]+'\n')\r
- self.linelen = LINELEN\r
- first = last\r
- self.hqxdata = self.hqxdata[first:]\r
- if force:\r
- self.ofp.write(self.hqxdata + ':\n')\r
-\r
- def close(self):\r
- if self.data:\r
- self.hqxdata = \\r
- self.hqxdata + binascii.b2a_hqx(self.data)\r
- self._flush(1)\r
- self.ofp.close()\r
- del self.ofp\r
-\r
-class _Rlecoderengine:\r
- """Write data to the RLE-coder in suitably large chunks"""\r
-\r
- def __init__(self, ofp):\r
- self.ofp = ofp\r
- self.data = ''\r
-\r
- def write(self, data):\r
- self.data = self.data + data\r
- if len(self.data) < REASONABLY_LARGE:\r
- return\r
- rledata = binascii.rlecode_hqx(self.data)\r
- self.ofp.write(rledata)\r
- self.data = ''\r
-\r
- def close(self):\r
- if self.data:\r
- rledata = binascii.rlecode_hqx(self.data)\r
- self.ofp.write(rledata)\r
- self.ofp.close()\r
- del self.ofp\r
-\r
-class BinHex:\r
- def __init__(self, name_finfo_dlen_rlen, ofp):\r
- name, finfo, dlen, rlen = name_finfo_dlen_rlen\r
- if type(ofp) == type(''):\r
- ofname = ofp\r
- ofp = open(ofname, 'w')\r
- ofp.write('(This file must be converted with BinHex 4.0)\n\n:')\r
- hqxer = _Hqxcoderengine(ofp)\r
- self.ofp = _Rlecoderengine(hqxer)\r
- self.crc = 0\r
- if finfo is None:\r
- finfo = FInfo()\r
- self.dlen = dlen\r
- self.rlen = rlen\r
- self._writeinfo(name, finfo)\r
- self.state = _DID_HEADER\r
-\r
- def _writeinfo(self, name, finfo):\r
- nl = len(name)\r
- if nl > 63:\r
- raise Error, 'Filename too long'\r
- d = chr(nl) + name + '\0'\r
- d2 = finfo.Type + finfo.Creator\r
-\r
- # Force all structs to be packed with big-endian\r
- d3 = struct.pack('>h', finfo.Flags)\r
- d4 = struct.pack('>ii', self.dlen, self.rlen)\r
- info = d + d2 + d3 + d4\r
- self._write(info)\r
- self._writecrc()\r
-\r
- def _write(self, data):\r
- self.crc = binascii.crc_hqx(data, self.crc)\r
- self.ofp.write(data)\r
-\r
- def _writecrc(self):\r
- # XXXX Should this be here??\r
- # self.crc = binascii.crc_hqx('\0\0', self.crc)\r
- if self.crc < 0:\r
- fmt = '>h'\r
- else:\r
- fmt = '>H'\r
- self.ofp.write(struct.pack(fmt, self.crc))\r
- self.crc = 0\r
-\r
- def write(self, data):\r
- if self.state != _DID_HEADER:\r
- raise Error, 'Writing data at the wrong time'\r
- self.dlen = self.dlen - len(data)\r
- self._write(data)\r
-\r
- def close_data(self):\r
- if self.dlen != 0:\r
- raise Error, 'Incorrect data size, diff=%r' % (self.rlen,)\r
- self._writecrc()\r
- self.state = _DID_DATA\r
-\r
- def write_rsrc(self, data):\r
- if self.state < _DID_DATA:\r
- self.close_data()\r
- if self.state != _DID_DATA:\r
- raise Error, 'Writing resource data at the wrong time'\r
- self.rlen = self.rlen - len(data)\r
- self._write(data)\r
-\r
- def close(self):\r
- if self.state < _DID_DATA:\r
- self.close_data()\r
- if self.state != _DID_DATA:\r
- raise Error, 'Close at the wrong time'\r
- if self.rlen != 0:\r
- raise Error, \\r
- "Incorrect resource-datasize, diff=%r" % (self.rlen,)\r
- self._writecrc()\r
- self.ofp.close()\r
- self.state = None\r
- del self.ofp\r
-\r
-def binhex(inp, out):\r
- """(infilename, outfilename) - Create binhex-encoded copy of a file"""\r
- finfo = getfileinfo(inp)\r
- ofp = BinHex(finfo, out)\r
-\r
- ifp = open(inp, 'rb')\r
- # XXXX Do textfile translation on non-mac systems\r
- while 1:\r
- d = ifp.read(128000)\r
- if not d: break\r
- ofp.write(d)\r
- ofp.close_data()\r
- ifp.close()\r
-\r
- ifp = openrsrc(inp, 'rb')\r
- while 1:\r
- d = ifp.read(128000)\r
- if not d: break\r
- ofp.write_rsrc(d)\r
- ofp.close()\r
- ifp.close()\r
-\r
-class _Hqxdecoderengine:\r
- """Read data via the decoder in 4-byte chunks"""\r
-\r
- def __init__(self, ifp):\r
- self.ifp = ifp\r
- self.eof = 0\r
-\r
- def read(self, totalwtd):\r
- """Read at least wtd bytes (or until EOF)"""\r
- decdata = ''\r
- wtd = totalwtd\r
- #\r
- # The loop here is convoluted, since we don't really now how\r
- # much to decode: there may be newlines in the incoming data.\r
- while wtd > 0:\r
- if self.eof: return decdata\r
- wtd = ((wtd+2)//3)*4\r
- data = self.ifp.read(wtd)\r
- #\r
- # Next problem: there may not be a complete number of\r
- # bytes in what we pass to a2b. Solve by yet another\r
- # loop.\r
- #\r
- while 1:\r
- try:\r
- decdatacur, self.eof = \\r
- binascii.a2b_hqx(data)\r
- break\r
- except binascii.Incomplete:\r
- pass\r
- newdata = self.ifp.read(1)\r
- if not newdata:\r
- raise Error, \\r
- 'Premature EOF on binhex file'\r
- data = data + newdata\r
- decdata = decdata + decdatacur\r
- wtd = totalwtd - len(decdata)\r
- if not decdata and not self.eof:\r
- raise Error, 'Premature EOF on binhex file'\r
- return decdata\r
-\r
- def close(self):\r
- self.ifp.close()\r
-\r
-class _Rledecoderengine:\r
- """Read data via the RLE-coder"""\r
-\r
- def __init__(self, ifp):\r
- self.ifp = ifp\r
- self.pre_buffer = ''\r
- self.post_buffer = ''\r
- self.eof = 0\r
-\r
- def read(self, wtd):\r
- if wtd > len(self.post_buffer):\r
- self._fill(wtd-len(self.post_buffer))\r
- rv = self.post_buffer[:wtd]\r
- self.post_buffer = self.post_buffer[wtd:]\r
- return rv\r
-\r
- def _fill(self, wtd):\r
- self.pre_buffer = self.pre_buffer + self.ifp.read(wtd+4)\r
- if self.ifp.eof:\r
- self.post_buffer = self.post_buffer + \\r
- binascii.rledecode_hqx(self.pre_buffer)\r
- self.pre_buffer = ''\r
- return\r
-\r
- #\r
- # Obfuscated code ahead. We have to take care that we don't\r
- # end up with an orphaned RUNCHAR later on. So, we keep a couple\r
- # of bytes in the buffer, depending on what the end of\r
- # the buffer looks like:\r
- # '\220\0\220' - Keep 3 bytes: repeated \220 (escaped as \220\0)\r
- # '?\220' - Keep 2 bytes: repeated something-else\r
- # '\220\0' - Escaped \220: Keep 2 bytes.\r
- # '?\220?' - Complete repeat sequence: decode all\r
- # otherwise: keep 1 byte.\r
- #\r
- mark = len(self.pre_buffer)\r
- if self.pre_buffer[-3:] == RUNCHAR + '\0' + RUNCHAR:\r
- mark = mark - 3\r
- elif self.pre_buffer[-1] == RUNCHAR:\r
- mark = mark - 2\r
- elif self.pre_buffer[-2:] == RUNCHAR + '\0':\r
- mark = mark - 2\r
- elif self.pre_buffer[-2] == RUNCHAR:\r
- pass # Decode all\r
- else:\r
- mark = mark - 1\r
-\r
- self.post_buffer = self.post_buffer + \\r
- binascii.rledecode_hqx(self.pre_buffer[:mark])\r
- self.pre_buffer = self.pre_buffer[mark:]\r
-\r
- def close(self):\r
- self.ifp.close()\r
-\r
-class HexBin:\r
- def __init__(self, ifp):\r
- if type(ifp) == type(''):\r
- ifp = open(ifp)\r
- #\r
- # Find initial colon.\r
- #\r
- while 1:\r
- ch = ifp.read(1)\r
- if not ch:\r
- raise Error, "No binhex data found"\r
- # Cater for \r\n terminated lines (which show up as \n\r, hence\r
- # all lines start with \r)\r
- if ch == '\r':\r
- continue\r
- if ch == ':':\r
- break\r
- if ch != '\n':\r
- dummy = ifp.readline()\r
-\r
- hqxifp = _Hqxdecoderengine(ifp)\r
- self.ifp = _Rledecoderengine(hqxifp)\r
- self.crc = 0\r
- self._readheader()\r
-\r
- def _read(self, len):\r
- data = self.ifp.read(len)\r
- self.crc = binascii.crc_hqx(data, self.crc)\r
- return data\r
-\r
- def _checkcrc(self):\r
- filecrc = struct.unpack('>h', self.ifp.read(2))[0] & 0xffff\r
- #self.crc = binascii.crc_hqx('\0\0', self.crc)\r
- # XXXX Is this needed??\r
- self.crc = self.crc & 0xffff\r
- if filecrc != self.crc:\r
- raise Error, 'CRC error, computed %x, read %x' \\r
- %(self.crc, filecrc)\r
- self.crc = 0\r
-\r
- def _readheader(self):\r
- len = self._read(1)\r
- fname = self._read(ord(len))\r
- rest = self._read(1+4+4+2+4+4)\r
- self._checkcrc()\r
-\r
- type = rest[1:5]\r
- creator = rest[5:9]\r
- flags = struct.unpack('>h', rest[9:11])[0]\r
- self.dlen = struct.unpack('>l', rest[11:15])[0]\r
- self.rlen = struct.unpack('>l', rest[15:19])[0]\r
-\r
- self.FName = fname\r
- self.FInfo = FInfo()\r
- self.FInfo.Creator = creator\r
- self.FInfo.Type = type\r
- self.FInfo.Flags = flags\r
-\r
- self.state = _DID_HEADER\r
-\r
- def read(self, *n):\r
- if self.state != _DID_HEADER:\r
- raise Error, 'Read data at wrong time'\r
- if n:\r
- n = n[0]\r
- n = min(n, self.dlen)\r
- else:\r
- n = self.dlen\r
- rv = ''\r
- while len(rv) < n:\r
- rv = rv + self._read(n-len(rv))\r
- self.dlen = self.dlen - n\r
- return rv\r
-\r
- def close_data(self):\r
- if self.state != _DID_HEADER:\r
- raise Error, 'close_data at wrong time'\r
- if self.dlen:\r
- dummy = self._read(self.dlen)\r
- self._checkcrc()\r
- self.state = _DID_DATA\r
-\r
- def read_rsrc(self, *n):\r
- if self.state == _DID_HEADER:\r
- self.close_data()\r
- if self.state != _DID_DATA:\r
- raise Error, 'Read resource data at wrong time'\r
- if n:\r
- n = n[0]\r
- n = min(n, self.rlen)\r
- else:\r
- n = self.rlen\r
- self.rlen = self.rlen - n\r
- return self._read(n)\r
-\r
- def close(self):\r
- if self.rlen:\r
- dummy = self.read_rsrc(self.rlen)\r
- self._checkcrc()\r
- self.state = _DID_RSRC\r
- self.ifp.close()\r
-\r
-def hexbin(inp, out):\r
- """(infilename, outfilename) - Decode binhexed file"""\r
- ifp = HexBin(inp)\r
- finfo = ifp.FInfo\r
- if not out:\r
- out = ifp.FName\r
-\r
- ofp = open(out, 'wb')\r
- # XXXX Do translation on non-mac systems\r
- while 1:\r
- d = ifp.read(128000)\r
- if not d: break\r
- ofp.write(d)\r
- ofp.close()\r
- ifp.close_data()\r
-\r
- d = ifp.read_rsrc(128000)\r
- if d:\r
- ofp = openrsrc(out, 'wb')\r
- ofp.write(d)\r
- while 1:\r
- d = ifp.read_rsrc(128000)\r
- if not d: break\r
- ofp.write(d)\r
- ofp.close()\r
-\r
- ifp.close()\r
-\r
-def _test():\r
- fname = sys.argv[1]\r
- binhex(fname, fname+'.hqx')\r
- hexbin(fname+'.hqx', fname+'.viahqx')\r
- #hexbin(fname, fname+'.unpacked')\r
- sys.exit(1)\r
-\r
-if __name__ == '__main__':\r
- _test()\r