"""Conversions to/from quoted-printable transport encoding as per RFC 1521."""
__all__ = ["encode", "decode", "encodestring", "decodestring"]
from binascii import a2b_qp, b2a_qp
def needsquoting(c, quotetabs, header):
"""Decide whether a particular character needs to be quoted.
The 'quotetabs' flag indicates whether embedded tabs and spaces should be
quoted. Note that line-ending tabs and spaces are always encoded, as per
# if header, we have to escape _ because _ is used to escape space
return c == ESCAPE or not (' ' <= c <= '~')
"""Quote a single character."""
return ESCAPE + HEX[i//16] + HEX[i%16]
def encode(input, output, quotetabs, header = 0):
"""Read 'input', apply quoted-printable encoding, and write to 'output'.
'input' and 'output' are files with readline() and write() methods.
The 'quotetabs' flag indicates whether embedded tabs and spaces should be
quoted. Note that line-ending tabs and spaces are always encoded, as per
The 'header' flag indicates whether we are encoding spaces as _ as per
odata = b2a_qp(data, quotetabs = quotetabs, header = header)
def write(s, output=output, lineEnd='\n'):
# RFC 1521 requires that the line ending in a space or tab must have
# that trailing character encoded.
if s and s[-1:] in ' \t':
output.write(s[:-1] + quote(s[-1]) + lineEnd)
output.write(quote(s) + lineEnd)
output.write(s + lineEnd)
# Strip off any readline induced trailing newline
# Calculate the un-length-limited encoded line
if needsquoting(c, quotetabs, header):
# First, write out the previous line
# Now see if we need any soft line breaks because of RFC-imposed
# length limitations. Then do the thisline->prevline dance.
thisline = EMPTYSTRING.join(outline)
while len(thisline) > MAXLINESIZE:
# Don't forget to include the soft line break `=' sign in the
write(thisline[:MAXLINESIZE-1], lineEnd='=\n')
thisline = thisline[MAXLINESIZE-1:]
# Write out the current line
# Write out the last line, without a trailing newline
write(prevline, lineEnd=stripped)
def encodestring(s, quotetabs = 0, header = 0):
return b2a_qp(s, quotetabs = quotetabs, header = header)
from cStringIO import StringIO
encode(infp, outfp, quotetabs, header)
def decode(input, output, header = 0):
"""Read 'input', apply quoted-printable decoding, and write to 'output'.
'input' and 'output' are files with readline() and write() methods.
If 'header' is true, decode underscore as space (per RFC 1522)."""
odata = a2b_qp(data, header = header)
if n > 0 and line[n-1] == '\n':
# Strip trailing whitespace
while n > 0 and line[n-1] in " \t\r":
elif i+1 == n and not partial:
elif i+1 < n and line[i+1] == ESCAPE:
new = new + ESCAPE; i = i+2
elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
new = new + chr(unhex(line[i+1:i+3])); i = i+3
else: # Bad escape sequence -- leave it in
def decodestring(s, header = 0):
return a2b_qp(s, header = header)
from cStringIO import StringIO
decode(infp, outfp, header = header)
"""Return true if the character 'c' is a hexadecimal digit."""
return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
"""Get the integer value of a hexadecimal number."""
bits = bits*16 + (ord(c) - i)
opts, args = getopt.getopt(sys.argv[1:], 'td')
except getopt.error, msg:
print "usage: quopri [-t | -d] [file] ..."
print "-d: decode; default encode"
print "-t and -d are mutually exclusive"
if not args: args = ['-']
sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
encode(fp, sys.stdout, tabs)
if __name__ == '__main__':