1 """binhex - Macintosh binhex compression/decompression
3 binhex(inputfilename, outputfilename)
4 hexbin(inputfilename, outputfilename)
8 # Jack Jansen, CWI, August 1995.
10 # The module is supposed to be as compatible as possible. Especially the
11 # easy interface should work "as expected" on any platform.
12 # XXXX Note: currently, textfiles appear in mac-form on all platforms.
13 # We seem to lack a simple character-translate in python.
14 # (we should probably use ISO-Latin-1 on all but the mac platform).
15 # XXXX The simeple routines are too simple: they expect to hold the complete
16 # files in-core. Should be fixed.
17 # XXXX It would be nice to handle AppleDouble format on unix (for servers serving
19 # XXXX I don't understand what happens when you get 0x90 times the same byte on
20 # input. The resulting code (xx 90 90) would appear to be interpreted as an
21 # escaped *value* of 0x90. All coders I've seen appear to ignore this nicety...
31 testf
=open('@binhex.dbg.out', 'w')
33 Error
= 'binhex.Error'
35 # States (what have we written)
36 [_DID_HEADER
, _DID_DATA
, _DID_RSRC
] = range(3)
39 REASONABLY_LARGE
=32768 # Minimal amount we pass the rle-coder
40 LINELEN
=48 # What we pass to hqx-coder at once
41 # *NOTE* Must be divisible by 3!
42 RUNCHAR
=chr(0x90) # run-length introducer
45 # The code is currently byte-order dependent
46 if struct
.pack('i', 0177) != '\0\0\0\177':
47 raise ImportError, 'Module binhex is big-endian only'
50 # Workarounds for non-mac machines.
57 def getfileinfo(name
):
58 finfo
= macfs
.FSSpec(name
).GetFInfo()
59 dir, file = os
.path
.split(name
)
60 # XXXX Get resource/data sizes
64 fp
= open(name
, '*rb')
67 return file, finfo
, dlen
, rlen
69 def openrsrc(name
, *mode
):
75 return open(name
, mode
)
79 # Glue code for non-macintosh useage
89 def getfileinfo(name
):
91 # Quick check for textfile
93 data
= open(name
).read(256)
95 if not c
in string
.whitespace
and (c
<' ' or ord(c
) > 0177):
102 dir, file = os
.path
.split(name
)
103 file = regsub
.sub(':', '-', file)
104 return file, finfo
, dsize
, 0
107 def __init__(self
, *args
):
110 def read(self
, *args
):
113 def write(self
, *args
):
119 class _Hqxcoderengine
:
120 """Write data to the coder in 3-byte chunks"""
122 def __init__(self
, ofp
):
126 def write(self
, data
):
127 self
.data
= self
.data
+ data
128 while len(self
.data
) > LINELEN
:
129 hqxdata
= binascii
.b2a_hqx(self
.data
[:LINELEN
])
130 self
.ofp
.write(hqxdata
+'\n')
131 self
.data
= self
.data
[LINELEN
:]
135 self
.ofp
.write(binascii
.b2a_hqx(self
.data
))
136 self
.ofp
.write(':\n')
139 class _Rlecoderengine
:
140 """Write data to the RLE-coder in suitably large chunks"""
142 def __init__(self
, ofp
):
146 def write(self
, data
):
148 testf
.write(data
) # XXXX
149 self
.data
= self
.data
+ data
150 if len(self
.data
) < REASONABLY_LARGE
:
152 rledata
= binascii
.rlecode_hqx(self
.data
)
153 self
.ofp
.write(rledata
)
158 rledata
= binascii
.rlecode_hqx(self
.data
)
159 self
.ofp
.write(rledata
)
163 def __init__(self
, (name
, finfo
, dlen
, rlen
), ofp
):
164 if type(ofp
) == type(''):
166 ofp
= open(ofname
, 'w')
168 fss
= macfs
.FSSpec(ofname
)
169 fss
.SetCreatorType('BnHq', 'TEXT')
170 ofp
.write('(This file may be decompressed with BinHex 4.0)\n\n:')
171 hqxer
= _Hqxcoderengine(ofp
)
172 self
.ofp
= _Rlecoderengine(hqxer
)
178 self
._writeinfo
(name
, finfo
)
179 self
.state
= _DID_HEADER
181 def _writeinfo(self
, name
, finfo
):
183 print 'binhex info:', name
, finfo
.Type
, finfo
.Creator
, self
.dlen
, self
.rlen
187 raise Error
, 'Filename too long'
188 d
= chr(nl
) + name
+ '\0'
189 d2
= finfo
.Type
+ finfo
.Creator
190 d3
= struct
.pack('h', finfo
.Flags
)
191 d4
= struct
.pack('ii', self
.dlen
, self
.rlen
)
192 info
= d
+ d2
+ d3
+ d4
196 def _write(self
, data
):
197 self
.crc
= binascii
.crc_hqx(data
, self
.crc
)
201 ## self.crc = binascii.crc_hqx('\0\0', self.crc) # XXXX Should this be here??
202 self
.ofp
.write(struct
.pack('h', self
.crc
))
205 def write(self
, data
):
206 if self
.state
!= _DID_HEADER
:
207 raise Error
, 'Writing data at the wrong time'
208 self
.dlen
= self
.dlen
- len(data
)
211 def close_data(self
):
213 raise Error
, 'Incorrect data size, diff='+`self
.rlen`
215 self
.state
= _DID_DATA
217 def write_rsrc(self
, data
):
218 if self
.state
< _DID_DATA
:
220 if self
.state
!= _DID_DATA
:
221 raise Error
, 'Writing resource data at the wrong time'
222 self
.rlen
= self
.rlen
- len(data
)
226 if self
.state
< _DID_DATA
:
228 if self
.state
!= _DID_DATA
:
229 raise Error
, 'Close at the wrong time'
231 raise Error
, "Incorrect resource-datasize, diff="+`self
.rlen`
236 def binhex(inp
, out
):
237 """(infilename, outfilename) - Create binhex-encoded copy of a file"""
238 finfo
= getfileinfo(inp
)
239 ofp
= BinHex(finfo
, out
)
241 ifp
= open(inp
, 'rb')
242 # XXXX Do textfile translation on non-mac systems
248 ifp
= openrsrc(inp
, 'rb')
254 class _Hqxdecoderengine
:
255 """Read data via the decoder in 4-byte chunks"""
257 def __init__(self
, ifp
):
261 def read(self
, totalwtd
):
262 """Read at least wtd bytes (or until EOF)"""
266 # The loop here is convoluted, since we don't really now how much
267 # to decode: there may be newlines in the incoming data.
269 if self
.eof
: return decdata
271 data
= self
.ifp
.read(wtd
)
273 # Next problem: there may not be a complete number of bytes in what we
274 # pass to a2b. Solve by yet another loop.
278 decdatacur
, self
.eof
= binascii
.a2b_hqx(data
)
279 if self
.eof
: print 'EOF'
281 except binascii
.Incomplete
:
283 newdata
= self
.ifp
.read(1)
285 raise Error
, 'Premature EOF on binhex file'
286 data
= data
+ newdata
287 decdata
= decdata
+ decdatacur
288 wtd
= totalwtd
- len(decdata
)
289 if not decdata
and not self
.eof
:
290 raise Error
, 'Premature EOF on binhex file'
296 class _Rledecoderengine
:
297 """Read data via the RLE-coder"""
299 def __init__(self
, ifp
):
302 self
.post_buffer
= ''
306 if wtd
> len(self
.post_buffer
):
307 self
._fill
(wtd
-len(self
.post_buffer
))
308 rv
= self
.post_buffer
[:wtd
]
309 self
.post_buffer
= self
.post_buffer
[wtd
:]
310 print 'WTD', wtd
, 'GOT', len(rv
)
313 def _fill(self
, wtd
):
315 # Obfuscated code ahead. We keep at least one byte in the pre_buffer,
316 # so we don't stumble over an orphaned RUNCHAR later on. If the
317 # last or second-last char is a RUNCHAR we keep more bytes.
319 self
.pre_buffer
= self
.pre_buffer
+ self
.ifp
.read(wtd
+2)
321 self
.post_buffer
= self
.post_buffer
+ \
322 binascii
.rledecode_hqx(self
.pre_buffer
)
326 lastrle
= string
.rfind(self
.pre_buffer
, RUNCHAR
)
327 if lastrle
> 0 and lastrle
== len(self
.pre_buffer
)-1:
328 # Last byte is an RLE, keep two bytes
329 mark
= len(self
.pre_buffer
)-2
330 elif lastrle
> 0 and lastrle
== len(self
.pre_buffer
)-2:
331 # second-last byte is an RLE. Decode all.
332 mark
= len(self
.pre_buffer
)
334 mark
= len(self
.pre_buffer
)-1
335 self
.post_buffer
= self
.post_buffer
+ \
336 binascii
.rledecode_hqx(self
.pre_buffer
[:mark
])
337 self
.pre_buffer
= self
.pre_buffer
[mark
:]
343 def __init__(self
, ifp
):
344 if type(ifp
) == type(''):
347 # Find initial colon.
352 raise Error
, "No binhex data found"
356 dummy
= ifp
.readline()
358 print 'SKIP:', ch
+dummy
360 hqxifp
= _Hqxdecoderengine(ifp
)
361 self
.ifp
= _Rledecoderengine(hqxifp
)
365 def _read(self
, len):
366 data
= self
.ifp
.read(len)
367 self
.crc
= binascii
.crc_hqx(data
, self
.crc
)
371 filecrc
= struct
.unpack('h', self
.ifp
.read(2))[0] & 0xffff
372 ## self.crc = binascii.crc_hqx('\0\0', self.crc) # XXXX Is this needed??
373 self
.crc
= self
.crc
& 0xffff
375 print 'DBG CRC %x %x'%(self
.crc
, filecrc
)
376 if filecrc
!= self
.crc
:
377 raise Error
, 'CRC error, computed %x, read %x'%(self
.crc
, filecrc
)
380 def _readheader(self
):
382 fname
= self
._read
(ord(len))
383 rest
= self
._read
(1+4+4+2+4+4)
388 flags
= struct
.unpack('h', rest
[9:11])[0]
389 self
.dlen
= struct
.unpack('l', rest
[11:15])[0]
390 self
.rlen
= struct
.unpack('l', rest
[15:19])[0]
393 print 'DATA, RLEN', self
.dlen
, self
.rlen
397 self
.FInfo
.Creator
= creator
398 self
.FInfo
.Type
= type
399 self
.FInfo
.Flags
= flags
401 self
.state
= _DID_HEADER
404 if self
.state
!= _DID_HEADER
:
405 raise Error
, 'Read data at wrong time'
408 n
= min(n
, self
.dlen
)
411 self
.dlen
= self
.dlen
- n
414 def close_data(self
):
415 if self
.state
!= _DID_HEADER
:
416 raise Error
, 'close_data at wrong time'
418 dummy
= self
._read
(self
.dlen
)
420 self
.state
= _DID_DATA
422 def read_rsrc(self
, *n
):
423 if self
.state
== _DID_HEADER
:
425 if self
.state
!= _DID_DATA
:
426 raise Error
, 'Read resource data at wrong time'
429 n
= min(n
, self
.rlen
)
432 self
.rlen
= self
.rlen
- n
437 dummy
= self
.read_rsrc(self
.rlen
)
439 self
.state
= _DID_RSRC
442 def hexbin(inp
, out
):
443 """(infilename, outfilename) - Decode binhexed file"""
449 ofss
= macfs
.FSSpec(out
)
450 out
= ofss
.as_pathname()
452 ofp
= open(out
, 'wb')
453 # XXXX Do translation on non-mac systems
461 ofp
= openrsrc(out
, 'wb')
466 nfinfo
= ofss
.GetFInfo()
467 nfinfo
.Creator
= finfo
.Creator
468 nfinfo
.Type
= finfo
.Type
469 nfinfo
.Flags
= finfo
.Flags
470 ofss
.SetFInfo(nfinfo
)
476 fss
, ok
= macfs
.PromptGetFile('File to convert:')
479 fname
= fss
.as_pathname()
482 #binhex(fname, fname+'.hqx')
483 #hexbin(fname+'.hqx', fname+'.viahqx')
484 hexbin(fname
, fname
+'.unpacked')
487 if __name__
== '__main__':