0001 #! /usr/bin/env python 0002 0003 """Conversions to/from quoted-printable transport encoding as per RFC 1521.""" 0004 0005 # (Dec 1991 version). 0006 0007 __all__ = ["encode", "decode", "encodestring", "decodestring"] 0008 0009 ESCAPE = '=' 0010 MAXLINESIZE = 76 0011 HEX = '0123456789ABCDEF' 0012 EMPTYSTRING = '' 0013 0014 try: 0015 from binascii import a2b_qp, b2a_qp 0016 except ImportError: 0017 a2b_qp = None 0018 b2a_qp = None 0019 0020 0021 def needsquoting(c, quotetabs, header): 0022 """Decide whether a particular character needs to be quoted. 0023 0024 The 'quotetabs' flag indicates whether embedded tabs and spaces should be 0025 quoted. Note that line-ending tabs and spaces are always encoded, as per 0026 RFC 1521. 0027 """ 0028 if c in ' \t': 0029 return quotetabs 0030 # if header, we have to escape _ because _ is used to escape space 0031 if c == '_': 0032 return header 0033 return c == ESCAPE or not (' ' <= c <= '~') 0034 0035 def quote(c): 0036 """Quote a single character.""" 0037 i = ord(c) 0038 return ESCAPE + HEX[i//16] + HEX[i%16] 0039 0040 0041 0042 def encode(input, output, quotetabs, header = 0): 0043 """Read 'input', apply quoted-printable encoding, and write to 'output'. 0044 0045 'input' and 'output' are files with readline() and write() methods. 0046 The 'quotetabs' flag indicates whether embedded tabs and spaces should be 0047 quoted. Note that line-ending tabs and spaces are always encoded, as per 0048 RFC 1521. 0049 The 'header' flag indicates whether we are encoding spaces as _ as per 0050 RFC 1522. 0051 """ 0052 0053 if b2a_qp is not None: 0054 data = input.read() 0055 odata = b2a_qp(data, quotetabs = quotetabs, header = header) 0056 output.write(odata) 0057 return 0058 0059 def write(s, output=output, lineEnd='\n'): 0060 # RFC 1521 requires that the line ending in a space or tab must have 0061 # that trailing character encoded. 0062 if s and s[-1:] in ' \t': 0063 output.write(s[:-1] + quote(s[-1]) + lineEnd) 0064 elif s == '.': 0065 output.write(quote(s) + lineEnd) 0066 else: 0067 output.write(s + lineEnd) 0068 0069 prevline = None 0070 while 1: 0071 line = input.readline() 0072 if not line: 0073 break 0074 outline = [] 0075 # Strip off any readline induced trailing newline 0076 stripped = '' 0077 if line[-1:] == '\n': 0078 line = line[:-1] 0079 stripped = '\n' 0080 # Calculate the un-length-limited encoded line 0081 for c in line: 0082 if needsquoting(c, quotetabs, header): 0083 c = quote(c) 0084 if header and c == ' ': 0085 outline.append('_') 0086 else: 0087 outline.append(c) 0088 # First, write out the previous line 0089 if prevline is not None: 0090 write(prevline) 0091 # Now see if we need any soft line breaks because of RFC-imposed 0092 # length limitations. Then do the thisline->prevline dance. 0093 thisline = EMPTYSTRING.join(outline) 0094 while len(thisline) > MAXLINESIZE: 0095 # Don't forget to include the soft line break `=' sign in the 0096 # length calculation! 0097 write(thisline[:MAXLINESIZE-1], lineEnd='=\n') 0098 thisline = thisline[MAXLINESIZE-1:] 0099 # Write out the current line 0100 prevline = thisline 0101 # Write out the last line, without a trailing newline 0102 if prevline is not None: 0103 write(prevline, lineEnd=stripped) 0104 0105 def encodestring(s, quotetabs = 0, header = 0): 0106 if b2a_qp is not None: 0107 return b2a_qp(s, quotetabs = quotetabs, header = header) 0108 from cStringIO import StringIO 0109 infp = StringIO(s) 0110 outfp = StringIO() 0111 encode(infp, outfp, quotetabs, header) 0112 return outfp.getvalue() 0113 0114 0115 0116 def decode(input, output, header = 0): 0117 """Read 'input', apply quoted-printable decoding, and write to 'output'. 0118 'input' and 'output' are files with readline() and write() methods. 0119 If 'header' is true, decode underscore as space (per RFC 1522).""" 0120 0121 if a2b_qp is not None: 0122 data = input.read() 0123 odata = a2b_qp(data, header = header) 0124 output.write(odata) 0125 return 0126 0127 new = '' 0128 while 1: 0129 line = input.readline() 0130 if not line: break 0131 i, n = 0, len(line) 0132 if n > 0 and line[n-1] == '\n': 0133 partial = 0; n = n-1 0134 # Strip trailing whitespace 0135 while n > 0 and line[n-1] in " \t\r": 0136 n = n-1 0137 else: 0138 partial = 1 0139 while i < n: 0140 c = line[i] 0141 if c == '_' and header: 0142 new = new + ' '; i = i+1 0143 elif c != ESCAPE: 0144 new = new + c; i = i+1 0145 elif i+1 == n and not partial: 0146 partial = 1; break 0147 elif i+1 < n and line[i+1] == ESCAPE: 0148 new = new + ESCAPE; i = i+2 0149 elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]): 0150 new = new + chr(unhex(line[i+1:i+3])); i = i+3 0151 else: # Bad escape sequence -- leave it in 0152 new = new + c; i = i+1 0153 if not partial: 0154 output.write(new + '\n') 0155 new = '' 0156 if new: 0157 output.write(new) 0158 0159 def decodestring(s, header = 0): 0160 if a2b_qp is not None: 0161 return a2b_qp(s, header = header) 0162 from cStringIO import StringIO 0163 infp = StringIO(s) 0164 outfp = StringIO() 0165 decode(infp, outfp, header = header) 0166 return outfp.getvalue() 0167 0168 0169 0170 # Other helper functions 0171 def ishex(c): 0172 """Return true if the character 'c' is a hexadecimal digit.""" 0173 return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F' 0174 0175 def unhex(s): 0176 """Get the integer value of a hexadecimal number.""" 0177 bits = 0 0178 for c in s: 0179 if '0' <= c <= '9': 0180 i = ord('0') 0181 elif 'a' <= c <= 'f': 0182 i = ord('a')-10 0183 elif 'A' <= c <= 'F': 0184 i = ord('A')-10 0185 else: 0186 break 0187 bits = bits*16 + (ord(c) - i) 0188 return bits 0189 0190 0191 0192 def main(): 0193 import sys 0194 import getopt 0195 try: 0196 opts, args = getopt.getopt(sys.argv[1:], 'td') 0197 except getopt.error, msg: 0198 sys.stdout = sys.stderr 0199 print msg 0200 print "usage: quopri [-t | -d] [file] ..." 0201 print "-t: quote tabs" 0202 print "-d: decode; default encode" 0203 sys.exit(2) 0204 deco = 0 0205 tabs = 0 0206 for o, a in opts: 0207 if o == '-t': tabs = 1 0208 if o == '-d': deco = 1 0209 if tabs and deco: 0210 sys.stdout = sys.stderr 0211 print "-t and -d are mutually exclusive" 0212 sys.exit(2) 0213 if not args: args = ['-'] 0214 sts = 0 0215 for file in args: 0216 if file == '-': 0217 fp = sys.stdin 0218 else: 0219 try: 0220 fp = open(file) 0221 except IOError, msg: 0222 sys.stderr.write("%s: can't open (%s)\n" % (file, msg)) 0223 sts = 1 0224 continue 0225 if deco: 0226 decode(fp, sys.stdout) 0227 else: 0228 encode(fp, sys.stdout, tabs) 0229 if fp is not sys.stdin: 0230 fp.close() 0231 if sts: 0232 sys.exit(sts) 0233 0234 0235 0236 if __name__ == '__main__': 0237 main() 0238
Generated by PyXR 0.9.4