Ditched '_find_SET()', since it was a no-value-added wrapper around
[python/dscho.git] / Lib / quopri.py
blobcd2f5eb86459a26ba774e15e34cdd47883deff02
1 #! /usr/bin/env python
3 """Conversions to/from quoted-printable transport encoding as per RFC-1521."""
5 # (Dec 1991 version).
7 ESCAPE = '='
8 MAXLINESIZE = 76
9 HEX = '0123456789ABCDEF'
11 def needsquoting(c, quotetabs):
12 """Decide whether a particular character needs to be quoted.
14 The 'quotetabs' flag indicates whether tabs should be quoted."""
15 if c == '\t':
16 return not quotetabs
17 return c == ESCAPE or not(' ' <= c <= '~')
19 def quote(c):
20 """Quote a single character."""
21 if c == ESCAPE:
22 return ESCAPE * 2
23 else:
24 i = ord(c)
25 return ESCAPE + HEX[i/16] + HEX[i%16]
27 def encode(input, output, quotetabs):
28 """Read 'input', apply quoted-printable encoding, and write to 'output'.
30 'input' and 'output' are files with readline() and write() methods.
31 The 'quotetabs' flag indicates whether tabs should be quoted."""
32 while 1:
33 line = input.readline()
34 if not line: break
35 new = ''
36 last = line[-1:]
37 if last == '\n': line = line[:-1]
38 else: last = ''
39 prev = ''
40 for c in line:
41 if needsquoting(c, quotetabs):
42 c = quote(c)
43 if len(new) + len(c) >= MAXLINESIZE:
44 output.write(new + ESCAPE + '\n')
45 new = ''
46 new = new + c
47 prev = c
48 if prev in (' ', '\t'):
49 output.write(new + ESCAPE + '\n\n')
50 else:
51 output.write(new + '\n')
53 def decode(input, output):
54 """Read 'input', apply quoted-printable decoding, and write to 'output'.
56 'input' and 'output' are files with readline() and write() methods."""
57 new = ''
58 while 1:
59 line = input.readline()
60 if not line: break
61 i, n = 0, len(line)
62 if n > 0 and line[n-1] == '\n':
63 partial = 0; n = n-1
64 # Strip trailing whitespace
65 while n > 0 and line[n-1] in (' ', '\t'):
66 n = n-1
67 else:
68 partial = 1
69 while i < n:
70 c = line[i]
71 if c <> ESCAPE:
72 new = new + c; i = i+1
73 elif i+1 == n and not partial:
74 partial = 1; break
75 elif i+1 < n and line[i+1] == ESCAPE:
76 new = new + ESCAPE; i = i+2
77 elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
78 new = new + chr(unhex(line[i+1:i+3])); i = i+3
79 else: # Bad escape sequence -- leave it in
80 new = new + c; i = i+1
81 if not partial:
82 output.write(new + '\n')
83 new = ''
84 if new:
85 output.write(new)
87 def ishex(c):
88 """Return true if the character 'c' is a hexadecimal digit."""
89 return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
91 def unhex(s):
92 """Get the integer value of a hexadecimal number."""
93 bits = 0
94 for c in s:
95 if '0' <= c <= '9':
96 i = ord('0')
97 elif 'a' <= c <= 'f':
98 i = ord('a')-10
99 elif 'A' <= c <= 'F':
100 i = ord('A')-10
101 else:
102 break
103 bits = bits*16 + (ord(c) - i)
104 return bits
106 def test():
107 import sys
108 import getopt
109 try:
110 opts, args = getopt.getopt(sys.argv[1:], 'td')
111 except getopt.error, msg:
112 sys.stdout = sys.stderr
113 print msg
114 print "usage: quopri [-t | -d] [file] ..."
115 print "-t: quote tabs"
116 print "-d: decode; default encode"
117 sys.exit(2)
118 deco = 0
119 tabs = 0
120 for o, a in opts:
121 if o == '-t': tabs = 1
122 if o == '-d': deco = 1
123 if tabs and deco:
124 sys.stdout = sys.stderr
125 print "-t and -d are mutually exclusive"
126 sys.exit(2)
127 if not args: args = ['-']
128 sts = 0
129 for file in args:
130 if file == '-':
131 fp = sys.stdin
132 else:
133 try:
134 fp = open(file)
135 except IOError, msg:
136 sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
137 sts = 1
138 continue
139 if deco:
140 decode(fp, sys.stdout)
141 else:
142 encode(fp, sys.stdout, tabs)
143 if fp is not sys.stdin:
144 fp.close()
145 if sts:
146 sys.exit(sts)
148 if __name__ == '__main__':
149 test()