1*cda5da8dSAndroid Build Coastguard Worker#! /usr/bin/env python3 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard Worker"""Conversions to/from quoted-printable transport encoding as per RFC 1521.""" 4*cda5da8dSAndroid Build Coastguard Worker 5*cda5da8dSAndroid Build Coastguard Worker# (Dec 1991 version). 6*cda5da8dSAndroid Build Coastguard Worker 7*cda5da8dSAndroid Build Coastguard Worker__all__ = ["encode", "decode", "encodestring", "decodestring"] 8*cda5da8dSAndroid Build Coastguard Worker 9*cda5da8dSAndroid Build Coastguard WorkerESCAPE = b'=' 10*cda5da8dSAndroid Build Coastguard WorkerMAXLINESIZE = 76 11*cda5da8dSAndroid Build Coastguard WorkerHEX = b'0123456789ABCDEF' 12*cda5da8dSAndroid Build Coastguard WorkerEMPTYSTRING = b'' 13*cda5da8dSAndroid Build Coastguard Worker 14*cda5da8dSAndroid Build Coastguard Workertry: 15*cda5da8dSAndroid Build Coastguard Worker from binascii import a2b_qp, b2a_qp 16*cda5da8dSAndroid Build Coastguard Workerexcept ImportError: 17*cda5da8dSAndroid Build Coastguard Worker a2b_qp = None 18*cda5da8dSAndroid Build Coastguard Worker b2a_qp = None 19*cda5da8dSAndroid Build Coastguard Worker 20*cda5da8dSAndroid Build Coastguard Worker 21*cda5da8dSAndroid Build Coastguard Workerdef needsquoting(c, quotetabs, header): 22*cda5da8dSAndroid Build Coastguard Worker """Decide whether a particular byte ordinal needs to be quoted. 23*cda5da8dSAndroid Build Coastguard Worker 24*cda5da8dSAndroid Build Coastguard Worker The 'quotetabs' flag indicates whether embedded tabs and spaces should be 25*cda5da8dSAndroid Build Coastguard Worker quoted. Note that line-ending tabs and spaces are always encoded, as per 26*cda5da8dSAndroid Build Coastguard Worker RFC 1521. 27*cda5da8dSAndroid Build Coastguard Worker """ 28*cda5da8dSAndroid Build Coastguard Worker assert isinstance(c, bytes) 29*cda5da8dSAndroid Build Coastguard Worker if c in b' \t': 30*cda5da8dSAndroid Build Coastguard Worker return quotetabs 31*cda5da8dSAndroid Build Coastguard Worker # if header, we have to escape _ because _ is used to escape space 32*cda5da8dSAndroid Build Coastguard Worker if c == b'_': 33*cda5da8dSAndroid Build Coastguard Worker return header 34*cda5da8dSAndroid Build Coastguard Worker return c == ESCAPE or not (b' ' <= c <= b'~') 35*cda5da8dSAndroid Build Coastguard Worker 36*cda5da8dSAndroid Build Coastguard Workerdef quote(c): 37*cda5da8dSAndroid Build Coastguard Worker """Quote a single character.""" 38*cda5da8dSAndroid Build Coastguard Worker assert isinstance(c, bytes) and len(c)==1 39*cda5da8dSAndroid Build Coastguard Worker c = ord(c) 40*cda5da8dSAndroid Build Coastguard Worker return ESCAPE + bytes((HEX[c//16], HEX[c%16])) 41*cda5da8dSAndroid Build Coastguard Worker 42*cda5da8dSAndroid Build Coastguard Worker 43*cda5da8dSAndroid Build Coastguard Worker 44*cda5da8dSAndroid Build Coastguard Workerdef encode(input, output, quotetabs, header=False): 45*cda5da8dSAndroid Build Coastguard Worker """Read 'input', apply quoted-printable encoding, and write to 'output'. 46*cda5da8dSAndroid Build Coastguard Worker 47*cda5da8dSAndroid Build Coastguard Worker 'input' and 'output' are binary file objects. The 'quotetabs' flag 48*cda5da8dSAndroid Build Coastguard Worker indicates whether embedded tabs and spaces should be quoted. Note that 49*cda5da8dSAndroid Build Coastguard Worker line-ending tabs and spaces are always encoded, as per RFC 1521. 50*cda5da8dSAndroid Build Coastguard Worker The 'header' flag indicates whether we are encoding spaces as _ as per RFC 51*cda5da8dSAndroid Build Coastguard Worker 1522.""" 52*cda5da8dSAndroid Build Coastguard Worker 53*cda5da8dSAndroid Build Coastguard Worker if b2a_qp is not None: 54*cda5da8dSAndroid Build Coastguard Worker data = input.read() 55*cda5da8dSAndroid Build Coastguard Worker odata = b2a_qp(data, quotetabs=quotetabs, header=header) 56*cda5da8dSAndroid Build Coastguard Worker output.write(odata) 57*cda5da8dSAndroid Build Coastguard Worker return 58*cda5da8dSAndroid Build Coastguard Worker 59*cda5da8dSAndroid Build Coastguard Worker def write(s, output=output, lineEnd=b'\n'): 60*cda5da8dSAndroid Build Coastguard Worker # RFC 1521 requires that the line ending in a space or tab must have 61*cda5da8dSAndroid Build Coastguard Worker # that trailing character encoded. 62*cda5da8dSAndroid Build Coastguard Worker if s and s[-1:] in b' \t': 63*cda5da8dSAndroid Build Coastguard Worker output.write(s[:-1] + quote(s[-1:]) + lineEnd) 64*cda5da8dSAndroid Build Coastguard Worker elif s == b'.': 65*cda5da8dSAndroid Build Coastguard Worker output.write(quote(s) + lineEnd) 66*cda5da8dSAndroid Build Coastguard Worker else: 67*cda5da8dSAndroid Build Coastguard Worker output.write(s + lineEnd) 68*cda5da8dSAndroid Build Coastguard Worker 69*cda5da8dSAndroid Build Coastguard Worker prevline = None 70*cda5da8dSAndroid Build Coastguard Worker while 1: 71*cda5da8dSAndroid Build Coastguard Worker line = input.readline() 72*cda5da8dSAndroid Build Coastguard Worker if not line: 73*cda5da8dSAndroid Build Coastguard Worker break 74*cda5da8dSAndroid Build Coastguard Worker outline = [] 75*cda5da8dSAndroid Build Coastguard Worker # Strip off any readline induced trailing newline 76*cda5da8dSAndroid Build Coastguard Worker stripped = b'' 77*cda5da8dSAndroid Build Coastguard Worker if line[-1:] == b'\n': 78*cda5da8dSAndroid Build Coastguard Worker line = line[:-1] 79*cda5da8dSAndroid Build Coastguard Worker stripped = b'\n' 80*cda5da8dSAndroid Build Coastguard Worker # Calculate the un-length-limited encoded line 81*cda5da8dSAndroid Build Coastguard Worker for c in line: 82*cda5da8dSAndroid Build Coastguard Worker c = bytes((c,)) 83*cda5da8dSAndroid Build Coastguard Worker if needsquoting(c, quotetabs, header): 84*cda5da8dSAndroid Build Coastguard Worker c = quote(c) 85*cda5da8dSAndroid Build Coastguard Worker if header and c == b' ': 86*cda5da8dSAndroid Build Coastguard Worker outline.append(b'_') 87*cda5da8dSAndroid Build Coastguard Worker else: 88*cda5da8dSAndroid Build Coastguard Worker outline.append(c) 89*cda5da8dSAndroid Build Coastguard Worker # First, write out the previous line 90*cda5da8dSAndroid Build Coastguard Worker if prevline is not None: 91*cda5da8dSAndroid Build Coastguard Worker write(prevline) 92*cda5da8dSAndroid Build Coastguard Worker # Now see if we need any soft line breaks because of RFC-imposed 93*cda5da8dSAndroid Build Coastguard Worker # length limitations. Then do the thisline->prevline dance. 94*cda5da8dSAndroid Build Coastguard Worker thisline = EMPTYSTRING.join(outline) 95*cda5da8dSAndroid Build Coastguard Worker while len(thisline) > MAXLINESIZE: 96*cda5da8dSAndroid Build Coastguard Worker # Don't forget to include the soft line break `=' sign in the 97*cda5da8dSAndroid Build Coastguard Worker # length calculation! 98*cda5da8dSAndroid Build Coastguard Worker write(thisline[:MAXLINESIZE-1], lineEnd=b'=\n') 99*cda5da8dSAndroid Build Coastguard Worker thisline = thisline[MAXLINESIZE-1:] 100*cda5da8dSAndroid Build Coastguard Worker # Write out the current line 101*cda5da8dSAndroid Build Coastguard Worker prevline = thisline 102*cda5da8dSAndroid Build Coastguard Worker # Write out the last line, without a trailing newline 103*cda5da8dSAndroid Build Coastguard Worker if prevline is not None: 104*cda5da8dSAndroid Build Coastguard Worker write(prevline, lineEnd=stripped) 105*cda5da8dSAndroid Build Coastguard Worker 106*cda5da8dSAndroid Build Coastguard Workerdef encodestring(s, quotetabs=False, header=False): 107*cda5da8dSAndroid Build Coastguard Worker if b2a_qp is not None: 108*cda5da8dSAndroid Build Coastguard Worker return b2a_qp(s, quotetabs=quotetabs, header=header) 109*cda5da8dSAndroid Build Coastguard Worker from io import BytesIO 110*cda5da8dSAndroid Build Coastguard Worker infp = BytesIO(s) 111*cda5da8dSAndroid Build Coastguard Worker outfp = BytesIO() 112*cda5da8dSAndroid Build Coastguard Worker encode(infp, outfp, quotetabs, header) 113*cda5da8dSAndroid Build Coastguard Worker return outfp.getvalue() 114*cda5da8dSAndroid Build Coastguard Worker 115*cda5da8dSAndroid Build Coastguard Worker 116*cda5da8dSAndroid Build Coastguard Worker 117*cda5da8dSAndroid Build Coastguard Workerdef decode(input, output, header=False): 118*cda5da8dSAndroid Build Coastguard Worker """Read 'input', apply quoted-printable decoding, and write to 'output'. 119*cda5da8dSAndroid Build Coastguard Worker 'input' and 'output' are binary file objects. 120*cda5da8dSAndroid Build Coastguard Worker If 'header' is true, decode underscore as space (per RFC 1522).""" 121*cda5da8dSAndroid Build Coastguard Worker 122*cda5da8dSAndroid Build Coastguard Worker if a2b_qp is not None: 123*cda5da8dSAndroid Build Coastguard Worker data = input.read() 124*cda5da8dSAndroid Build Coastguard Worker odata = a2b_qp(data, header=header) 125*cda5da8dSAndroid Build Coastguard Worker output.write(odata) 126*cda5da8dSAndroid Build Coastguard Worker return 127*cda5da8dSAndroid Build Coastguard Worker 128*cda5da8dSAndroid Build Coastguard Worker new = b'' 129*cda5da8dSAndroid Build Coastguard Worker while 1: 130*cda5da8dSAndroid Build Coastguard Worker line = input.readline() 131*cda5da8dSAndroid Build Coastguard Worker if not line: break 132*cda5da8dSAndroid Build Coastguard Worker i, n = 0, len(line) 133*cda5da8dSAndroid Build Coastguard Worker if n > 0 and line[n-1:n] == b'\n': 134*cda5da8dSAndroid Build Coastguard Worker partial = 0; n = n-1 135*cda5da8dSAndroid Build Coastguard Worker # Strip trailing whitespace 136*cda5da8dSAndroid Build Coastguard Worker while n > 0 and line[n-1:n] in b" \t\r": 137*cda5da8dSAndroid Build Coastguard Worker n = n-1 138*cda5da8dSAndroid Build Coastguard Worker else: 139*cda5da8dSAndroid Build Coastguard Worker partial = 1 140*cda5da8dSAndroid Build Coastguard Worker while i < n: 141*cda5da8dSAndroid Build Coastguard Worker c = line[i:i+1] 142*cda5da8dSAndroid Build Coastguard Worker if c == b'_' and header: 143*cda5da8dSAndroid Build Coastguard Worker new = new + b' '; i = i+1 144*cda5da8dSAndroid Build Coastguard Worker elif c != ESCAPE: 145*cda5da8dSAndroid Build Coastguard Worker new = new + c; i = i+1 146*cda5da8dSAndroid Build Coastguard Worker elif i+1 == n and not partial: 147*cda5da8dSAndroid Build Coastguard Worker partial = 1; break 148*cda5da8dSAndroid Build Coastguard Worker elif i+1 < n and line[i+1:i+2] == ESCAPE: 149*cda5da8dSAndroid Build Coastguard Worker new = new + ESCAPE; i = i+2 150*cda5da8dSAndroid Build Coastguard Worker elif i+2 < n and ishex(line[i+1:i+2]) and ishex(line[i+2:i+3]): 151*cda5da8dSAndroid Build Coastguard Worker new = new + bytes((unhex(line[i+1:i+3]),)); i = i+3 152*cda5da8dSAndroid Build Coastguard Worker else: # Bad escape sequence -- leave it in 153*cda5da8dSAndroid Build Coastguard Worker new = new + c; i = i+1 154*cda5da8dSAndroid Build Coastguard Worker if not partial: 155*cda5da8dSAndroid Build Coastguard Worker output.write(new + b'\n') 156*cda5da8dSAndroid Build Coastguard Worker new = b'' 157*cda5da8dSAndroid Build Coastguard Worker if new: 158*cda5da8dSAndroid Build Coastguard Worker output.write(new) 159*cda5da8dSAndroid Build Coastguard Worker 160*cda5da8dSAndroid Build Coastguard Workerdef decodestring(s, header=False): 161*cda5da8dSAndroid Build Coastguard Worker if a2b_qp is not None: 162*cda5da8dSAndroid Build Coastguard Worker return a2b_qp(s, header=header) 163*cda5da8dSAndroid Build Coastguard Worker from io import BytesIO 164*cda5da8dSAndroid Build Coastguard Worker infp = BytesIO(s) 165*cda5da8dSAndroid Build Coastguard Worker outfp = BytesIO() 166*cda5da8dSAndroid Build Coastguard Worker decode(infp, outfp, header=header) 167*cda5da8dSAndroid Build Coastguard Worker return outfp.getvalue() 168*cda5da8dSAndroid Build Coastguard Worker 169*cda5da8dSAndroid Build Coastguard Worker 170*cda5da8dSAndroid Build Coastguard Worker 171*cda5da8dSAndroid Build Coastguard Worker# Other helper functions 172*cda5da8dSAndroid Build Coastguard Workerdef ishex(c): 173*cda5da8dSAndroid Build Coastguard Worker """Return true if the byte ordinal 'c' is a hexadecimal digit in ASCII.""" 174*cda5da8dSAndroid Build Coastguard Worker assert isinstance(c, bytes) 175*cda5da8dSAndroid Build Coastguard Worker return b'0' <= c <= b'9' or b'a' <= c <= b'f' or b'A' <= c <= b'F' 176*cda5da8dSAndroid Build Coastguard Worker 177*cda5da8dSAndroid Build Coastguard Workerdef unhex(s): 178*cda5da8dSAndroid Build Coastguard Worker """Get the integer value of a hexadecimal number.""" 179*cda5da8dSAndroid Build Coastguard Worker bits = 0 180*cda5da8dSAndroid Build Coastguard Worker for c in s: 181*cda5da8dSAndroid Build Coastguard Worker c = bytes((c,)) 182*cda5da8dSAndroid Build Coastguard Worker if b'0' <= c <= b'9': 183*cda5da8dSAndroid Build Coastguard Worker i = ord('0') 184*cda5da8dSAndroid Build Coastguard Worker elif b'a' <= c <= b'f': 185*cda5da8dSAndroid Build Coastguard Worker i = ord('a')-10 186*cda5da8dSAndroid Build Coastguard Worker elif b'A' <= c <= b'F': 187*cda5da8dSAndroid Build Coastguard Worker i = ord(b'A')-10 188*cda5da8dSAndroid Build Coastguard Worker else: 189*cda5da8dSAndroid Build Coastguard Worker assert False, "non-hex digit "+repr(c) 190*cda5da8dSAndroid Build Coastguard Worker bits = bits*16 + (ord(c) - i) 191*cda5da8dSAndroid Build Coastguard Worker return bits 192*cda5da8dSAndroid Build Coastguard Worker 193*cda5da8dSAndroid Build Coastguard Worker 194*cda5da8dSAndroid Build Coastguard Worker 195*cda5da8dSAndroid Build Coastguard Workerdef main(): 196*cda5da8dSAndroid Build Coastguard Worker import sys 197*cda5da8dSAndroid Build Coastguard Worker import getopt 198*cda5da8dSAndroid Build Coastguard Worker try: 199*cda5da8dSAndroid Build Coastguard Worker opts, args = getopt.getopt(sys.argv[1:], 'td') 200*cda5da8dSAndroid Build Coastguard Worker except getopt.error as msg: 201*cda5da8dSAndroid Build Coastguard Worker sys.stdout = sys.stderr 202*cda5da8dSAndroid Build Coastguard Worker print(msg) 203*cda5da8dSAndroid Build Coastguard Worker print("usage: quopri [-t | -d] [file] ...") 204*cda5da8dSAndroid Build Coastguard Worker print("-t: quote tabs") 205*cda5da8dSAndroid Build Coastguard Worker print("-d: decode; default encode") 206*cda5da8dSAndroid Build Coastguard Worker sys.exit(2) 207*cda5da8dSAndroid Build Coastguard Worker deco = False 208*cda5da8dSAndroid Build Coastguard Worker tabs = False 209*cda5da8dSAndroid Build Coastguard Worker for o, a in opts: 210*cda5da8dSAndroid Build Coastguard Worker if o == '-t': tabs = True 211*cda5da8dSAndroid Build Coastguard Worker if o == '-d': deco = True 212*cda5da8dSAndroid Build Coastguard Worker if tabs and deco: 213*cda5da8dSAndroid Build Coastguard Worker sys.stdout = sys.stderr 214*cda5da8dSAndroid Build Coastguard Worker print("-t and -d are mutually exclusive") 215*cda5da8dSAndroid Build Coastguard Worker sys.exit(2) 216*cda5da8dSAndroid Build Coastguard Worker if not args: args = ['-'] 217*cda5da8dSAndroid Build Coastguard Worker sts = 0 218*cda5da8dSAndroid Build Coastguard Worker for file in args: 219*cda5da8dSAndroid Build Coastguard Worker if file == '-': 220*cda5da8dSAndroid Build Coastguard Worker fp = sys.stdin.buffer 221*cda5da8dSAndroid Build Coastguard Worker else: 222*cda5da8dSAndroid Build Coastguard Worker try: 223*cda5da8dSAndroid Build Coastguard Worker fp = open(file, "rb") 224*cda5da8dSAndroid Build Coastguard Worker except OSError as msg: 225*cda5da8dSAndroid Build Coastguard Worker sys.stderr.write("%s: can't open (%s)\n" % (file, msg)) 226*cda5da8dSAndroid Build Coastguard Worker sts = 1 227*cda5da8dSAndroid Build Coastguard Worker continue 228*cda5da8dSAndroid Build Coastguard Worker try: 229*cda5da8dSAndroid Build Coastguard Worker if deco: 230*cda5da8dSAndroid Build Coastguard Worker decode(fp, sys.stdout.buffer) 231*cda5da8dSAndroid Build Coastguard Worker else: 232*cda5da8dSAndroid Build Coastguard Worker encode(fp, sys.stdout.buffer, tabs) 233*cda5da8dSAndroid Build Coastguard Worker finally: 234*cda5da8dSAndroid Build Coastguard Worker if file != '-': 235*cda5da8dSAndroid Build Coastguard Worker fp.close() 236*cda5da8dSAndroid Build Coastguard Worker if sts: 237*cda5da8dSAndroid Build Coastguard Worker sys.exit(sts) 238*cda5da8dSAndroid Build Coastguard Worker 239*cda5da8dSAndroid Build Coastguard Worker 240*cda5da8dSAndroid Build Coastguard Worker 241*cda5da8dSAndroid Build Coastguard Workerif __name__ == '__main__': 242*cda5da8dSAndroid Build Coastguard Worker main() 243