3
"""Conversions to/from quoted-printable transport encoding as per RFC 1521."""
7
__all__ = ["encode", "decode", "encodestring", "decodestring"]
11
HEX = '0123456789ABCDEF'
15
from binascii import a2b_qp, b2a_qp
21
def needsquoting(c, quotetabs, header):
22
"""Decide whether a particular character needs to be quoted.
24
The 'quotetabs' flag indicates whether embedded tabs and spaces should be
25
quoted. Note that line-ending tabs and spaces are always encoded, as per
30
# if header, we have to escape _ because _ is used to escape space
33
return c == ESCAPE or not (' ' <= c <= '~')
36
"""Quote a single character."""
38
return ESCAPE + HEX[i//16] + HEX[i%16]
42
def encode(input, output, quotetabs, header = 0):
43
"""Read 'input', apply quoted-printable encoding, and write to 'output'.
45
'input' and 'output' are files with readline() and write() methods.
46
The 'quotetabs' flag indicates whether embedded tabs and spaces should be
47
quoted. Note that line-ending tabs and spaces are always encoded, as per
49
The 'header' flag indicates whether we are encoding spaces as _ as per
53
if b2a_qp is not None:
55
odata = b2a_qp(data, quotetabs = quotetabs, header = header)
59
def write(s, output=output, lineEnd='\n'):
60
# RFC 1521 requires that the line ending in a space or tab must have
61
# that trailing character encoded.
62
if s and s[-1:] in ' \t':
63
output.write(s[:-1] + quote(s[-1]) + lineEnd)
65
output.write(quote(s) + lineEnd)
67
output.write(s + lineEnd)
71
line = input.readline()
75
# Strip off any readline induced trailing newline
80
# Calculate the un-length-limited encoded line
82
if needsquoting(c, quotetabs, header):
84
if header and c == ' ':
88
# First, write out the previous line
89
if prevline is not None:
91
# Now see if we need any soft line breaks because of RFC-imposed
92
# length limitations. Then do the thisline->prevline dance.
93
thisline = EMPTYSTRING.join(outline)
94
while len(thisline) > MAXLINESIZE:
95
# Don't forget to include the soft line break `=' sign in the
97
write(thisline[:MAXLINESIZE-1], lineEnd='=\n')
98
thisline = thisline[MAXLINESIZE-1:]
99
# Write out the current line
101
# Write out the last line, without a trailing newline
102
if prevline is not None:
103
write(prevline, lineEnd=stripped)
105
def encodestring(s, quotetabs = 0, header = 0):
106
if b2a_qp is not None:
107
return b2a_qp(s, quotetabs = quotetabs, header = header)
108
from cStringIO import StringIO
111
encode(infp, outfp, quotetabs, header)
112
return outfp.getvalue()
116
def decode(input, output, header = 0):
117
"""Read 'input', apply quoted-printable decoding, and write to 'output'.
118
'input' and 'output' are files with readline() and write() methods.
119
If 'header' is true, decode underscore as space (per RFC 1522)."""
121
if a2b_qp is not None:
123
odata = a2b_qp(data, header = header)
129
line = input.readline()
132
if n > 0 and line[n-1] == '\n':
134
# Strip trailing whitespace
135
while n > 0 and line[n-1] in " \t\r":
141
if c == '_' and header:
142
new = new + ' '; i = i+1
144
new = new + c; i = i+1
145
elif i+1 == n and not partial:
147
elif i+1 < n and line[i+1] == ESCAPE:
148
new = new + ESCAPE; i = i+2
149
elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
150
new = new + chr(unhex(line[i+1:i+3])); i = i+3
151
else: # Bad escape sequence -- leave it in
152
new = new + c; i = i+1
154
output.write(new + '\n')
159
def decodestring(s, header = 0):
160
if a2b_qp is not None:
161
return a2b_qp(s, header = header)
162
from cStringIO import StringIO
165
decode(infp, outfp, header = header)
166
return outfp.getvalue()
170
# Other helper functions
172
"""Return true if the character 'c' is a hexadecimal digit."""
173
return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
176
"""Get the integer value of a hexadecimal number."""
181
elif 'a' <= c <= 'f':
183
elif 'A' <= c <= 'F':
187
bits = bits*16 + (ord(c) - i)
196
opts, args = getopt.getopt(sys.argv[1:], 'td')
197
except getopt.error, msg:
198
sys.stdout = sys.stderr
200
print "usage: quopri [-t | -d] [file] ..."
201
print "-t: quote tabs"
202
print "-d: decode; default encode"
207
if o == '-t': tabs = 1
208
if o == '-d': deco = 1
210
sys.stdout = sys.stderr
211
print "-t and -d are mutually exclusive"
213
if not args: args = ['-']
222
sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
226
decode(fp, sys.stdout)
228
encode(fp, sys.stdout, tabs)
229
if fp is not sys.stdin:
236
if __name__ == '__main__':