home *** CD-ROM | disk | FTP | other *** search
/ linuxmafia.com 2016 / linuxmafia.com.tar / linuxmafia.com / pub / palmos / pippy-0.6beta-src.tar.gz / pippy-0.6beta-src.tar / pippy-0.6beta-src / src / Lib / binhex.py < prev    next >
Text File  |  2000-12-21  |  15KB  |  531 lines

  1. """Macintosh binhex compression/decompression.
  2.  
  3. easy interface:
  4. binhex(inputfilename, outputfilename)
  5. hexbin(inputfilename, outputfilename)
  6. """
  7.  
  8. #
  9. # Jack Jansen, CWI, August 1995.
  10. #
  11. # The module is supposed to be as compatible as possible. Especially the
  12. # easy interface should work "as expected" on any platform.
  13. # XXXX Note: currently, textfiles appear in mac-form on all platforms.
  14. # We seem to lack a simple character-translate in python.
  15. # (we should probably use ISO-Latin-1 on all but the mac platform).
  16. # XXXX The simeple routines are too simple: they expect to hold the complete
  17. # files in-core. Should be fixed.
  18. # XXXX It would be nice to handle AppleDouble format on unix
  19. # (for servers serving macs).
  20. # XXXX I don't understand what happens when you get 0x90 times the same byte on
  21. # input. The resulting code (xx 90 90) would appear to be interpreted as an
  22. # escaped *value* of 0x90. All coders I've seen appear to ignore this nicety...
  23. #
  24. import sys
  25. import os
  26. import struct
  27. import string
  28. import binascii
  29.     
  30. Error = 'binhex.Error'
  31.  
  32. # States (what have we written)
  33. [_DID_HEADER, _DID_DATA, _DID_RSRC] = range(3)
  34.  
  35. # Various constants
  36. REASONABLY_LARGE=32768  # Minimal amount we pass the rle-coder
  37. LINELEN=64
  38. RUNCHAR=chr(0x90)   # run-length introducer
  39.  
  40. #
  41. # This code is no longer byte-order dependent
  42.  
  43. #
  44. # Workarounds for non-mac machines.
  45. if os.name == 'mac':
  46.     import macfs
  47.     import MacOS
  48.     try:
  49.         openrf = MacOS.openrf
  50.     except AttributeError:
  51.         # Backward compatability
  52.         openrf = open
  53.     
  54.     def FInfo():
  55.         return macfs.FInfo()
  56.     
  57.     def getfileinfo(name):
  58.         finfo = macfs.FSSpec(name).GetFInfo()
  59.         dir, file = os.path.split(name)
  60.         # XXXX Get resource/data sizes
  61.         fp = open(name, 'rb')
  62.         fp.seek(0, 2)
  63.         dlen = fp.tell()
  64.         fp = openrf(name, '*rb')
  65.         fp.seek(0, 2)
  66.         rlen = fp.tell()
  67.         return file, finfo, dlen, rlen
  68.     
  69.     def openrsrc(name, *mode):
  70.         if not mode:
  71.             mode = '*rb'
  72.         else:
  73.             mode = '*' + mode[0]
  74.         return openrf(name, mode)
  75.  
  76. else:
  77.     #
  78.     # Glue code for non-macintosh useage
  79.     #
  80.     
  81.     class FInfo:
  82.         def __init__(self):
  83.             self.Type = '????'
  84.             self.Creator = '????'
  85.             self.Flags = 0
  86.  
  87.     def getfileinfo(name):
  88.         finfo = FInfo()
  89.         # Quick check for textfile
  90.         fp = open(name)
  91.         data = open(name).read(256)
  92.         for c in data:
  93.             if not c in string.whitespace \
  94.                 and (c<' ' or ord(c) > 0177):
  95.                 break
  96.         else:
  97.             finfo.Type = 'TEXT'
  98.         fp.seek(0, 2)
  99.         dsize = fp.tell()
  100.         fp.close()
  101.         dir, file = os.path.split(name)
  102.         file = string.replace(file, ':', '-', 1)
  103.         return file, finfo, dsize, 0
  104.  
  105.     class openrsrc:
  106.         def __init__(self, *args):
  107.             pass
  108.     
  109.         def read(self, *args):
  110.             return ''
  111.     
  112.         def write(self, *args):
  113.             pass
  114.             
  115.         def close(self):
  116.             pass
  117.     
  118. class _Hqxcoderengine:
  119.     """Write data to the coder in 3-byte chunks"""
  120.     
  121.     def __init__(self, ofp):
  122.         self.ofp = ofp
  123.         self.data = ''
  124.         self.hqxdata = ''
  125.         self.linelen = LINELEN-1
  126.  
  127.     def write(self, data):
  128.         self.data = self.data + data
  129.         datalen = len(self.data)
  130.         todo = (datalen/3)*3
  131.         data = self.data[:todo]
  132.         self.data = self.data[todo:]
  133.         if not data:
  134.             return
  135.         self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)
  136.         self._flush(0)
  137.  
  138.     def _flush(self, force):
  139.         first = 0
  140.         while first <= len(self.hqxdata)-self.linelen:
  141.             last = first + self.linelen
  142.             self.ofp.write(self.hqxdata[first:last]+'\n')
  143.             self.linelen = LINELEN
  144.             first = last
  145.         self.hqxdata = self.hqxdata[first:]
  146.         if force:
  147.             self.ofp.write(self.hqxdata + ':\n')
  148.  
  149.     def close(self):
  150.         if self.data:
  151.             self.hqxdata = \
  152.                  self.hqxdata + binascii.b2a_hqx(self.data)
  153.         self._flush(1)
  154.         self.ofp.close()
  155.         del self.ofp
  156.  
  157. class _Rlecoderengine:
  158.     """Write data to the RLE-coder in suitably large chunks"""
  159.  
  160.     def __init__(self, ofp):
  161.         self.ofp = ofp
  162.         self.data = ''
  163.  
  164.     def write(self, data):
  165.         self.data = self.data + data
  166.         if len(self.data) < REASONABLY_LARGE:
  167.             return
  168.         rledata = binascii.rlecode_hqx(self.data)
  169.         self.ofp.write(rledata)
  170.         self.data = ''
  171.  
  172.     def close(self):
  173.         if self.data:
  174.             rledata = binascii.rlecode_hqx(self.data)
  175.             self.ofp.write(rledata)
  176.         self.ofp.close()
  177.         del self.ofp
  178.  
  179. class BinHex:
  180.     def __init__(self, (name, finfo, dlen, rlen), ofp):
  181.         if type(ofp) == type(''):
  182.             ofname = ofp
  183.             ofp = open(ofname, 'w')
  184.             if os.name == 'mac':
  185.                 fss = macfs.FSSpec(ofname)
  186.                 fss.SetCreatorType('BnHq', 'TEXT')
  187.         ofp.write('(This file must be converted with BinHex 4.0)\n\n:')
  188.         hqxer = _Hqxcoderengine(ofp)
  189.         self.ofp = _Rlecoderengine(hqxer)
  190.         self.crc = 0
  191.         if finfo == None:
  192.             finfo = FInfo()
  193.         self.dlen = dlen
  194.         self.rlen = rlen
  195.         self._writeinfo(name, finfo)
  196.         self.state = _DID_HEADER
  197.  
  198.     def _writeinfo(self, name, finfo):
  199.         name = name
  200.         nl = len(name)
  201.         if nl > 63:
  202.             raise Error, 'Filename too long'
  203.         d = chr(nl) + name + '\0'
  204.         d2 = finfo.Type + finfo.Creator
  205.  
  206.         # Force all structs to be packed with big-endian
  207.         d3 = struct.pack('>h', finfo.Flags)
  208.         d4 = struct.pack('>ii', self.dlen, self.rlen)
  209.         info = d + d2 + d3 + d4
  210.         self._write(info)
  211.         self._writecrc()
  212.  
  213.     def _write(self, data):
  214.         self.crc = binascii.crc_hqx(data, self.crc)
  215.         self.ofp.write(data)
  216.  
  217.     def _writecrc(self):
  218.         # XXXX Should this be here??
  219.         # self.crc = binascii.crc_hqx('\0\0', self.crc)
  220.         self.ofp.write(struct.pack('>h', self.crc))
  221.         self.crc = 0
  222.  
  223.     def write(self, data):
  224.         if self.state != _DID_HEADER:
  225.             raise Error, 'Writing data at the wrong time'
  226.         self.dlen = self.dlen - len(data)
  227.         self._write(data)
  228.  
  229.     def close_data(self):
  230.         if self.dlen <> 0:
  231.             raise Error, 'Incorrect data size, diff='+`self.rlen`
  232.         self._writecrc()
  233.         self.state = _DID_DATA
  234.  
  235.     def write_rsrc(self, data):
  236.         if self.state < _DID_DATA:
  237.             self.close_data()
  238.         if self.state != _DID_DATA:
  239.             raise Error, 'Writing resource data at the wrong time'
  240.         self.rlen = self.rlen - len(data)
  241.         self._write(data)
  242.  
  243.     def close(self):
  244.         if self.state < _DID_DATA:
  245.             self.close_data()
  246.         if self.state != _DID_DATA:
  247.             raise Error, 'Close at the wrong time'
  248.         if self.rlen <> 0:
  249.             raise Error, \
  250.                   "Incorrect resource-datasize, diff="+`self.rlen`
  251.         self._writecrc()
  252.         self.ofp.close()
  253.         self.state = None
  254.         del self.ofp
  255.     
  256. def binhex(inp, out):
  257.     """(infilename, outfilename) - Create binhex-encoded copy of a file"""
  258.     finfo = getfileinfo(inp)
  259.     ofp = BinHex(finfo, out)
  260.     
  261.     ifp = open(inp, 'rb')
  262.     # XXXX Do textfile translation on non-mac systems
  263.     while 1:
  264.         d = ifp.read(128000)
  265.         if not d: break
  266.         ofp.write(d)
  267.     ofp.close_data()
  268.     ifp.close()
  269.  
  270.     ifp = openrsrc(inp, 'rb')
  271.     while 1:
  272.         d = ifp.read(128000)
  273.         if not d: break
  274.         ofp.write_rsrc(d)
  275.     ofp.close()
  276.     ifp.close() 
  277.  
  278. class _Hqxdecoderengine:
  279.     """Read data via the decoder in 4-byte chunks"""
  280.     
  281.     def __init__(self, ifp):
  282.         self.ifp = ifp
  283.         self.eof = 0
  284.  
  285.     def read(self, totalwtd):
  286.         """Read at least wtd bytes (or until EOF)"""
  287.         decdata = ''
  288.         wtd = totalwtd
  289.         #
  290.         # The loop here is convoluted, since we don't really now how 
  291.         # much to decode: there may be newlines in the incoming data.
  292.         while wtd > 0:
  293.             if self.eof: return decdata
  294.             wtd = ((wtd+2)/3)*4
  295.             data = self.ifp.read(wtd)
  296.             #
  297.             # Next problem: there may not be a complete number of
  298.             # bytes in what we pass to a2b. Solve by yet another
  299.             # loop.
  300.             #
  301.             while 1:
  302.                 try:
  303.                     decdatacur, self.eof = \
  304.                             binascii.a2b_hqx(data)
  305.                     break
  306.                 except binascii.Incomplete:
  307.                     pass
  308.                 newdata = self.ifp.read(1)
  309.                 if not newdata:
  310.                     raise Error, \
  311.                           'Premature EOF on binhex file'
  312.                 data = data + newdata
  313.             decdata = decdata + decdatacur
  314.             wtd = totalwtd - len(decdata)
  315.             if not decdata and not self.eof:
  316.                 raise Error, 'Premature EOF on binhex file'
  317.         return decdata
  318.  
  319.     def close(self):
  320.         self.ifp.close()
  321.  
  322. class _Rledecoderengine:
  323.     """Read data via the RLE-coder"""
  324.  
  325.     def __init__(self, ifp):
  326.         self.ifp = ifp
  327.         self.pre_buffer = ''
  328.         self.post_buffer = ''
  329.         self.eof = 0
  330.  
  331.     def read(self, wtd):
  332.         if wtd > len(self.post_buffer):
  333.             self._fill(wtd-len(self.post_buffer))
  334.         rv = self.post_buffer[:wtd]
  335.         self.post_buffer = self.post_buffer[wtd:]
  336.         return rv
  337.  
  338.     def _fill(self, wtd):
  339.         self.pre_buffer = self.pre_buffer + self.ifp.read(wtd+4)
  340.         if self.ifp.eof:
  341.             self.post_buffer = self.post_buffer + \
  342.                 binascii.rledecode_hqx(self.pre_buffer)
  343.             self.pre_buffer = ''
  344.             return
  345.             
  346.         #
  347.         # Obfuscated code ahead. We have to take care that we don't
  348.         # end up with an orphaned RUNCHAR later on. So, we keep a couple
  349.         # of bytes in the buffer, depending on what the end of
  350.         # the buffer looks like:
  351.         # '\220\0\220' - Keep 3 bytes: repeated \220 (escaped as \220\0)
  352.         # '?\220' - Keep 2 bytes: repeated something-else
  353.         # '\220\0' - Escaped \220: Keep 2 bytes.
  354.         # '?\220?' - Complete repeat sequence: decode all
  355.         # otherwise: keep 1 byte.
  356.         #
  357.         mark = len(self.pre_buffer)
  358.         if self.pre_buffer[-3:] == RUNCHAR + '\0' + RUNCHAR:
  359.             mark = mark - 3
  360.         elif self.pre_buffer[-1] == RUNCHAR:
  361.             mark = mark - 2
  362.         elif self.pre_buffer[-2:] == RUNCHAR + '\0':
  363.             mark = mark - 2
  364.         elif self.pre_buffer[-2] == RUNCHAR:
  365.             pass # Decode all
  366.         else:
  367.             mark = mark - 1
  368.  
  369.         self.post_buffer = self.post_buffer + \
  370.             binascii.rledecode_hqx(self.pre_buffer[:mark])
  371.         self.pre_buffer = self.pre_buffer[mark:]
  372.  
  373.     def close(self):
  374.         self.ifp.close()
  375.  
  376. class HexBin:
  377.     def __init__(self, ifp):
  378.         if type(ifp) == type(''):
  379.             ifp = open(ifp)
  380.         #
  381.         # Find initial colon.
  382.         #
  383.         while 1:
  384.             ch = ifp.read(1)
  385.             if not ch:
  386.                 raise Error, "No binhex data found"
  387.             # Cater for \r\n terminated lines (which show up as \n\r, hence
  388.             # all lines start with \r)
  389.             if ch == '\r':
  390.                 continue
  391.             if ch == ':':
  392.                 break
  393.             if ch != '\n':
  394.                 dummy = ifp.readline()
  395.             
  396.         hqxifp = _Hqxdecoderengine(ifp)
  397.         self.ifp = _Rledecoderengine(hqxifp)
  398.         self.crc = 0
  399.         self._readheader()
  400.         
  401.     def _read(self, len):
  402.         data = self.ifp.read(len)
  403.         self.crc = binascii.crc_hqx(data, self.crc)
  404.         return data
  405.         
  406.     def _checkcrc(self):
  407.         filecrc = struct.unpack('>h', self.ifp.read(2))[0] & 0xffff
  408.         #self.crc = binascii.crc_hqx('\0\0', self.crc)
  409.         # XXXX Is this needed??
  410.         self.crc = self.crc & 0xffff
  411.         if filecrc != self.crc:
  412.             raise Error, 'CRC error, computed %x, read %x' \
  413.                   %(self.crc, filecrc)
  414.         self.crc = 0
  415.  
  416.     def _readheader(self):
  417.         len = self._read(1)
  418.         fname = self._read(ord(len))
  419.         rest = self._read(1+4+4+2+4+4)
  420.         self._checkcrc()
  421.         
  422.         type = rest[1:5]
  423.         creator = rest[5:9]
  424.         flags = struct.unpack('>h', rest[9:11])[0]
  425.         self.dlen = struct.unpack('>l', rest[11:15])[0]
  426.         self.rlen = struct.unpack('>l', rest[15:19])[0]
  427.         
  428.         self.FName = fname
  429.         self.FInfo = FInfo()
  430.         self.FInfo.Creator = creator
  431.         self.FInfo.Type = type
  432.         self.FInfo.Flags = flags
  433.         
  434.         self.state = _DID_HEADER
  435.         
  436.     def read(self, *n):
  437.         if self.state != _DID_HEADER:
  438.             raise Error, 'Read data at wrong time'
  439.         if n:
  440.             n = n[0]
  441.             n = min(n, self.dlen)
  442.         else:
  443.             n = self.dlen
  444.         rv = ''
  445.         while len(rv) < n:
  446.             rv = rv + self._read(n-len(rv))
  447.         self.dlen = self.dlen - n
  448.         return rv
  449.         
  450.     def close_data(self):
  451.         if self.state != _DID_HEADER:
  452.             raise Error, 'close_data at wrong time'
  453.         if self.dlen:
  454.             dummy = self._read(self.dlen)
  455.         self._checkcrc()
  456.         self.state = _DID_DATA
  457.         
  458.     def read_rsrc(self, *n):
  459.         if self.state == _DID_HEADER:
  460.             self.close_data()
  461.         if self.state != _DID_DATA:
  462.             raise Error, 'Read resource data at wrong time'
  463.         if n:
  464.             n = n[0]
  465.             n = min(n, self.rlen)
  466.         else:
  467.             n = self.rlen
  468.         self.rlen = self.rlen - n
  469.         return self._read(n)
  470.         
  471.     def close(self):
  472.         if self.rlen:
  473.             dummy = self.read_rsrc(self.rlen)
  474.         self._checkcrc()
  475.         self.state = _DID_RSRC
  476.         self.ifp.close()
  477.         
  478. def hexbin(inp, out):
  479.     """(infilename, outfilename) - Decode binhexed file"""
  480.     ifp = HexBin(inp)
  481.     finfo = ifp.FInfo
  482.     if not out:
  483.         out = ifp.FName
  484.     if os.name == 'mac':
  485.         ofss = macfs.FSSpec(out)
  486.         out = ofss.as_pathname()
  487.  
  488.     ofp = open(out, 'wb')
  489.     # XXXX Do translation on non-mac systems
  490.     while 1:
  491.         d = ifp.read(128000)
  492.         if not d: break
  493.         ofp.write(d)
  494.     ofp.close()
  495.     ifp.close_data()
  496.     
  497.     d = ifp.read_rsrc(128000)
  498.     if d:
  499.         ofp = openrsrc(out, 'wb')
  500.         ofp.write(d)
  501.         while 1:
  502.             d = ifp.read_rsrc(128000)
  503.             if not d: break
  504.             ofp.write(d)
  505.         ofp.close()
  506.  
  507.     if os.name == 'mac':
  508.         nfinfo = ofss.GetFInfo()
  509.         nfinfo.Creator = finfo.Creator
  510.         nfinfo.Type = finfo.Type
  511.         nfinfo.Flags = finfo.Flags
  512.         ofss.SetFInfo(nfinfo)
  513.     
  514.     ifp.close()
  515.  
  516. def _test():
  517.     if os.name == 'mac':
  518.         fss, ok = macfs.PromptGetFile('File to convert:')
  519.         if not ok:
  520.             sys.exit(0)
  521.         fname = fss.as_pathname()
  522.     else:
  523.         fname = sys.argv[1]
  524.     binhex(fname, fname+'.hqx')
  525.     hexbin(fname+'.hqx', fname+'.viahqx')
  526.     #hexbin(fname, fname+'.unpacked')
  527.     sys.exit(1)
  528.     
  529. if __name__ == '__main__':
  530.     _test()
  531.