Merged release21-maint changes.
[python/dscho.git] / Lib / mimetools.py
blob7cf8ad672e023c9ecfc057c23bcdaad826070b11
1 """Various tools used by MIME-reading or MIME-writing programs."""
4 import os
5 import rfc822
6 import tempfile
8 __all__ = ["Message","choose_boundary","encode","decode","copyliteral",
9 "copybinary"]
11 class Message(rfc822.Message):
12 """A derived class of rfc822.Message that knows about MIME headers and
13 contains some hooks for decoding encoded and multipart messages."""
15 def __init__(self, fp, seekable = 1):
16 rfc822.Message.__init__(self, fp, seekable)
17 self.encodingheader = \
18 self.getheader('content-transfer-encoding')
19 self.typeheader = \
20 self.getheader('content-type')
21 self.parsetype()
22 self.parseplist()
24 def parsetype(self):
25 str = self.typeheader
26 if str is None:
27 str = 'text/plain'
28 if ';' in str:
29 i = str.index(';')
30 self.plisttext = str[i:]
31 str = str[:i]
32 else:
33 self.plisttext = ''
34 fields = str.split('/')
35 for i in range(len(fields)):
36 fields[i] = fields[i].strip().lower()
37 self.type = '/'.join(fields)
38 self.maintype = fields[0]
39 self.subtype = '/'.join(fields[1:])
41 def parseplist(self):
42 str = self.plisttext
43 self.plist = []
44 while str[:1] == ';':
45 str = str[1:]
46 if ';' in str:
47 # XXX Should parse quotes!
48 end = str.index(';')
49 else:
50 end = len(str)
51 f = str[:end]
52 if '=' in f:
53 i = f.index('=')
54 f = f[:i].strip().lower() + \
55 '=' + f[i+1:].strip()
56 self.plist.append(f.strip())
57 str = str[end:]
59 def getplist(self):
60 return self.plist
62 def getparam(self, name):
63 name = name.lower() + '='
64 n = len(name)
65 for p in self.plist:
66 if p[:n] == name:
67 return rfc822.unquote(p[n:])
68 return None
70 def getparamnames(self):
71 result = []
72 for p in self.plist:
73 i = p.find('=')
74 if i >= 0:
75 result.append(p[:i].lower())
76 return result
78 def getencoding(self):
79 if self.encodingheader is None:
80 return '7bit'
81 return self.encodingheader.lower()
83 def gettype(self):
84 return self.type
86 def getmaintype(self):
87 return self.maintype
89 def getsubtype(self):
90 return self.subtype
95 # Utility functions
96 # -----------------
99 _prefix = None
101 def choose_boundary():
102 """Return a random string usable as a multipart boundary.
103 The method used is so that it is *very* unlikely that the same
104 string of characters will every occur again in the Universe,
105 so the caller needn't check the data it is packing for the
106 occurrence of the boundary.
108 The boundary contains dots so you have to quote it in the header."""
110 global _prefix
111 import time
112 import random
113 if _prefix is None:
114 import socket
115 import os
116 hostid = socket.gethostbyname(socket.gethostname())
117 try:
118 uid = `os.getuid()`
119 except:
120 uid = '1'
121 try:
122 pid = `os.getpid()`
123 except:
124 pid = '1'
125 _prefix = hostid + '.' + uid + '.' + pid
126 timestamp = '%.3f' % time.time()
127 seed = `random.randint(0, 32767)`
128 return _prefix + '.' + timestamp + '.' + seed
131 # Subroutines for decoding some common content-transfer-types
133 def decode(input, output, encoding):
134 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
135 if encoding == 'base64':
136 import base64
137 return base64.decode(input, output)
138 if encoding == 'quoted-printable':
139 import quopri
140 return quopri.decode(input, output)
141 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
142 import uu
143 return uu.decode(input, output)
144 if encoding in ('7bit', '8bit'):
145 return output.write(input.read())
146 if decodetab.has_key(encoding):
147 pipethrough(input, decodetab[encoding], output)
148 else:
149 raise ValueError, \
150 'unknown Content-Transfer-Encoding: %s' % encoding
152 def encode(input, output, encoding):
153 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
154 if encoding == 'base64':
155 import base64
156 return base64.encode(input, output)
157 if encoding == 'quoted-printable':
158 import quopri
159 return quopri.encode(input, output, 0)
160 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
161 import uu
162 return uu.encode(input, output)
163 if encoding in ('7bit', '8bit'):
164 return output.write(input.read())
165 if encodetab.has_key(encoding):
166 pipethrough(input, encodetab[encoding], output)
167 else:
168 raise ValueError, \
169 'unknown Content-Transfer-Encoding: %s' % encoding
171 # The following is no longer used for standard encodings
173 # XXX This requires that uudecode and mmencode are in $PATH
175 uudecode_pipe = '''(
176 TEMP=/tmp/@uu.$$
177 sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
178 cat $TEMP
179 rm $TEMP
180 )'''
182 decodetab = {
183 'uuencode': uudecode_pipe,
184 'x-uuencode': uudecode_pipe,
185 'uue': uudecode_pipe,
186 'x-uue': uudecode_pipe,
187 'quoted-printable': 'mmencode -u -q',
188 'base64': 'mmencode -u -b',
191 encodetab = {
192 'x-uuencode': 'uuencode tempfile',
193 'uuencode': 'uuencode tempfile',
194 'x-uue': 'uuencode tempfile',
195 'uue': 'uuencode tempfile',
196 'quoted-printable': 'mmencode -q',
197 'base64': 'mmencode -b',
200 def pipeto(input, command):
201 pipe = os.popen(command, 'w')
202 copyliteral(input, pipe)
203 pipe.close()
205 def pipethrough(input, command, output):
206 tempname = tempfile.mktemp()
207 temp = open(tempname, 'w')
208 copyliteral(input, temp)
209 temp.close()
210 pipe = os.popen(command + ' <' + tempname, 'r')
211 copybinary(pipe, output)
212 pipe.close()
213 os.unlink(tempname)
215 def copyliteral(input, output):
216 while 1:
217 line = input.readline()
218 if not line: break
219 output.write(line)
221 def copybinary(input, output):
222 BUFSIZE = 8192
223 while 1:
224 line = input.read(BUFSIZE)
225 if not line: break
226 output.write(line)