py-cvs-rel2_1 (Rev 1.2) merge
[python/dscho.git] / Lib / quopri.py
blob575fcd11fb4b1b2c0e7831080bd8e1add49bf87a
1 #! /usr/bin/env python
3 """Conversions to/from quoted-printable transport encoding as per RFC 1521."""
5 # (Dec 1991 version).
7 __all__ = ["encode", "decode", "encodestring", "decodestring"]
9 ESCAPE = '='
10 MAXLINESIZE = 76
11 HEX = '0123456789ABCDEF'
12 EMPTYSTRING = ''
16 def needsquoting(c, quotetabs):
17 """Decide whether a particular character needs to be quoted.
19 The 'quotetabs' flag indicates whether embedded tabs and spaces should be
20 quoted. Note that line-ending tabs and spaces are always encoded, as per
21 RFC 1521.
22 """
23 if c in ' \t':
24 return quotetabs
25 return c == ESCAPE or not (' ' <= c <= '~')
27 def quote(c):
28 """Quote a single character."""
29 i = ord(c)
30 return ESCAPE + HEX[i/16] + HEX[i%16]
34 def encode(input, output, quotetabs):
35 """Read 'input', apply quoted-printable encoding, and write to 'output'.
37 'input' and 'output' are files with readline() and write() methods.
38 The 'quotetabs' flag indicates whether embedded tabs and spaces should be
39 quoted. Note that line-ending tabs and spaces are always encoded, as per
40 RFC 1521.
41 """
42 def write(s, output=output, lineEnd='\n'):
43 # RFC 1521 requires that the line ending in a space or tab must have
44 # that trailing character encoded.
45 if s and s[-1:] in ' \t':
46 output.write(s[:-1] + quote(s[-1]) + lineEnd)
47 else:
48 output.write(s + lineEnd)
50 prevline = None
51 while 1:
52 line = input.readline()
53 if not line:
54 break
55 outline = []
56 # Strip off any readline induced trailing newline
57 stripped = ''
58 if line[-1:] == '\n':
59 line = line[:-1]
60 stripped = '\n'
61 # Calculate the un-length-limited encoded line
62 for c in line:
63 if needsquoting(c, quotetabs):
64 c = quote(c)
65 outline.append(c)
66 # First, write out the previous line
67 if prevline is not None:
68 write(prevline)
69 # Now see if we need any soft line breaks because of RFC-imposed
70 # length limitations. Then do the thisline->prevline dance.
71 thisline = EMPTYSTRING.join(outline)
72 while len(thisline) > MAXLINESIZE:
73 # Don't forget to include the soft line break `=' sign in the
74 # length calculation!
75 write(thisline[:MAXLINESIZE-1], lineEnd='=\n')
76 thisline = thisline[MAXLINESIZE-1:]
77 # Write out the current line
78 prevline = thisline
79 # Write out the last line, without a trailing newline
80 if prevline is not None:
81 write(prevline, lineEnd=stripped)
83 def encodestring(s, quotetabs=0):
84 from cStringIO import StringIO
85 infp = StringIO(s)
86 outfp = StringIO()
87 encode(infp, outfp, quotetabs)
88 return outfp.getvalue()
92 def decode(input, output):
93 """Read 'input', apply quoted-printable decoding, and write to 'output'.
95 'input' and 'output' are files with readline() and write() methods."""
96 new = ''
97 while 1:
98 line = input.readline()
99 if not line: break
100 i, n = 0, len(line)
101 if n > 0 and line[n-1] == '\n':
102 partial = 0; n = n-1
103 # Strip trailing whitespace
104 while n > 0 and line[n-1] in " \t\r":
105 n = n-1
106 else:
107 partial = 1
108 while i < n:
109 c = line[i]
110 if c != ESCAPE:
111 new = new + c; i = i+1
112 elif i+1 == n and not partial:
113 partial = 1; break
114 elif i+1 < n and line[i+1] == ESCAPE:
115 new = new + ESCAPE; i = i+2
116 elif i+2 < n and ishex(line[i+1]) and ishex(line[i+2]):
117 new = new + chr(unhex(line[i+1:i+3])); i = i+3
118 else: # Bad escape sequence -- leave it in
119 new = new + c; i = i+1
120 if not partial:
121 output.write(new + '\n')
122 new = ''
123 if new:
124 output.write(new)
126 def decodestring(s):
127 from cStringIO import StringIO
128 infp = StringIO(s)
129 outfp = StringIO()
130 decode(infp, outfp)
131 return outfp.getvalue()
135 # Other helper functions
136 def ishex(c):
137 """Return true if the character 'c' is a hexadecimal digit."""
138 return '0' <= c <= '9' or 'a' <= c <= 'f' or 'A' <= c <= 'F'
140 def unhex(s):
141 """Get the integer value of a hexadecimal number."""
142 bits = 0
143 for c in s:
144 if '0' <= c <= '9':
145 i = ord('0')
146 elif 'a' <= c <= 'f':
147 i = ord('a')-10
148 elif 'A' <= c <= 'F':
149 i = ord('A')-10
150 else:
151 break
152 bits = bits*16 + (ord(c) - i)
153 return bits
157 def main():
158 import sys
159 import getopt
160 try:
161 opts, args = getopt.getopt(sys.argv[1:], 'td')
162 except getopt.error, msg:
163 sys.stdout = sys.stderr
164 print msg
165 print "usage: quopri [-t | -d] [file] ..."
166 print "-t: quote tabs"
167 print "-d: decode; default encode"
168 sys.exit(2)
169 deco = 0
170 tabs = 0
171 for o, a in opts:
172 if o == '-t': tabs = 1
173 if o == '-d': deco = 1
174 if tabs and deco:
175 sys.stdout = sys.stderr
176 print "-t and -d are mutually exclusive"
177 sys.exit(2)
178 if not args: args = ['-']
179 sts = 0
180 for file in args:
181 if file == '-':
182 fp = sys.stdin
183 else:
184 try:
185 fp = open(file)
186 except IOError, msg:
187 sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
188 sts = 1
189 continue
190 if deco:
191 decode(fp, sys.stdout)
192 else:
193 encode(fp, sys.stdout, tabs)
194 if fp is not sys.stdin:
195 fp.close()
196 if sts:
197 sys.exit(sts)
201 if __name__ == '__main__':
202 main()