blob: beb54cb79c1ead0f007898e2b2d3d8c5f32cc4ee [file] [log] [blame]
Guido van Rossum105bd981997-07-11 18:39:03 +00001#! /usr/bin/env python
2
Barry Warsaw9b630a52001-06-19 19:07:46 +00003"""Conversions to/from quoted-printable transport encoding as per RFC 1521."""
Guido van Rossume7b146f2000-02-04 15:28:42 +00004
Guido van Rossumf1945461995-06-14 23:43:44 +00005# (Dec 1991 version).
6
Barry Warsaw9b630a52001-06-19 19:07:46 +00007__all__ = ["encode", "decode", "encodestring", "decodestring"]
Skip Montanaroc62c81e2001-02-12 02:00:42 +00008
Guido van Rossumf1945461995-06-14 23:43:44 +00009ESCAPE = '='
10MAXLINESIZE = 76
11HEX = '0123456789ABCDEF'
Barry Warsaw9b630a52001-06-19 19:07:46 +000012EMPTYSTRING = ''
Guido van Rossumf1945461995-06-14 23:43:44 +000013
Barry Warsaw9b630a52001-06-19 19:07:46 +000014
15
Guido van Rossumf1945461995-06-14 23:43:44 +000016def needsquoting(c, quotetabs):
Jeremy Hylton77249442000-10-05 17:24:33 +000017 """Decide whether a particular character needs to be quoted.
Guido van Rossume7b146f2000-02-04 15:28:42 +000018
Barry Warsaw9b630a52001-06-19 19:07:46 +000019 The 'quotetabs' flag indicates whether embedded tabs and spaces should be
20 quoted. Note that line-ending tabs and spaces are always encoded, as per
21 RFC 1521.
22 """
23 if c in ' \t':
24 return quotetabs
25 return c == ESCAPE or not (' ' <= c <= '~')
Guido van Rossumf1945461995-06-14 23:43:44 +000026
27def quote(c):
Jeremy Hylton77249442000-10-05 17:24:33 +000028 """Quote a single character."""
29 i = ord(c)
30 return ESCAPE + HEX[i/16] + HEX[i%16]
Guido van Rossumf1945461995-06-14 23:43:44 +000031
Barry Warsaw9b630a52001-06-19 19:07:46 +000032
33
Guido van Rossumf1945461995-06-14 23:43:44 +000034def encode(input, output, quotetabs):
Jeremy Hylton77249442000-10-05 17:24:33 +000035 """Read 'input', apply quoted-printable encoding, and write to 'output'.
Guido van Rossume7b146f2000-02-04 15:28:42 +000036
Jeremy Hylton77249442000-10-05 17:24:33 +000037 'input' and 'output' are files with readline() and write() methods.
Barry Warsaw9b630a52001-06-19 19:07:46 +000038 The 'quotetabs' flag indicates whether embedded tabs and spaces should be
39 quoted. Note that line-ending tabs and spaces are always encoded, as per
40 RFC 1521.
41 """
42 def write(s, output=output, lineEnd='\n'):
43 # RFC 1521 requires that the line ending in a space or tab must have
44 # that trailing character encoded.
45 if s and s[-1:] in ' \t':
46 output.write(s[:-1] + quote(s[-1]) + lineEnd)
47 else:
48 output.write(s + lineEnd)
49
50 prevline = None
51 linelen = 0
Jeremy Hylton77249442000-10-05 17:24:33 +000052 while 1:
53 line = input.readline()
54 if not line:
55 break
Barry Warsaw9b630a52001-06-19 19:07:46 +000056 outline = []
57 # Strip off any readline induced trailing newline
58 stripped = ''
59 if line[-1:] == '\n':
Jeremy Hylton77249442000-10-05 17:24:33 +000060 line = line[:-1]
Barry Warsaw9b630a52001-06-19 19:07:46 +000061 stripped = '\n'
Jeremy Hylton77249442000-10-05 17:24:33 +000062 for c in line:
63 if needsquoting(c, quotetabs):
64 c = quote(c)
Barry Warsaw9b630a52001-06-19 19:07:46 +000065 # Have we hit the RFC 1521 encoded line maximum?
66 if linelen + len(c) >= MAXLINESIZE:
67 # Write out the previous line
68 if prevline is not None:
69 write(prevline)
70 prevline = EMPTYSTRING.join(outline)
71 linelen = 0
72 outline = []
73 outline.append(c)
74 linelen += len(c)
75 # Write out the current line
76 if prevline is not None:
77 write(prevline)
78 prevline = EMPTYSTRING.join(outline)
79 linelen = 0
80 outline = []
81 # Write out the last line, without a trailing newline
82 if prevline is not None:
83 write(prevline, lineEnd=stripped)
Guido van Rossumf1945461995-06-14 23:43:44 +000084
Barry Warsaw9b630a52001-06-19 19:07:46 +000085def encodestring(s, quotetabs=0):
86 from cStringIO import StringIO
87 infp = StringIO(s)
88 outfp = StringIO()
89 encode(infp, outfp, quotetabs)
90 return outfp.getvalue()
91
92
93
Guido van Rossumf1945461995-06-14 23:43:44 +000094def decode(input, output):
Jeremy Hylton77249442000-10-05 17:24:33 +000095 """Read 'input', apply quoted-printable decoding, and write to 'output'.
Guido van Rossume7b146f2000-02-04 15:28:42 +000096
Jeremy Hylton77249442000-10-05 17:24:33 +000097 'input' and 'output' are files with readline() and write() methods."""
98 new = ''
99 while 1:
100 line = input.readline()
101 if not line: break
102 i, n = 0, len(line)
103 if n > 0 and line[n-1] == '\n':
104 partial = 0; n = n-1
105 # Strip trailing whitespace
Guido van Rossum14b90a42001-03-22 22:30:21 +0000106 while n > 0 and line[n-1] in " \t\r":
Jeremy Hylton77249442000-10-05 17:24:33 +0000107 n = n-1
108 else:
109 partial = 1
110 while i < n:
111 c = line[i]
Fred Drake8152d322000-12-12 23:20:45 +0000112 if c != ESCAPE:
Jeremy Hylton77249442000-10-05 17:24:33 +0000113 new = new + c; i = i+1
114 elif i+1 == n and not partial:
115 partial = 1; break
116 elif i+1 < n and line[i+1] == ESCAPE:
117 new = new + ESCAPE; i = i+2
118 elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
119 new = new + chr(unhex(line[i+1:i+3])); i = i+3
120 else: # Bad escape sequence -- leave it in
121 new = new + c; i = i+1
122 if not partial:
123 output.write(new + '\n')
124 new = ''
125 if new:
126 output.write(new)
Guido van Rossumf1945461995-06-14 23:43:44 +0000127
Barry Warsaw9b630a52001-06-19 19:07:46 +0000128def decodestring(s):
129 from cStringIO import StringIO
130 infp = StringIO(s)
131 outfp = StringIO()
132 decode(infp, outfp)
133 return outfp.getvalue()
134
135
136
137# Other helper functions
Guido van Rossumf1945461995-06-14 23:43:44 +0000138def ishex(c):
Jeremy Hylton77249442000-10-05 17:24:33 +0000139 """Return true if the character 'c' is a hexadecimal digit."""
140 return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
Guido van Rossumf1945461995-06-14 23:43:44 +0000141
142def unhex(s):
Jeremy Hylton77249442000-10-05 17:24:33 +0000143 """Get the integer value of a hexadecimal number."""
144 bits = 0
145 for c in s:
146 if '0' <= c <= '9':
147 i = ord('0')
148 elif 'a' <= c <= 'f':
149 i = ord('a')-10
150 elif 'A' <= c <= 'F':
151 i = ord('A')-10
152 else:
153 break
154 bits = bits*16 + (ord(c) - i)
155 return bits
Guido van Rossumf1945461995-06-14 23:43:44 +0000156
Barry Warsaw9b630a52001-06-19 19:07:46 +0000157
158
159def main():
Jeremy Hylton77249442000-10-05 17:24:33 +0000160 import sys
161 import getopt
162 try:
163 opts, args = getopt.getopt(sys.argv[1:], 'td')
164 except getopt.error, msg:
165 sys.stdout = sys.stderr
166 print msg
167 print "usage: quopri [-t | -d] [file] ..."
168 print "-t: quote tabs"
169 print "-d: decode; default encode"
170 sys.exit(2)
171 deco = 0
172 tabs = 0
173 for o, a in opts:
174 if o == '-t': tabs = 1
175 if o == '-d': deco = 1
176 if tabs and deco:
177 sys.stdout = sys.stderr
178 print "-t and -d are mutually exclusive"
179 sys.exit(2)
180 if not args: args = ['-']
181 sts = 0
182 for file in args:
183 if file == '-':
184 fp = sys.stdin
185 else:
186 try:
187 fp = open(file)
188 except IOError, msg:
189 sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
190 sts = 1
191 continue
192 if deco:
193 decode(fp, sys.stdout)
194 else:
195 encode(fp, sys.stdout, tabs)
196 if fp is not sys.stdin:
197 fp.close()
198 if sts:
199 sys.exit(sts)
Guido van Rossumf1945461995-06-14 23:43:44 +0000200
Barry Warsaw9b630a52001-06-19 19:07:46 +0000201
202
Guido van Rossumf1945461995-06-14 23:43:44 +0000203if __name__ == '__main__':
Barry Warsaw9b630a52001-06-19 19:07:46 +0000204 main()