xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/quopri.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
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