1 """Macintosh binhex compression/decompression.
4 binhex(inputfilename, outputfilename)
5 hexbin(inputfilename, outputfilename)
9 # Jack Jansen, CWI, August 1995.
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 simple 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...
29 __all__
= ["binhex","hexbin","Error"]
31 class Error(Exception):
34 # States (what have we written)
35 [_DID_HEADER
, _DID_DATA
, _DID_RSRC
] = range(3)
38 REASONABLY_LARGE
=32768 # Minimal amount we pass the rle-coder
40 RUNCHAR
=chr(0x90) # run-length introducer
43 # This code is no longer byte-order dependent
46 # Workarounds for non-mac machines.
52 except AttributeError:
53 # Backward compatibility
59 def getfileinfo(name
):
60 finfo
= macfs
.FSSpec(name
).GetFInfo()
61 dir, file = os
.path
.split(name
)
62 # XXXX Get resource/data sizes
66 fp
= openrf(name
, '*rb')
69 return file, finfo
, dlen
, rlen
71 def openrsrc(name
, *mode
):
76 return openrf(name
, mode
)
80 # Glue code for non-macintosh usage
89 def getfileinfo(name
):
91 # Quick check for textfile
93 data
= open(name
).read(256)
95 if not c
.isspace() and (c
<' ' or ord(c
) > 0x7f):
102 dir, file = os
.path
.split(name
)
103 file = file.replace(':', '-', 1)
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 self
.linelen
= LINELEN
-1
128 def write(self
, data
):
129 self
.data
= self
.data
+ data
130 datalen
= len(self
.data
)
131 todo
= (datalen
//3)*3
132 data
= self
.data
[:todo
]
133 self
.data
= self
.data
[todo
:]
136 self
.hqxdata
= self
.hqxdata
+ binascii
.b2a_hqx(data
)
139 def _flush(self
, force
):
141 while first
<= len(self
.hqxdata
)-self
.linelen
:
142 last
= first
+ self
.linelen
143 self
.ofp
.write(self
.hqxdata
[first
:last
]+'\n')
144 self
.linelen
= LINELEN
146 self
.hqxdata
= self
.hqxdata
[first
:]
148 self
.ofp
.write(self
.hqxdata
+ ':\n')
153 self
.hqxdata
+ binascii
.b2a_hqx(self
.data
)
158 class _Rlecoderengine
:
159 """Write data to the RLE-coder in suitably large chunks"""
161 def __init__(self
, ofp
):
165 def write(self
, data
):
166 self
.data
= self
.data
+ data
167 if len(self
.data
) < REASONABLY_LARGE
:
169 rledata
= binascii
.rlecode_hqx(self
.data
)
170 self
.ofp
.write(rledata
)
175 rledata
= binascii
.rlecode_hqx(self
.data
)
176 self
.ofp
.write(rledata
)
181 def __init__(self
, (name
, finfo
, dlen
, rlen
), ofp
):
182 if type(ofp
) == type(''):
184 ofp
= open(ofname
, 'w')
186 fss
= macfs
.FSSpec(ofname
)
187 fss
.SetCreatorType('BnHq', 'TEXT')
188 ofp
.write('(This file must be converted with BinHex 4.0)\n\n:')
189 hqxer
= _Hqxcoderengine(ofp
)
190 self
.ofp
= _Rlecoderengine(hqxer
)
196 self
._writeinfo
(name
, finfo
)
197 self
.state
= _DID_HEADER
199 def _writeinfo(self
, name
, finfo
):
202 raise Error
, 'Filename too long'
203 d
= chr(nl
) + name
+ '\0'
204 d2
= finfo
.Type
+ finfo
.Creator
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
213 def _write(self
, data
):
214 self
.crc
= binascii
.crc_hqx(data
, self
.crc
)
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
))
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
)
229 def close_data(self
):
231 raise Error
, 'Incorrect data size, diff=%r' % (self
.rlen
,)
233 self
.state
= _DID_DATA
235 def write_rsrc(self
, data
):
236 if self
.state
< _DID_DATA
:
238 if self
.state
!= _DID_DATA
:
239 raise Error
, 'Writing resource data at the wrong time'
240 self
.rlen
= self
.rlen
- len(data
)
244 if self
.state
< _DID_DATA
:
246 if self
.state
!= _DID_DATA
:
247 raise Error
, 'Close at the wrong time'
250 "Incorrect resource-datasize, diff=%r" % (self
.rlen
,)
256 def binhex(inp
, out
):
257 """(infilename, outfilename) - Create binhex-encoded copy of a file"""
258 finfo
= getfileinfo(inp
)
259 ofp
= BinHex(finfo
, out
)
261 ifp
= open(inp
, 'rb')
262 # XXXX Do textfile translation on non-mac systems
270 ifp
= openrsrc(inp
, 'rb')
278 class _Hqxdecoderengine
:
279 """Read data via the decoder in 4-byte chunks"""
281 def __init__(self
, ifp
):
285 def read(self
, totalwtd
):
286 """Read at least wtd bytes (or until EOF)"""
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.
293 if self
.eof
: return decdata
295 data
= self
.ifp
.read(wtd
)
297 # Next problem: there may not be a complete number of
298 # bytes in what we pass to a2b. Solve by yet another
303 decdatacur
, self
.eof
= \
304 binascii
.a2b_hqx(data
)
306 except binascii
.Incomplete
:
308 newdata
= self
.ifp
.read(1)
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'
322 class _Rledecoderengine
:
323 """Read data via the RLE-coder"""
325 def __init__(self
, ifp
):
328 self
.post_buffer
= ''
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
:]
338 def _fill(self
, wtd
):
339 self
.pre_buffer
= self
.pre_buffer
+ self
.ifp
.read(wtd
+4)
341 self
.post_buffer
= self
.post_buffer
+ \
342 binascii
.rledecode_hqx(self
.pre_buffer
)
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.
357 mark
= len(self
.pre_buffer
)
358 if self
.pre_buffer
[-3:] == RUNCHAR
+ '\0' + RUNCHAR
:
360 elif self
.pre_buffer
[-1] == RUNCHAR
:
362 elif self
.pre_buffer
[-2:] == RUNCHAR
+ '\0':
364 elif self
.pre_buffer
[-2] == RUNCHAR
:
369 self
.post_buffer
= self
.post_buffer
+ \
370 binascii
.rledecode_hqx(self
.pre_buffer
[:mark
])
371 self
.pre_buffer
= self
.pre_buffer
[mark
:]
377 def __init__(self
, ifp
):
378 if type(ifp
) == type(''):
381 # Find initial colon.
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)
394 dummy
= ifp
.readline()
396 hqxifp
= _Hqxdecoderengine(ifp
)
397 self
.ifp
= _Rledecoderengine(hqxifp
)
401 def _read(self
, len):
402 data
= self
.ifp
.read(len)
403 self
.crc
= binascii
.crc_hqx(data
, self
.crc
)
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' \
416 def _readheader(self
):
418 fname
= self
._read
(ord(len))
419 rest
= self
._read
(1+4+4+2+4+4)
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]
430 self
.FInfo
.Creator
= creator
431 self
.FInfo
.Type
= type
432 self
.FInfo
.Flags
= flags
434 self
.state
= _DID_HEADER
437 if self
.state
!= _DID_HEADER
:
438 raise Error
, 'Read data at wrong time'
441 n
= min(n
, self
.dlen
)
446 rv
= rv
+ self
._read
(n
-len(rv
))
447 self
.dlen
= self
.dlen
- n
450 def close_data(self
):
451 if self
.state
!= _DID_HEADER
:
452 raise Error
, 'close_data at wrong time'
454 dummy
= self
._read
(self
.dlen
)
456 self
.state
= _DID_DATA
458 def read_rsrc(self
, *n
):
459 if self
.state
== _DID_HEADER
:
461 if self
.state
!= _DID_DATA
:
462 raise Error
, 'Read resource data at wrong time'
465 n
= min(n
, self
.rlen
)
468 self
.rlen
= self
.rlen
- n
473 dummy
= self
.read_rsrc(self
.rlen
)
475 self
.state
= _DID_RSRC
478 def hexbin(inp
, out
):
479 """(infilename, outfilename) - Decode binhexed file"""
485 ofss
= macfs
.FSSpec(out
)
486 out
= ofss
.as_pathname()
488 ofp
= open(out
, 'wb')
489 # XXXX Do translation on non-mac systems
497 d
= ifp
.read_rsrc(128000)
499 ofp
= openrsrc(out
, 'wb')
502 d
= ifp
.read_rsrc(128000)
508 nfinfo
= ofss
.GetFInfo()
509 nfinfo
.Creator
= finfo
.Creator
510 nfinfo
.Type
= finfo
.Type
511 nfinfo
.Flags
= finfo
.Flags
512 ofss
.SetFInfo(nfinfo
)
518 fss
, ok
= macfs
.PromptGetFile('File to convert:')
521 fname
= fss
.as_pathname()
524 binhex(fname
, fname
+'.hqx')
525 hexbin(fname
+'.hqx', fname
+'.viahqx')
526 #hexbin(fname, fname+'.unpacked')
529 if __name__
== '__main__':