diff options
author | Barry Warsaw <barry@python.org> | 2001-06-19 19:07:46 (GMT) |
---|---|---|
committer | Barry Warsaw <barry@python.org> | 2001-06-19 19:07:46 (GMT) |
commit | 9b630a50200e44e16acfe3dee4052b3d0b0677e4 (patch) | |
tree | 9b9d9a753241e22f9bf6ff2844e9e06075fbd964 | |
parent | e275c42c38f7a83275de027b200b39460b3ec7b7 (diff) | |
download | cpython-9b630a50200e44e16acfe3dee4052b3d0b0677e4.zip cpython-9b630a50200e44e16acfe3dee4052b3d0b0677e4.tar.gz cpython-9b630a50200e44e16acfe3dee4052b3d0b0677e4.tar.bz2 |
Better support for RFC 1521 quoted-printable specification, along with
addition of interface for consistency with base64 module. Namely,
encodestring(), decodestring(): New functions which accept a string
object and return a string object. They just wrap the string in
StringIOs and pass them to the encode() and decode() methods
respectively. encodestring() accepts a default argument of quotetabs,
defaulting to zero, which is passed on straight through to encode().
encode(): Fix the bug where an extra newline would always be added to
the output, which prevented an idempotent roundtrip through
encode->decode. Now, if the source string doesn't end in a newline,
then the result string won't end in a newline.
Also, extend the quotetabs argument semantics to include quoting
embedded strings, which is also optional according to the RFC.
test() -> main()
"from quopri import *" also imports encodestring() and decodestring().
-rwxr-xr-x | Lib/quopri.py | 102 |
1 files changed, 77 insertions, 25 deletions
diff --git a/Lib/quopri.py b/Lib/quopri.py index e7e4bec..beb54cb 100755 --- a/Lib/quopri.py +++ b/Lib/quopri.py @@ -1,58 +1,96 @@ #! /usr/bin/env python -"""Conversions to/from quoted-printable transport encoding as per RFC-1521.""" +"""Conversions to/from quoted-printable transport encoding as per RFC 1521.""" # (Dec 1991 version). -__all__ = ["encode","decode"] +__all__ = ["encode", "decode", "encodestring", "decodestring"] ESCAPE = '=' MAXLINESIZE = 76 HEX = '0123456789ABCDEF' +EMPTYSTRING = '' + + def needsquoting(c, quotetabs): """Decide whether a particular character needs to be quoted. - The 'quotetabs' flag indicates whether tabs should be quoted.""" - if c == '\t': - return not quotetabs - return c == ESCAPE or not(' ' <= c <= '~') + The 'quotetabs' flag indicates whether embedded tabs and spaces should be + quoted. Note that line-ending tabs and spaces are always encoded, as per + RFC 1521. + """ + if c in ' \t': + return quotetabs + return c == ESCAPE or not (' ' <= c <= '~') def quote(c): """Quote a single character.""" i = ord(c) return ESCAPE + HEX[i/16] + HEX[i%16] + + def encode(input, output, quotetabs): """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 tabs should 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 + RFC 1521. + """ + 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) + else: + output.write(s + lineEnd) + + prevline = None + linelen = 0 while 1: line = input.readline() if not line: break - new = '' - last = line[-1:] - if last == '\n': + outline = [] + # Strip off any readline induced trailing newline + stripped = '' + if line[-1:] == '\n': line = line[:-1] - else: - last = '' - prev = '' + stripped = '\n' for c in line: if needsquoting(c, quotetabs): c = quote(c) - if len(new) + len(c) >= MAXLINESIZE: - output.write(new + ESCAPE + '\n') - new = '' - new = new + c - prev = c - if prev in (' ', '\t'): - output.write(new + ESCAPE + '\n\n') - else: - output.write(new + '\n') + # Have we hit the RFC 1521 encoded line maximum? + if linelen + len(c) >= MAXLINESIZE: + # Write out the previous line + if prevline is not None: + write(prevline) + prevline = EMPTYSTRING.join(outline) + linelen = 0 + outline = [] + outline.append(c) + linelen += len(c) + # Write out the current line + if prevline is not None: + write(prevline) + prevline = EMPTYSTRING.join(outline) + linelen = 0 + outline = [] + # Write out the last line, without a trailing newline + if prevline is not None: + write(prevline, lineEnd=stripped) +def encodestring(s, quotetabs=0): + from cStringIO import StringIO + infp = StringIO(s) + outfp = StringIO() + encode(infp, outfp, quotetabs) + return outfp.getvalue() + + + def decode(input, output): """Read 'input', apply quoted-printable decoding, and write to 'output'. @@ -87,6 +125,16 @@ def decode(input, output): if new: output.write(new) +def decodestring(s): + from cStringIO import StringIO + infp = StringIO(s) + outfp = StringIO() + decode(infp, outfp) + return outfp.getvalue() + + + +# Other helper functions def ishex(c): """Return true if the character 'c' is a hexadecimal digit.""" return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F' @@ -106,7 +154,9 @@ def unhex(s): bits = bits*16 + (ord(c) - i) return bits -def test(): + + +def main(): import sys import getopt try: @@ -148,5 +198,7 @@ def test(): if sts: sys.exit(sts) + + if __name__ == '__main__': - test() + main() |