summaryrefslogtreecommitdiffstats
path: root/Lib/binhex.py
blob: 469344330757711089f6e3651fbc0b52cdef1e56 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
"""binhex - Macintosh binhex compression/decompression
easy interface:
binhex(inputfilename, outputfilename)
hexbin(inputfilename, outputfilename)
"""

#
# Jack Jansen, CWI, August 1995.
#
# The module is supposed to be as compatible as possible. Especially the
# easy interface should work "as expected" on any platform.
# XXXX Note: currently, textfiles appear in mac-form on all platforms.
# We seem to lack a simple character-translate in python.
# (we should probably use ISO-Latin-1 on all but the mac platform).
# XXXX The simeple routines are too simple: they expect to hold the complete
# files in-core. Should be fixed.
# XXXX It would be nice to handle AppleDouble format on unix
# (for servers serving macs).
# XXXX I don't understand what happens when you get 0x90 times the same byte on
# input. The resulting code (xx 90 90) would appear to be interpreted as an
# escaped *value* of 0x90. All coders I've seen appear to ignore this nicety...
#
import sys
import os
import struct
import string
import binascii
	
Error = 'binhex.Error'

# States (what have we written)
[_DID_HEADER, _DID_DATA, _DID_RSRC] = range(3)

# Various constants
REASONABLY_LARGE=32768	# Minimal amount we pass the rle-coder
LINELEN=64
RUNCHAR=chr(0x90)	# run-length introducer

#
# This code is no longer byte-order dependent

#
# Workarounds for non-mac machines.
if os.name == 'mac':
	import macfs
	import MacOS
	try:
		openrf = MacOS.openrf
	except AttributeError:
		# Backward compatability
		openrf = open
	
	def FInfo():
		return macfs.FInfo()
	
	def getfileinfo(name):
		finfo = macfs.FSSpec(name).GetFInfo()
		dir, file = os.path.split(name)
		# XXXX Get resource/data sizes
		fp = open(name, 'rb')
		fp.seek(0, 2)
		dlen = fp.tell()
		fp = openrf(name, '*rb')
		fp.seek(0, 2)
		rlen = fp.tell()
		return file, finfo, dlen, rlen
	
	def openrsrc(name, *mode):
		if not mode:
			mode = '*rb'
		else:
			mode = '*' + mode[0]
		return openrf(name, mode)

else:
	#
	# Glue code for non-macintosh useage
	#
	
	class FInfo:
		def __init__(self):
			self.Type = '????'
			self.Creator = '????'
			self.Flags = 0

	def getfileinfo(name):
		finfo = FInfo()
		# Quick check for textfile
		fp = open(name)
		data = open(name).read(256)
		for c in data:
		    if not c in string.whitespace \
			and (c<' ' or ord(c) > 0177):
			break
		else:
			finfo.Type = 'TEXT'
		fp.seek(0, 2)
		dsize = fp.tell()
		fp.close()
		dir, file = os.path.split(name)
		file = string.replace(file, ':', '-', 1)
		return file, finfo, dsize, 0

	class openrsrc:
		def __init__(self, *args):
			pass
	
		def read(self, *args):
			return ''
	
		def write(self, *args):
			pass
			
		def close(self):
			pass
	
class _Hqxcoderengine:
	"""Write data to the coder in 3-byte chunks"""
	
	def __init__(self, ofp):
		self.ofp = ofp
		self.data = ''
		self.hqxdata = ''
		self.linelen = LINELEN-1

	def write(self, data):
		self.data = self.data + data
		datalen = len(self.data)
		todo = (datalen/3)*3
		data = self.data[:todo]
		self.data = self.data[todo:]
		if not data:
			return
		self.hqxdata = self.hqxdata + binascii.b2a_hqx(data)
		self._flush(0)

	def _flush(self, force):
		first = 0
		while first <= len(self.hqxdata)-self.linelen:
			last = first + self.linelen
			self.ofp.write(self.hqxdata[first:last]+'\n')
			self.linelen = LINELEN
			first = last
		self.hqxdata = self.hqxdata[first:]
		if force:
			self.ofp.write(self.hqxdata + ':\n')

	def close(self):
		if self.data:
			self.hqxdata = \
			     self.hqxdata + binascii.b2a_hqx(self.data)
		self._flush(1)
		self.ofp.close()
		del self.ofp

class _Rlecoderengine:
	"""Write data to the RLE-coder in suitably large chunks"""

	def __init__(self, ofp):
		self.ofp = ofp
		self.data = ''

	def write(self, data):
		self.data = self.data + data
		if len(self.data) < REASONABLY_LARGE:
			return
		rledata = binascii.rlecode_hqx(self.data)
		self.ofp.write(rledata)
		self.data = ''

	def close(self):
		if self.data:
			rledata = binascii.rlecode_hqx(self.data)
			self.ofp.write(rledata)
		self.ofp.close()
		del self.ofp

class BinHex:
	def __init__(self, (name, finfo, dlen, rlen), ofp):
		if type(ofp) == type(''):
			ofname = ofp
			ofp = open(ofname, 'w')
			if os.name == 'mac':
				fss = macfs.FSSpec(ofname)
				fss.SetCreatorType('BnHq', 'TEXT')
		ofp.write('(This file must be converted with BinHex 4.0)\n\n:')
		hqxer = _Hqxcoderengine(ofp)
		self.ofp = _Rlecoderengine(hqxer)
		self.crc = 0
		if finfo == None:
			finfo = FInfo()
		self.dlen = dlen
		self.rlen = rlen
		self._writeinfo(name, finfo)
		self.state = _DID_HEADER

	def _writeinfo(self, name, finfo):
		name = name
		nl = len(name)
		if nl > 63:
			raise Error, 'Filename too long'
		d = chr(nl) + name + '\0'
		d2 = finfo.Type + finfo.Creator

		# Force all structs to be packed with big-endian
		d3 = struct.pack('>h', finfo.Flags)
		d4 = struct.pack('>ii', self.dlen, self.rlen)
		info = d + d2 + d3 + d4
		self._write(info)
		self._writecrc()

	def _write(self, data):
		self.crc = binascii.crc_hqx(data, self.crc)
		self.ofp.write(data)

	def _writecrc(self):
	    # XXXX Should this be here??
	    # self.crc = binascii.crc_hqx('\0\0', self.crc)
	    self.ofp.write(struct.pack('>h', self.crc))
	    self.crc = 0

	def write(self, data):
		if self.state != _DID_HEADER:
			raise Error, 'Writing data at the wrong time'
		self.dlen = self.dlen - len(data)
		self._write(data)

	def close_data(self):
		if self.dlen <> 0:
			raise Error, 'Incorrect data size, diff='+`self.rlen`
		self._writecrc()
		self.state = _DID_DATA

	def write_rsrc(self, data):
		if self.state < _DID_DATA:
			self.close_data()
		if self.state != _DID_DATA:
			raise Error, 'Writing resource data at the wrong time'
		self.rlen = self.rlen - len(data)
		self._write(data)

	def close(self):
		if self.state < _DID_DATA:
			self.close_data()
		if self.state != _DID_DATA:
			raise Error, 'Close at the wrong time'
		if self.rlen <> 0:
			raise Error, \
			      "Incorrect resource-datasize, diff="+`self.rlen`
		self._writecrc()
		self.ofp.close()
		self.state = None
		del self.ofp
	
def binhex(inp, out):
	"""(infilename, outfilename) - Create binhex-encoded copy of a file"""
	finfo = getfileinfo(inp)
	ofp = BinHex(finfo, out)
	
	ifp = open(inp, 'rb')
	# XXXX Do textfile translation on non-mac systems
	while 1:
		d = ifp.read(128000)
		if not d: break
		ofp.write(d)
	ofp.close_data()
	ifp.close()

	ifp = openrsrc(inp, 'rb')
	while 1:
		d = ifp.read(128000)
		if not d: break
		ofp.write_rsrc(d)
	ofp.close()
	ifp.close() 

class _Hqxdecoderengine:
	"""Read data via the decoder in 4-byte chunks"""
	
	def __init__(self, ifp):
		self.ifp = ifp
		self.eof = 0

	def read(self, totalwtd):
		"""Read at least wtd bytes (or until EOF)"""
		decdata = ''
		wtd = totalwtd
		#
		# The loop here is convoluted, since we don't really now how 
		# much to decode: there may be newlines in the incoming data.
		while wtd > 0:
			if self.eof: return decdata
			wtd = ((wtd+2)/3)*4
			data = self.ifp.read(wtd)
			#
			# Next problem: there may not be a complete number of
			# bytes in what we pass to a2b. Solve by yet another
			# loop.
			#
			while 1:
				try:
					decdatacur, self.eof = \
						    binascii.a2b_hqx(data)
					break
				except binascii.Incomplete:
					pass
				newdata = self.ifp.read(1)
				if not newdata:
					raise Error, \
					      'Premature EOF on binhex file'
				data = data + newdata
			decdata = decdata + decdatacur
			wtd = totalwtd - len(decdata)
			if not decdata and not self.eof:
				raise Error, 'Premature EOF on binhex file'
		return decdata

	def close(self):
		self.ifp.close()

class _Rledecoderengine:
	"""Read data via the RLE-coder"""

	def __init__(self, ifp):
		self.ifp = ifp
		self.pre_buffer = ''
		self.post_buffer = ''
		self.eof = 0

	def read(self, wtd):
		if wtd > len(self.post_buffer):
			self._fill(wtd-len(self.post_buffer))
		rv = self.post_buffer[:wtd]
		self.post_buffer = self.post_buffer[wtd:]
		return rv

	def _fill(self, wtd):
		self.pre_buffer = self.pre_buffer + self.ifp.read(wtd+4)
		if self.ifp.eof:
			self.post_buffer = self.post_buffer + \
				binascii.rledecode_hqx(self.pre_buffer)
			self.pre_buffer = ''
			return
			
		#
		# Obfuscated code ahead. We have to take care that we don't
		# end up with an orphaned RUNCHAR later on. So, we keep a couple
		# of bytes in the buffer, depending on what the end of
		# the buffer looks like:
		# '\220\0\220' - Keep 3 bytes: repeated \220 (escaped as \220\0)
		# '?\220' - Keep 2 bytes: repeated something-else
		# '\220\0' - Escaped \220: Keep 2 bytes.
		# '?\220?' - Complete repeat sequence: decode all
		# otherwise: keep 1 byte.
		#
		mark = len(self.pre_buffer)
		if self.pre_buffer[-3:] == RUNCHAR + '\0' + RUNCHAR:
			mark = mark - 3
		elif self.pre_buffer[-1] == RUNCHAR:
			mark = mark - 2
		elif self.pre_buffer[-2:] == RUNCHAR + '\0':
			mark = mark - 2
		elif self.pre_buffer[-2] == RUNCHAR:
			pass # Decode all
		else:
			mark = mark - 1

		self.post_buffer = self.post_buffer + \
			binascii.rledecode_hqx(self.pre_buffer[:mark])
		self.pre_buffer = self.pre_buffer[mark:]

	def close(self):
		self.ifp.close()

class HexBin:
	def __init__(self, ifp):
		if type(ifp) == type(''):
			ifp = open(ifp)
		#
		# Find initial colon.
		#
		while 1:
			ch = ifp.read(1)
			if not ch:
				raise Error, "No binhex data found"
			# Cater for \r\n terminated lines (which show up as \n\r, hence
			# all lines start with \r)
			if ch == '\r':
				continue
			if ch == ':':
				break
			if ch != '\n':
				dummy = ifp.readline()
			
		hqxifp = _Hqxdecoderengine(ifp)
		self.ifp = _Rledecoderengine(hqxifp)
		self.crc = 0
		self._readheader()
		
	def _read(self, len):
		data = self.ifp.read(len)
		self.crc = binascii.crc_hqx(data, self.crc)
		return data
		
	def _checkcrc(self):
		filecrc = struct.unpack('>h', self.ifp.read(2))[0] & 0xffff
		#self.crc = binascii.crc_hqx('\0\0', self.crc)
		# XXXX Is this needed??
		self.crc = self.crc & 0xffff
		if filecrc != self.crc:
			raise Error, 'CRC error, computed %x, read %x' \
			      %(self.crc, filecrc)
		self.crc = 0

	def _readheader(self):
		len = self._read(1)
		fname = self._read(ord(len))
		rest = self._read(1+4+4+2+4+4)
		self._checkcrc()
		
		type = rest[1:5]
		creator = rest[5:9]
		flags = struct.unpack('>h', rest[9:11])[0]
		self.dlen = struct.unpack('>l', rest[11:15])[0]
		self.rlen = struct.unpack('>l', rest[15:19])[0]
		
		self.FName = fname
		self.FInfo = FInfo()
		self.FInfo.Creator = creator
		self.FInfo.Type = type
		self.FInfo.Flags = flags
		
		self.state = _DID_HEADER
		
	def read(self, *n):
		if self.state != _DID_HEADER:
			raise Error, 'Read data at wrong time'
		if n:
			n = n[0]
			n = min(n, self.dlen)
		else:
			n = self.dlen
		rv = ''
		while len(rv) < n:
			rv = rv + self._read(n-len(rv))
		self.dlen = self.dlen - n
		return rv
		
	def close_data(self):
		if self.state != _DID_HEADER:
			raise Error, 'close_data at wrong time'
		if self.dlen:
			dummy = self._read(self.dlen)
		self._checkcrc()
		self.state = _DID_DATA
		
	def read_rsrc(self, *n):
		if self.state == _DID_HEADER:
			self.close_data()
		if self.state != _DID_DATA:
			raise Error, 'Read resource data at wrong time'
		if n:
			n = n[0]
			n = min(n, self.rlen)
		else:
			n = self.rlen
		self.rlen = self.rlen - n
		return self._read(n)
		
	def close(self):
		if self.rlen:
			dummy = self.read_rsrc(self.rlen)
		self._checkcrc()
		self.state = _DID_RSRC
		self.ifp.close()
		
def hexbin(inp, out):
	"""(infilename, outfilename) - Decode binhexed file"""
	ifp = HexBin(inp)
	finfo = ifp.FInfo
	if not out:
		out = ifp.FName
	if os.name == 'mac':
		ofss = macfs.FSSpec(out)
		out = ofss.as_pathname()

	ofp = open(out, 'wb')
	# XXXX Do translation on non-mac systems
	while 1:
		d = ifp.read(128000)
		if not d: break
		ofp.write(d)
	ofp.close()
	ifp.close_data()
	
	d = ifp.read_rsrc(128000)
	if d:
		ofp = openrsrc(out, 'wb')
		ofp.write(d)
		while 1:
			d = ifp.read_rsrc(128000)
			if not d: break
			ofp.write(d)
		ofp.close()

	if os.name == 'mac':
		nfinfo = ofss.GetFInfo()
		nfinfo.Creator = finfo.Creator
		nfinfo.Type = finfo.Type
		nfinfo.Flags = finfo.Flags
		ofss.SetFInfo(nfinfo)
	
	ifp.close()

def _test():
	if os.name == 'mac':
		fss, ok = macfs.PromptGetFile('File to convert:')
		if not ok:
			sys.exit(0)
		fname = fss.as_pathname()
	else:
		fname = sys.argv[1]
	binhex(fname, fname+'.hqx')
	hexbin(fname+'.hqx', fname+'.viahqx')
	#hexbin(fname, fname+'.unpacked')
	sys.exit(1)
	
if __name__ == '__main__':
	_test()