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...
30 __all__
= ["binhex","hexbin","Error"]
32 class Error(Exception):
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
44 # This code is no longer byte-order dependent
47 # Workarounds for non-mac machines.
49 from Carbon
.File
import FSSpec
, FInfo
50 from MacOS
import openrf
52 def getfileinfo(name
):
53 finfo
= FSSpec(name
).FSpGetFInfo()
54 dir, file = os
.path
.split(name
)
55 # XXX Get resource/data sizes
56 fp
= io
.open(name
, 'rb')
59 fp
= openrf(name
, '*rb')
62 return file, finfo
, dlen
, rlen
64 def openrsrc(name
, *mode
):
69 return openrf(name
, mode
)
73 # Glue code for non-macintosh usage
82 def getfileinfo(name
):
84 fp
= io
.open(name
, 'rb')
85 # Quick check for textfile
92 dir, file = os
.path
.split(name
)
93 file = file.replace(':', '-', 1)
94 return file, finfo
, dsize
, 0
97 def __init__(self
, *args
):
100 def read(self
, *args
):
103 def write(self
, *args
):
109 class _Hqxcoderengine
:
110 """Write data to the coder in 3-byte chunks"""
112 def __init__(self
, ofp
):
116 self
.linelen
= LINELEN
- 1
118 def write(self
, data
):
119 self
.data
= self
.data
+ data
120 datalen
= len(self
.data
)
121 todo
= (datalen
// 3) * 3
122 data
= self
.data
[:todo
]
123 self
.data
= self
.data
[todo
:]
126 self
.hqxdata
= self
.hqxdata
+ binascii
.b2a_hqx(data
)
129 def _flush(self
, force
):
131 while first
<= len(self
.hqxdata
) - self
.linelen
:
132 last
= first
+ self
.linelen
133 self
.ofp
.write(self
.hqxdata
[first
:last
] + b
'\n')
134 self
.linelen
= LINELEN
136 self
.hqxdata
= self
.hqxdata
[first
:]
138 self
.ofp
.write(self
.hqxdata
+ b
':\n')
142 self
.hqxdata
= self
.hqxdata
+ binascii
.b2a_hqx(self
.data
)
147 class _Rlecoderengine
:
148 """Write data to the RLE-coder in suitably large chunks"""
150 def __init__(self
, ofp
):
154 def write(self
, data
):
155 self
.data
= self
.data
+ data
156 if len(self
.data
) < REASONABLY_LARGE
:
158 rledata
= binascii
.rlecode_hqx(self
.data
)
159 self
.ofp
.write(rledata
)
164 rledata
= binascii
.rlecode_hqx(self
.data
)
165 self
.ofp
.write(rledata
)
170 def __init__(self
, name_finfo_dlen_rlen
, ofp
):
171 name
, finfo
, dlen
, rlen
= name_finfo_dlen_rlen
172 if isinstance(ofp
, str):
174 ofp
= io
.open(ofname
, 'wb')
177 fss
.SetCreatorType('BnHq', 'TEXT')
178 ofp
.write(b
'(This file must be converted with BinHex 4.0)\r\r:')
179 hqxer
= _Hqxcoderengine(ofp
)
180 self
.ofp
= _Rlecoderengine(hqxer
)
186 self
._writeinfo
(name
, finfo
)
187 self
.state
= _DID_HEADER
189 def _writeinfo(self
, name
, finfo
):
192 raise Error('Filename too long')
193 d
= bytes([nl
]) + name
.encode("latin-1") + b
'\0'
194 tp
, cr
= finfo
.Type
, finfo
.Creator
195 if isinstance(tp
, str):
196 tp
= tp
.encode("latin-1")
197 if isinstance(cr
, str):
198 cr
= cr
.encode("latin-1")
201 # Force all structs to be packed with big-endian
202 d3
= struct
.pack('>h', finfo
.Flags
)
203 d4
= struct
.pack('>ii', self
.dlen
, self
.rlen
)
204 info
= d
+ d2
+ d3
+ d4
208 def _write(self
, data
):
209 self
.crc
= binascii
.crc_hqx(data
, self
.crc
)
213 # XXXX Should this be here??
214 # self.crc = binascii.crc_hqx('\0\0', self.crc)
219 self
.ofp
.write(struct
.pack(fmt
, self
.crc
))
222 def write(self
, data
):
223 if self
.state
!= _DID_HEADER
:
224 raise Error('Writing data at the wrong time')
225 self
.dlen
= self
.dlen
- len(data
)
228 def close_data(self
):
230 raise Error('Incorrect data size, diff=%r' % (self
.rlen
,))
232 self
.state
= _DID_DATA
234 def write_rsrc(self
, data
):
235 if self
.state
< _DID_DATA
:
237 if self
.state
!= _DID_DATA
:
238 raise Error('Writing resource data at the wrong time')
239 self
.rlen
= self
.rlen
- len(data
)
243 if self
.state
< _DID_DATA
:
245 if self
.state
!= _DID_DATA
:
246 raise Error('Close at the wrong time')
248 raise Error("Incorrect resource-datasize, diff=%r" % (self
.rlen
,))
254 def binhex(inp
, out
):
255 """binhex(infilename, outfilename): create binhex-encoded copy of a file"""
256 finfo
= getfileinfo(inp
)
257 ofp
= BinHex(finfo
, out
)
259 ifp
= io
.open(inp
, 'rb')
260 # XXXX Do textfile translation on non-mac systems
268 ifp
= openrsrc(inp
, 'rb')
276 class _Hqxdecoderengine
:
277 """Read data via the decoder in 4-byte chunks"""
279 def __init__(self
, ifp
):
283 def read(self
, totalwtd
):
284 """Read at least wtd bytes (or until EOF)"""
288 # The loop here is convoluted, since we don't really now how
289 # much to decode: there may be newlines in the incoming data.
291 if self
.eof
: return decdata
292 wtd
= ((wtd
+ 2) // 3) * 4
293 data
= self
.ifp
.read(wtd
)
295 # Next problem: there may not be a complete number of
296 # bytes in what we pass to a2b. Solve by yet another
301 decdatacur
, self
.eof
= binascii
.a2b_hqx(data
)
303 except binascii
.Incomplete
:
305 newdata
= self
.ifp
.read(1)
307 raise Error('Premature EOF on binhex file')
308 data
= data
+ newdata
309 decdata
= decdata
+ decdatacur
310 wtd
= totalwtd
- len(decdata
)
311 if not decdata
and not self
.eof
:
312 raise Error('Premature EOF on binhex file')
318 class _Rledecoderengine
:
319 """Read data via the RLE-coder"""
321 def __init__(self
, ifp
):
323 self
.pre_buffer
= b
''
324 self
.post_buffer
= b
''
328 if wtd
> len(self
.post_buffer
):
329 self
._fill
(wtd
- len(self
.post_buffer
))
330 rv
= self
.post_buffer
[:wtd
]
331 self
.post_buffer
= self
.post_buffer
[wtd
:]
334 def _fill(self
, wtd
):
335 self
.pre_buffer
= self
.pre_buffer
+ self
.ifp
.read(wtd
+ 4)
337 self
.post_buffer
= self
.post_buffer
+ \
338 binascii
.rledecode_hqx(self
.pre_buffer
)
339 self
.pre_buffer
= b
''
343 # Obfuscated code ahead. We have to take care that we don't
344 # end up with an orphaned RUNCHAR later on. So, we keep a couple
345 # of bytes in the buffer, depending on what the end of
346 # the buffer looks like:
347 # '\220\0\220' - Keep 3 bytes: repeated \220 (escaped as \220\0)
348 # '?\220' - Keep 2 bytes: repeated something-else
349 # '\220\0' - Escaped \220: Keep 2 bytes.
350 # '?\220?' - Complete repeat sequence: decode all
351 # otherwise: keep 1 byte.
353 mark
= len(self
.pre_buffer
)
354 if self
.pre_buffer
[-3:] == RUNCHAR
+ b
'\0' + RUNCHAR
:
356 elif self
.pre_buffer
[-1] == RUNCHAR
:
358 elif self
.pre_buffer
[-2:] == RUNCHAR
+ b
'\0':
360 elif self
.pre_buffer
[-2] == RUNCHAR
:
365 self
.post_buffer
= self
.post_buffer
+ \
366 binascii
.rledecode_hqx(self
.pre_buffer
[:mark
])
367 self
.pre_buffer
= self
.pre_buffer
[mark
:]
373 def __init__(self
, ifp
):
374 if isinstance(ifp
, str):
375 ifp
= io
.open(ifp
, 'rb')
377 # Find initial colon.
382 raise Error("No binhex data found")
383 # Cater for \r\n terminated lines (which show up as \n\r, hence
384 # all lines start with \r)
390 hqxifp
= _Hqxdecoderengine(ifp
)
391 self
.ifp
= _Rledecoderengine(hqxifp
)
395 def _read(self
, len):
396 data
= self
.ifp
.read(len)
397 self
.crc
= binascii
.crc_hqx(data
, self
.crc
)
401 filecrc
= struct
.unpack('>h', self
.ifp
.read(2))[0] & 0xffff
402 #self.crc = binascii.crc_hqx('\0\0', self.crc)
403 # XXXX Is this needed??
404 self
.crc
= self
.crc
& 0xffff
405 if filecrc
!= self
.crc
:
406 raise Error('CRC error, computed %x, read %x'
407 % (self
.crc
, filecrc
))
410 def _readheader(self
):
412 fname
= self
._read
(ord(len))
413 rest
= self
._read
(1 + 4 + 4 + 2 + 4 + 4)
418 flags
= struct
.unpack('>h', rest
[9:11])[0]
419 self
.dlen
= struct
.unpack('>l', rest
[11:15])[0]
420 self
.rlen
= struct
.unpack('>l', rest
[15:19])[0]
424 self
.FInfo
.Creator
= creator
425 self
.FInfo
.Type
= type
426 self
.FInfo
.Flags
= flags
428 self
.state
= _DID_HEADER
431 if self
.state
!= _DID_HEADER
:
432 raise Error('Read data at wrong time')
435 n
= min(n
, self
.dlen
)
440 rv
= rv
+ self
._read
(n
-len(rv
))
441 self
.dlen
= self
.dlen
- n
444 def close_data(self
):
445 if self
.state
!= _DID_HEADER
:
446 raise Error('close_data at wrong time')
448 dummy
= self
._read
(self
.dlen
)
450 self
.state
= _DID_DATA
452 def read_rsrc(self
, *n
):
453 if self
.state
== _DID_HEADER
:
455 if self
.state
!= _DID_DATA
:
456 raise Error('Read resource data at wrong time')
459 n
= min(n
, self
.rlen
)
462 self
.rlen
= self
.rlen
- n
467 dummy
= self
.read_rsrc(self
.rlen
)
469 self
.state
= _DID_RSRC
472 def hexbin(inp
, out
):
473 """hexbin(infilename, outfilename) - Decode binhexed file"""
480 out
= ofss
.as_pathname()
482 ofp
= io
.open(out
, 'wb')
483 # XXXX Do translation on non-mac systems
491 d
= ifp
.read_rsrc(128000)
493 ofp
= openrsrc(out
, 'wb')
496 d
= ifp
.read_rsrc(128000)
502 nfinfo
= ofss
.GetFInfo()
503 nfinfo
.Creator
= finfo
.Creator
504 nfinfo
.Type
= finfo
.Type
505 nfinfo
.Flags
= finfo
.Flags
506 ofss
.SetFInfo(nfinfo
)