mirror of
https://github.com/python/cpython.git
synced 2025-12-15 21:44:50 +00:00
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().
This commit is contained in:
parent
e275c42c38
commit
9b630a5020
1 changed files with 77 additions and 25 deletions
102
Lib/quopri.py
102
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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue