usrbinenv python3 conversions tofrom quotedprintable transport encoding as per rfc 1521 dec 1991 version all encode decode encodestring decodestring escape b maxlinesize 76 hex b 0123456789abcdef emptystring b try from binascii import a2bqp b2aqp except importerror a2bqp none b2aqp none def needsquotingc quotetabs header assert isinstancec bytes if c in b t return quotetabs if header we have to escape because is used to escape space if c b return header return c escape or not b c b def quotec read input apply quotedprintable encoding and write to output input and output are binary file objects the quotetabs flag indicates whether embedded tabs and spaces should be quoted note that lineending tabs and spaces are always encoded as per rfc 1521 the header flag indicates whether we are encoding spaces as as per rfc 1522 if b2aqp is not none data input read odata b2aqpdata quotetabsquotetabs headerheader output writeodata return def writes outputoutput lineendb n rfc 1521 requires that the line ending in a space or tab must have that trailing character encoded if s and s1 in b t output writes 1 quotes1 lineend elif s b output writequotes lineend else output writes lineend prevline none while line input readline outline strip off any readline induced trailing newline stripped b if line1 b n line line 1 stripped b n calculate the unlengthlimited encoded line for c in line c bytesc if needsquotingc quotetabs header c quotec if header and c b outline appendb else outline appendc first write out the previous line if prevline is not none writeprevline now see if we need any soft line breaks because of rfcimposed length limitations then do the thislineprevline dance thisline emptystring joinoutline while lenthisline maxlinesize don t forget to include the soft line break sign in the length calculation writethisline maxlinesize1 lineendb n thisline thislinemaxlinesize1 write out the current line prevline thisline write out the last line without a trailing newline if prevline is not none writeprevline lineendstripped def encodestrings quotetabsfalse headerfalse if b2aqp is not none return b2aqps quotetabsquotetabs headerheader from io import bytesio infp bytesios outfp bytesio encodeinfp outfp quotetabs header return outfp getvalue def decodeinput output headerfalse strip trailing whitespace other helper functions return true if the byte ordinal c is a hexadecimal digit in ascii assert isinstancec bytes return b 0 c b 9 or b a c b f or b a c b f def unhexs usr bin env python3 conversions to from quoted printable transport encoding as per rfc 1521 dec 1991 version decide whether a particular byte ordinal 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 rfc 1521 if header we have to escape _ because _ is used to escape space quote a single character read input apply quoted printable encoding and write to output input and output are binary file objects 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 the header flag indicates whether we are encoding spaces as _ as per rfc 1522 rfc 1521 requires that the line ending in a space or tab must have that trailing character encoded strip off any readline induced trailing newline calculate the un length limited encoded line 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 don t forget to include the soft line break sign in the length calculation write out the current line write out the last line without a trailing newline read input apply quoted printable decoding and write to output input and output are binary file objects if header is true decode underscore as space per rfc 1522 strip trailing whitespace bad escape sequence leave it in other helper functions return true if the byte ordinal c is a hexadecimal digit in ascii get the integer value of a hexadecimal number
__all__ = ["encode", "decode", "encodestring", "decodestring"]

ESCAPE = b'='
MAXLINESIZE = 76
HEX = b'0123456789ABCDEF'
EMPTYSTRING = b''

try:
    from binascii import a2b_qp, b2a_qp
except ImportError:
    a2b_qp = None
    b2a_qp = None


def needsquoting(c, quotetabs, header):
    
    assert isinstance(c, bytes)
    if c in b' \t':
        return quotetabs
    
    if c == b'_':
        return header
    return c == ESCAPE or not (b' ' <= c <= b'~')

def quote(c):
    
    assert isinstance(c, bytes) and len(c)==1
    c = ord(c)
    return ESCAPE + bytes((HEX[c//16], HEX[c%16]))



def encode(input, output, quotetabs, header=False):
    

    if b2a_qp is not None:
        data = input.read()
        odata = b2a_qp(data, quotetabs=quotetabs, header=header)
        output.write(odata)
        return

    def write(s, output=output, lineEnd=b'\n'):
        
        
        if s and s[-1:] in b' \t':
            output.write(s[:-1] + quote(s[-1:]) + lineEnd)
        elif s == b'.':
            output.write(quote(s) + lineEnd)
        else:
            output.write(s + lineEnd)

    prevline = None
    while line := input.readline():
        outline = []
        
        stripped = b''
        if line[-1:] == b'\n':
            line = line[:-1]
            stripped = b'\n'
        
        for c in line:
            c = bytes((c,))
            if needsquoting(c, quotetabs, header):
                c = quote(c)
            if header and c == b' ':
                outline.append(b'_')
            else:
                outline.append(c)
        
        if prevline is not None:
            write(prevline)
        
        
        thisline = EMPTYSTRING.join(outline)
        while len(thisline) > MAXLINESIZE:
            
            
            write(thisline[:MAXLINESIZE-1], lineEnd=b'=\n')
            thisline = thisline[MAXLINESIZE-1:]
        
        prevline = thisline
    
    if prevline is not None:
        write(prevline, lineEnd=stripped)

def encodestring(s, quotetabs=False, header=False):
    if b2a_qp is not None:
        return b2a_qp(s, quotetabs=quotetabs, header=header)
    from io import BytesIO
    infp = BytesIO(s)
    outfp = BytesIO()
    encode(infp, outfp, quotetabs, header)
    return outfp.getvalue()



def decode(input, output, header=False):
    

    if a2b_qp is not None:
        data = input.read()
        odata = a2b_qp(data, header=header)
        output.write(odata)
        return

    new = b''
    while line := input.readline():
        i, n = 0, len(line)
        if n > 0 and line[n-1:n] == b'\n':
            partial = 0; n = n-1
            
            while n > 0 and line[n-1:n] in b" \t\r":
                n = n-1
        else:
            partial = 1
        while i < n:
            c = line[i:i+1]
            if c == b'_' and header:
                new = new + b' '; i = i+1
            elif c != ESCAPE:
                new = new + c; i = i+1
            elif i+1 == n and not partial:
                partial = 1; break
            elif i+1 < n and line[i+1:i+2] == ESCAPE:
                new = new + ESCAPE; i = i+2
            elif i+2 < n and ishex(line[i+1:i+2]) and ishex(line[i+2:i+3]):
                new = new + bytes((unhex(line[i+1:i+3]),)); i = i+3
            else: 
                new = new + c; i = i+1
        if not partial:
            output.write(new + b'\n')
            new = b''
    if new:
        output.write(new)

def decodestring(s, header=False):
    if a2b_qp is not None:
        return a2b_qp(s, header=header)
    from io import BytesIO
    infp = BytesIO(s)
    outfp = BytesIO()
    decode(infp, outfp, header=header)
    return outfp.getvalue()




def ishex(c):
    
    assert isinstance(c, bytes)
    return b'0' <= c <= b'9' or b'a' <= c <= b'f' or b'A' <= c <= b'F'

def unhex(s):
    
    bits = 0
    for c in s:
        c = bytes((c,))
        if b'0' <= c <= b'9':
            i = ord('0')
        elif b'a' <= c <= b'f':
            i = ord('a')-10
        elif b'A' <= c <= b'F':
            i = ord(b'A')-10
        else:
            assert False, "non-hex digit "+repr(c)
        bits = bits*16 + (ord(c) - i)
    return bits



def main():
    import sys
    import getopt
    try:
        opts, args = getopt.getopt(sys.argv[1:], 'td')
    except getopt.error as msg:
        sys.stdout = sys.stderr
        print(msg)
        print("usage: quopri [-t | -d] [file] ...")
        print("-t: quote tabs")
        print("-d: decode; default encode")
        sys.exit(2)
    deco = False
    tabs = False
    for o, a in opts:
        if o == '-t': tabs = True
        if o == '-d': deco = True
    if tabs and deco:
        sys.stdout = sys.stderr
        print("-t and -d are mutually exclusive")
        sys.exit(2)
    if not args: args = ['-']
    sts = 0
    for file in args:
        if file == '-':
            fp = sys.stdin.buffer
        else:
            try:
                fp = open(file, "rb")
            except OSError as msg:
                sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
                sts = 1
                continue
        try:
            if deco:
                decode(fp, sys.stdout.buffer)
            else:
                encode(fp, sys.stdout.buffer, tabs)
        finally:
            if file != '-':
                fp.close()
    if sts:
        sys.exit(sts)



if __name__ == '__main__':
    main()