3 """Conversions to/from quoted-printable transport encoding as per RFC 1521."""
7 __all__
= ["encode", "decode", "encodestring", "decodestring"]
11 HEX
= b
'0123456789ABCDEF'
15 from binascii
import a2b_qp
, b2a_qp
21 def needsquoting(c
, quotetabs
, header
):
22 """Decide whether a particular byte ordinal needs to be quoted.
24 The 'quotetabs' flag indicates whether embedded tabs and spaces should be
25 quoted. Note that line-ending tabs and spaces are always encoded, as per
28 assert isinstance(c
, bytes
)
31 # if header, we have to escape _ because _ is used to escape space
34 return c
== ESCAPE
or not (b
' ' <= c
<= b
'~')
37 """Quote a single character."""
38 assert isinstance(c
, bytes
) and len(c
)==1
40 return ESCAPE
+ bytes((HEX
[c
//16], HEX
[c
%16]))
44 def encode(input, output
, quotetabs
, header
= 0):
45 """Read 'input', apply quoted-printable encoding, and write to 'output'.
47 'input' and 'output' are files with readline() and write() methods.
48 The 'quotetabs' flag indicates whether embedded tabs and spaces should be
49 quoted. Note that line-ending tabs and spaces are always encoded, as per
51 The 'header' flag indicates whether we are encoding spaces as _ as per
55 if b2a_qp
is not None:
57 odata
= b2a_qp(data
, quotetabs
= quotetabs
, header
= header
)
61 def write(s
, output
=output
, lineEnd
=b
'\n'):
62 # RFC 1521 requires that the line ending in a space or tab must have
63 # that trailing character encoded.
64 if s
and s
[-1:] in b
' \t':
65 output
.write(s
[:-1] + quote(s
[-1:]) + lineEnd
)
67 output
.write(quote(s
) + lineEnd
)
69 output
.write(s
+ lineEnd
)
73 line
= input.readline()
77 # Strip off any readline induced trailing newline
79 if line
[-1:] == b
'\n':
82 # Calculate the un-length-limited encoded line
85 if needsquoting(c
, quotetabs
, header
):
87 if header
and c
== b
' ':
91 # First, write out the previous line
92 if prevline
is not None:
94 # Now see if we need any soft line breaks because of RFC-imposed
95 # length limitations. Then do the thisline->prevline dance.
96 thisline
= EMPTYSTRING
.join(outline
)
97 while len(thisline
) > MAXLINESIZE
:
98 # Don't forget to include the soft line break `=' sign in the
100 write(thisline
[:MAXLINESIZE
-1], lineEnd
=b
'=\n')
101 thisline
= thisline
[MAXLINESIZE
-1:]
102 # Write out the current line
104 # Write out the last line, without a trailing newline
105 if prevline
is not None:
106 write(prevline
, lineEnd
=stripped
)
108 def encodestring(s
, quotetabs
= 0, header
= 0):
109 if b2a_qp
is not None:
110 return b2a_qp(s
, quotetabs
= quotetabs
, header
= header
)
111 from io
import BytesIO
114 encode(infp
, outfp
, quotetabs
, header
)
115 return outfp
.getvalue()
119 def decode(input, output
, header
= 0):
120 """Read 'input', apply quoted-printable decoding, and write to 'output'.
121 'input' and 'output' are files with readline() and write() methods.
122 If 'header' is true, decode underscore as space (per RFC 1522)."""
124 if a2b_qp
is not None:
126 odata
= a2b_qp(data
, header
= header
)
132 line
= input.readline()
135 if n
> 0 and line
[n
-1:n
] == b
'\n':
137 # Strip trailing whitespace
138 while n
> 0 and line
[n
-1:n
] in b
" \t\r":
144 if c
== b
'_' and header
:
145 new
= new
+ b
' '; i
= i
+1
147 new
= new
+ c
; i
= i
+1
148 elif i
+1 == n
and not partial
:
150 elif i
+1 < n
and line
[i
+1] == ESCAPE
:
151 new
= new
+ ESCAPE
; i
= i
+2
152 elif i
+2 < n
and ishex(line
[i
+1:i
+2]) and ishex(line
[i
+2:i
+3]):
153 new
= new
+ bytes((unhex(line
[i
+1:i
+3]),)); i
= i
+3
154 else: # Bad escape sequence -- leave it in
155 new
= new
+ c
; i
= i
+1
157 output
.write(new
+ b
'\n')
162 def decodestring(s
, header
= 0):
163 if a2b_qp
is not None:
164 return a2b_qp(s
, header
= header
)
165 from io
import BytesIO
168 decode(infp
, outfp
, header
= header
)
169 return outfp
.getvalue()
173 # Other helper functions
175 """Return true if the byte ordinal 'c' is a hexadecimal digit in ASCII."""
176 assert isinstance(c
, bytes
)
177 return b
'0' <= c
<= b
'9' or b
'a' <= c
<= b
'f' or b
'A' <= c
<= b
'F'
180 """Get the integer value of a hexadecimal number."""
184 if b
'0' <= c
<= b
'9':
186 elif b
'a' <= c
<= b
'f':
188 elif b
'A' <= c
<= b
'F':
191 assert False, "non-hex digit "+repr(c
)
192 bits
= bits
*16 + (ord(c
) - i
)
201 opts
, args
= getopt
.getopt(sys
.argv
[1:], 'td')
202 except getopt
.error
as msg
:
203 sys
.stdout
= sys
.stderr
205 print("usage: quopri [-t | -d] [file] ...")
206 print("-t: quote tabs")
207 print("-d: decode; default encode")
212 if o
== '-t': tabs
= 1
213 if o
== '-d': deco
= 1
215 sys
.stdout
= sys
.stderr
216 print("-t and -d are mutually exclusive")
218 if not args
: args
= ['-']
222 fp
= sys
.stdin
.buffer
225 fp
= open(file, "rb")
226 except IOError as msg
:
227 sys
.stderr
.write("%s: can't open (%s)\n" % (file, msg
))
232 decode(fp
, sys
.stdout
.buffer)
234 encode(fp
, sys
.stdout
.buffer, tabs
)
243 if __name__
== '__main__':