PyXR

c:\python24\lib \ binhex.py



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
SourceForge.net Logo