blob: 5f5e80c6dbfbd8bea1031704744bb66974485c6b [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
Skip Montanaroc62c81e2001-02-12 02:00:42 +00007__all__ = ["encode","decode"]
8
Guido van Rossumf1945461995-06-14 23:43:44 +00009ESCAPE = '='
10MAXLINESIZE = 76
11HEX = '0123456789ABCDEF'
12
13def needsquoting(c, quotetabs):
Jeremy Hylton77249442000-10-05 17:24:33 +000014 """Decide whether a particular character needs to be quoted.
Guido van Rossume7b146f2000-02-04 15:28:42 +000015
Jeremy Hylton77249442000-10-05 17:24:33 +000016 The 'quotetabs' flag indicates whether tabs should be quoted."""
17 if c == '\t':
18 return not quotetabs
19 return c == ESCAPE or not(' ' <= c <= '~')
Guido van Rossumf1945461995-06-14 23:43:44 +000020
21def quote(c):
Jeremy Hylton77249442000-10-05 17:24:33 +000022 """Quote a single character."""
23 i = ord(c)
24 return ESCAPE + HEX[i/16] + HEX[i%16]
Guido van Rossumf1945461995-06-14 23:43:44 +000025
26def encode(input, output, quotetabs):
Jeremy Hylton77249442000-10-05 17:24:33 +000027 """Read 'input', apply quoted-printable encoding, and write to 'output'.
Guido van Rossume7b146f2000-02-04 15:28:42 +000028
Jeremy Hylton77249442000-10-05 17:24:33 +000029 'input' and 'output' are files with readline() and write() methods.
30 The 'quotetabs' flag indicates whether tabs should be quoted.
Tim Peters2344fae2001-01-15 00:50:52 +000031 """
Jeremy Hylton77249442000-10-05 17:24:33 +000032 while 1:
33 line = input.readline()
34 if not line:
35 break
36 new = ''
37 last = line[-1:]
38 if last == '\n':
39 line = line[:-1]
40 else:
41 last = ''
42 prev = ''
43 for c in line:
44 if needsquoting(c, quotetabs):
45 c = quote(c)
46 if len(new) + len(c) >= MAXLINESIZE:
47 output.write(new + ESCAPE + '\n')
48 new = ''
49 new = new + c
50 prev = c
51 if prev in (' ', '\t'):
52 output.write(new + ESCAPE + '\n\n')
53 else:
54 output.write(new + '\n')
Guido van Rossumf1945461995-06-14 23:43:44 +000055
56def decode(input, output):
Jeremy Hylton77249442000-10-05 17:24:33 +000057 """Read 'input', apply quoted-printable decoding, and write to 'output'.
Guido van Rossume7b146f2000-02-04 15:28:42 +000058
Jeremy Hylton77249442000-10-05 17:24:33 +000059 'input' and 'output' are files with readline() and write() methods."""
60 new = ''
61 while 1:
62 line = input.readline()
63 if not line: break
64 i, n = 0, len(line)
65 if n > 0 and line[n-1] == '\n':
66 partial = 0; n = n-1
67 # Strip trailing whitespace
68 while n > 0 and line[n-1] in (' ', '\t'):
69 n = n-1
70 else:
71 partial = 1
72 while i < n:
73 c = line[i]
Fred Drake8152d322000-12-12 23:20:45 +000074 if c != ESCAPE:
Jeremy Hylton77249442000-10-05 17:24:33 +000075 new = new + c; i = i+1
76 elif i+1 == n and not partial:
77 partial = 1; break
78 elif i+1 < n and line[i+1] == ESCAPE:
79 new = new + ESCAPE; i = i+2
80 elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
81 new = new + chr(unhex(line[i+1:i+3])); i = i+3
82 else: # Bad escape sequence -- leave it in
83 new = new + c; i = i+1
84 if not partial:
85 output.write(new + '\n')
86 new = ''
87 if new:
88 output.write(new)
Guido van Rossumf1945461995-06-14 23:43:44 +000089
90def ishex(c):
Jeremy Hylton77249442000-10-05 17:24:33 +000091 """Return true if the character 'c' is a hexadecimal digit."""
92 return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
Guido van Rossumf1945461995-06-14 23:43:44 +000093
94def unhex(s):
Jeremy Hylton77249442000-10-05 17:24:33 +000095 """Get the integer value of a hexadecimal number."""
96 bits = 0
97 for c in s:
98 if '0' <= c <= '9':
99 i = ord('0')
100 elif 'a' <= c <= 'f':
101 i = ord('a')-10
102 elif 'A' <= c <= 'F':
103 i = ord('A')-10
104 else:
105 break
106 bits = bits*16 + (ord(c) - i)
107 return bits
Guido van Rossumf1945461995-06-14 23:43:44 +0000108
109def test():
Jeremy Hylton77249442000-10-05 17:24:33 +0000110 import sys
111 import getopt
112 try:
113 opts, args = getopt.getopt(sys.argv[1:], 'td')
114 except getopt.error, msg:
115 sys.stdout = sys.stderr
116 print msg
117 print "usage: quopri [-t | -d] [file] ..."
118 print "-t: quote tabs"
119 print "-d: decode; default encode"
120 sys.exit(2)
121 deco = 0
122 tabs = 0
123 for o, a in opts:
124 if o == '-t': tabs = 1
125 if o == '-d': deco = 1
126 if tabs and deco:
127 sys.stdout = sys.stderr
128 print "-t and -d are mutually exclusive"
129 sys.exit(2)
130 if not args: args = ['-']
131 sts = 0
132 for file in args:
133 if file == '-':
134 fp = sys.stdin
135 else:
136 try:
137 fp = open(file)
138 except IOError, msg:
139 sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
140 sts = 1
141 continue
142 if deco:
143 decode(fp, sys.stdout)
144 else:
145 encode(fp, sys.stdout, tabs)
146 if fp is not sys.stdin:
147 fp.close()
148 if sts:
149 sys.exit(sts)
Guido van Rossumf1945461995-06-14 23:43:44 +0000150
151if __name__ == '__main__':
Jeremy Hylton77249442000-10-05 17:24:33 +0000152 test()