blob: cd2f5eb86459a26ba774e15e34cdd47883deff02 [file] [log] [blame]
Guido van Rossum105bd981997-07-11 18:39:03 +00001#! /usr/bin/env python
2
Guido van Rossume7b146f2000-02-04 15:28:42 +00003"""Conversions to/from quoted-printable transport encoding as per RFC-1521."""
4
Guido van Rossumf1945461995-06-14 23:43:44 +00005# (Dec 1991 version).
6
7ESCAPE = '='
8MAXLINESIZE = 76
9HEX = '0123456789ABCDEF'
10
11def needsquoting(c, quotetabs):
Guido van Rossume7b146f2000-02-04 15:28:42 +000012 """Decide whether a particular character needs to be quoted.
13
14 The 'quotetabs' flag indicates whether tabs should be quoted."""
Guido van Rossumf1945461995-06-14 23:43:44 +000015 if c == '\t':
16 return not quotetabs
17 return c == ESCAPE or not(' ' <= c <= '~')
18
19def quote(c):
Guido van Rossume7b146f2000-02-04 15:28:42 +000020 """Quote a single character."""
Guido van Rossumf1945461995-06-14 23:43:44 +000021 if c == ESCAPE:
22 return ESCAPE * 2
23 else:
24 i = ord(c)
25 return ESCAPE + HEX[i/16] + HEX[i%16]
26
27def encode(input, output, quotetabs):
Guido van Rossume7b146f2000-02-04 15:28:42 +000028 """Read 'input', apply quoted-printable encoding, and write to 'output'.
29
30 'input' and 'output' are files with readline() and write() methods.
31 The 'quotetabs' flag indicates whether tabs should be quoted."""
Guido van Rossumf1945461995-06-14 23:43:44 +000032 while 1:
33 line = input.readline()
34 if not line: break
35 new = ''
36 last = line[-1:]
37 if last == '\n': line = line[:-1]
38 else: last = ''
39 prev = ''
40 for c in line:
41 if needsquoting(c, quotetabs):
42 c = quote(c)
43 if len(new) + len(c) >= MAXLINESIZE:
44 output.write(new + ESCAPE + '\n')
45 new = ''
46 new = new + c
47 prev = c
48 if prev in (' ', '\t'):
49 output.write(new + ESCAPE + '\n\n')
50 else:
51 output.write(new + '\n')
52
53def decode(input, output):
Guido van Rossume7b146f2000-02-04 15:28:42 +000054 """Read 'input', apply quoted-printable decoding, and write to 'output'.
55
56 'input' and 'output' are files with readline() and write() methods."""
Guido van Rossumf1945461995-06-14 23:43:44 +000057 new = ''
58 while 1:
59 line = input.readline()
60 if not line: break
61 i, n = 0, len(line)
62 if n > 0 and line[n-1] == '\n':
63 partial = 0; n = n-1
64 # Strip trailing whitespace
65 while n > 0 and line[n-1] in (' ', '\t'):
66 n = n-1
67 else:
68 partial = 1
69 while i < n:
70 c = line[i]
71 if c <> ESCAPE:
72 new = new + c; i = i+1
73 elif i+1 == n and not partial:
74 partial = 1; break
75 elif i+1 < n and line[i+1] == ESCAPE:
76 new = new + ESCAPE; i = i+2
77 elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
78 new = new + chr(unhex(line[i+1:i+3])); i = i+3
79 else: # Bad escape sequence -- leave it in
80 new = new + c; i = i+1
81 if not partial:
82 output.write(new + '\n')
83 new = ''
84 if new:
85 output.write(new)
86
87def ishex(c):
Guido van Rossume7b146f2000-02-04 15:28:42 +000088 """Return true if the character 'c' is a hexadecimal digit."""
Guido van Rossumf1945461995-06-14 23:43:44 +000089 return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
90
91def unhex(s):
Guido van Rossume7b146f2000-02-04 15:28:42 +000092 """Get the integer value of a hexadecimal number."""
Guido van Rossumf1945461995-06-14 23:43:44 +000093 bits = 0
94 for c in s:
95 if '0' <= c <= '9':
96 i = ord('0')
97 elif 'a' <= c <= 'f':
98 i = ord('a')-10
99 elif 'A' <= c <= 'F':
100 i = ord('A')-10
101 else:
102 break
103 bits = bits*16 + (ord(c) - i)
104 return bits
105
106def test():
107 import sys
Guido van Rossum54c15101995-09-18 21:49:24 +0000108 import getopt
109 try:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000110 opts, args = getopt.getopt(sys.argv[1:], 'td')
Guido van Rossum54c15101995-09-18 21:49:24 +0000111 except getopt.error, msg:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000112 sys.stdout = sys.stderr
113 print msg
114 print "usage: quopri [-t | -d] [file] ..."
115 print "-t: quote tabs"
116 print "-d: decode; default encode"
117 sys.exit(2)
Guido van Rossum54c15101995-09-18 21:49:24 +0000118 deco = 0
119 tabs = 0
120 for o, a in opts:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000121 if o == '-t': tabs = 1
122 if o == '-d': deco = 1
Guido van Rossum54c15101995-09-18 21:49:24 +0000123 if tabs and deco:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000124 sys.stdout = sys.stderr
125 print "-t and -d are mutually exclusive"
126 sys.exit(2)
Guido van Rossum54c15101995-09-18 21:49:24 +0000127 if not args: args = ['-']
128 sts = 0
129 for file in args:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000130 if file == '-':
131 fp = sys.stdin
132 else:
133 try:
134 fp = open(file)
135 except IOError, msg:
136 sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
137 sts = 1
138 continue
139 if deco:
140 decode(fp, sys.stdout)
141 else:
142 encode(fp, sys.stdout, tabs)
143 if fp is not sys.stdin:
144 fp.close()
Guido van Rossum54c15101995-09-18 21:49:24 +0000145 if sts:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000146 sys.exit(sts)
Guido van Rossumf1945461995-06-14 23:43:44 +0000147
148if __name__ == '__main__':
Guido van Rossum54c15101995-09-18 21:49:24 +0000149 test()