Patch by Jeremy Katz (SF #1609407)
[python.git] / Lib / mimetools.py
blob8c1cc199031cf0e5a86520e9549b8dc88e0f1179
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 # -----------------
98 try:
99 import thread
100 except ImportError:
101 import dummy_thread as thread
102 _counter_lock = thread.allocate_lock()
103 del thread
105 _counter = 0
106 def _get_next_counter():
107 global _counter
108 _counter_lock.acquire()
109 _counter += 1
110 result = _counter
111 _counter_lock.release()
112 return result
114 _prefix = None
116 def choose_boundary():
117 """Return a string usable as a multipart boundary.
119 The string chosen is unique within a single program run, and
120 incorporates the user id (if available), process id (if available),
121 and current time. So it's very unlikely the returned string appears
122 in message text, but there's no guarantee.
124 The boundary contains dots so you have to quote it in the header."""
126 global _prefix
127 import time
128 if _prefix is None:
129 import socket
130 try:
131 hostid = socket.gethostbyname(socket.gethostname())
132 except socket.gaierror:
133 hostid = '127.0.0.1'
134 try:
135 uid = repr(os.getuid())
136 except AttributeError:
137 uid = '1'
138 try:
139 pid = repr(os.getpid())
140 except AttributeError:
141 pid = '1'
142 _prefix = hostid + '.' + uid + '.' + pid
143 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
146 # Subroutines for decoding some common content-transfer-types
148 def decode(input, output, encoding):
149 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
150 if encoding == 'base64':
151 import base64
152 return base64.decode(input, output)
153 if encoding == 'quoted-printable':
154 import quopri
155 return quopri.decode(input, output)
156 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
157 import uu
158 return uu.decode(input, output)
159 if encoding in ('7bit', '8bit'):
160 return output.write(input.read())
161 if encoding in decodetab:
162 pipethrough(input, decodetab[encoding], output)
163 else:
164 raise ValueError, \
165 'unknown Content-Transfer-Encoding: %s' % encoding
167 def encode(input, output, encoding):
168 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
169 if encoding == 'base64':
170 import base64
171 return base64.encode(input, output)
172 if encoding == 'quoted-printable':
173 import quopri
174 return quopri.encode(input, output, 0)
175 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
176 import uu
177 return uu.encode(input, output)
178 if encoding in ('7bit', '8bit'):
179 return output.write(input.read())
180 if encoding in encodetab:
181 pipethrough(input, encodetab[encoding], output)
182 else:
183 raise ValueError, \
184 'unknown Content-Transfer-Encoding: %s' % encoding
186 # The following is no longer used for standard encodings
188 # XXX This requires that uudecode and mmencode are in $PATH
190 uudecode_pipe = '''(
191 TEMP=/tmp/@uu.$$
192 sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
193 cat $TEMP
194 rm $TEMP
195 )'''
197 decodetab = {
198 'uuencode': uudecode_pipe,
199 'x-uuencode': uudecode_pipe,
200 'uue': uudecode_pipe,
201 'x-uue': uudecode_pipe,
202 'quoted-printable': 'mmencode -u -q',
203 'base64': 'mmencode -u -b',
206 encodetab = {
207 'x-uuencode': 'uuencode tempfile',
208 'uuencode': 'uuencode tempfile',
209 'x-uue': 'uuencode tempfile',
210 'uue': 'uuencode tempfile',
211 'quoted-printable': 'mmencode -q',
212 'base64': 'mmencode -b',
215 def pipeto(input, command):
216 pipe = os.popen(command, 'w')
217 copyliteral(input, pipe)
218 pipe.close()
220 def pipethrough(input, command, output):
221 (fd, tempname) = tempfile.mkstemp()
222 temp = os.fdopen(fd, 'w')
223 copyliteral(input, temp)
224 temp.close()
225 pipe = os.popen(command + ' <' + tempname, 'r')
226 copybinary(pipe, output)
227 pipe.close()
228 os.unlink(tempname)
230 def copyliteral(input, output):
231 while 1:
232 line = input.readline()
233 if not line: break
234 output.write(line)
236 def copybinary(input, output):
237 BUFSIZE = 8192
238 while 1:
239 line = input.read(BUFSIZE)
240 if not line: break
241 output.write(line)