blob: 02963b55548de44f582f73364420129666dbce57 [file] [log] [blame]
Guido van Rossum54f22ed2000-02-04 15:10:34 +00001"""Various tools used by MIME-reading or MIME-writing programs."""
Guido van Rossum01ca3361992-07-13 14:28:59 +00002
3
Guido van Rossumb6775db1994-08-01 11:34:53 +00004import os
Guido van Rossum01ca3361992-07-13 14:28:59 +00005import rfc822
Guido van Rossumb6775db1994-08-01 11:34:53 +00006import tempfile
Guido van Rossum01ca3361992-07-13 14:28:59 +00007
Benjamin Petersona03722f2008-06-12 14:23:49 +00008from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +00009warnpy3k("in 3.x, mimetools has been removed in favor of the email package",
10 stacklevel=2)
Benjamin Petersona03722f2008-06-12 14:23:49 +000011
Skip Montanaro03d90142001-01-25 15:29:22 +000012__all__ = ["Message","choose_boundary","encode","decode","copyliteral",
13 "copybinary"]
Guido van Rossum01ca3361992-07-13 14:28:59 +000014
Guido van Rossum01ca3361992-07-13 14:28:59 +000015class Message(rfc822.Message):
Tim Peters07e99cb2001-01-14 23:47:14 +000016 """A derived class of rfc822.Message that knows about MIME headers and
17 contains some hooks for decoding encoded and multipart messages."""
Guido van Rossum01ca3361992-07-13 14:28:59 +000018
Tim Peters07e99cb2001-01-14 23:47:14 +000019 def __init__(self, fp, seekable = 1):
20 rfc822.Message.__init__(self, fp, seekable)
21 self.encodingheader = \
22 self.getheader('content-transfer-encoding')
23 self.typeheader = \
24 self.getheader('content-type')
25 self.parsetype()
26 self.parseplist()
Guido van Rossum01ca3361992-07-13 14:28:59 +000027
Tim Peters07e99cb2001-01-14 23:47:14 +000028 def parsetype(self):
29 str = self.typeheader
30 if str is None:
31 str = 'text/plain'
32 if ';' in str:
33 i = str.index(';')
34 self.plisttext = str[i:]
35 str = str[:i]
36 else:
37 self.plisttext = ''
38 fields = str.split('/')
39 for i in range(len(fields)):
40 fields[i] = fields[i].strip().lower()
41 self.type = '/'.join(fields)
42 self.maintype = fields[0]
43 self.subtype = '/'.join(fields[1:])
Guido van Rossum01ca3361992-07-13 14:28:59 +000044
Tim Peters07e99cb2001-01-14 23:47:14 +000045 def parseplist(self):
46 str = self.plisttext
47 self.plist = []
48 while str[:1] == ';':
49 str = str[1:]
50 if ';' in str:
51 # XXX Should parse quotes!
52 end = str.index(';')
53 else:
54 end = len(str)
55 f = str[:end]
56 if '=' in f:
57 i = f.index('=')
58 f = f[:i].strip().lower() + \
59 '=' + f[i+1:].strip()
60 self.plist.append(f.strip())
61 str = str[end:]
Guido van Rossum01ca3361992-07-13 14:28:59 +000062
Tim Peters07e99cb2001-01-14 23:47:14 +000063 def getplist(self):
64 return self.plist
Guido van Rossum01ca3361992-07-13 14:28:59 +000065
Tim Peters07e99cb2001-01-14 23:47:14 +000066 def getparam(self, name):
67 name = name.lower() + '='
68 n = len(name)
69 for p in self.plist:
70 if p[:n] == name:
71 return rfc822.unquote(p[n:])
72 return None
Guido van Rossum01ca3361992-07-13 14:28:59 +000073
Tim Peters07e99cb2001-01-14 23:47:14 +000074 def getparamnames(self):
75 result = []
76 for p in self.plist:
77 i = p.find('=')
78 if i >= 0:
79 result.append(p[:i].lower())
80 return result
Guido van Rossum4be63d11996-10-04 20:14:02 +000081
Tim Peters07e99cb2001-01-14 23:47:14 +000082 def getencoding(self):
83 if self.encodingheader is None:
84 return '7bit'
85 return self.encodingheader.lower()
Guido van Rossum01ca3361992-07-13 14:28:59 +000086
Tim Peters07e99cb2001-01-14 23:47:14 +000087 def gettype(self):
88 return self.type
Guido van Rossum01ca3361992-07-13 14:28:59 +000089
Tim Peters07e99cb2001-01-14 23:47:14 +000090 def getmaintype(self):
91 return self.maintype
Guido van Rossum01ca3361992-07-13 14:28:59 +000092
Tim Peters07e99cb2001-01-14 23:47:14 +000093 def getsubtype(self):
94 return self.subtype
Guido van Rossum01ca3361992-07-13 14:28:59 +000095
96
97
98
99# Utility functions
100# -----------------
101
Tim Peters080da282003-06-15 22:05:58 +0000102try:
103 import thread
104except ImportError:
105 import dummy_thread as thread
106_counter_lock = thread.allocate_lock()
107del thread
108
109_counter = 0
110def _get_next_counter():
111 global _counter
112 _counter_lock.acquire()
113 _counter += 1
114 result = _counter
115 _counter_lock.release()
116 return result
Guido van Rossum01ca3361992-07-13 14:28:59 +0000117
Guido van Rossum01ca3361992-07-13 14:28:59 +0000118_prefix = None
119
120def choose_boundary():
Tim Peters080da282003-06-15 22:05:58 +0000121 """Return a string usable as a multipart boundary.
122
123 The string chosen is unique within a single program run, and
124 incorporates the user id (if available), process id (if available),
125 and current time. So it's very unlikely the returned string appears
126 in message text, but there's no guarantee.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000127
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 The boundary contains dots so you have to quote it in the header."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000129
Tim Peters07e99cb2001-01-14 23:47:14 +0000130 global _prefix
131 import time
Tim Peters07e99cb2001-01-14 23:47:14 +0000132 if _prefix is None:
133 import socket
Georg Brandldd2245f2006-03-31 17:18:06 +0000134 try:
135 hostid = socket.gethostbyname(socket.gethostname())
136 except socket.gaierror:
137 hostid = '127.0.0.1'
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000139 uid = repr(os.getuid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000140 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000141 uid = '1'
142 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000143 pid = repr(os.getpid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000144 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 pid = '1'
146 _prefix = hostid + '.' + uid + '.' + pid
Tim Peters080da282003-06-15 22:05:58 +0000147 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000148
149
150# Subroutines for decoding some common content-transfer-types
151
Guido van Rossumb6775db1994-08-01 11:34:53 +0000152def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
154 if encoding == 'base64':
155 import base64
156 return base64.decode(input, output)
157 if encoding == 'quoted-printable':
158 import quopri
159 return quopri.decode(input, output)
160 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
161 import uu
162 return uu.decode(input, output)
163 if encoding in ('7bit', '8bit'):
164 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000165 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000166 pipethrough(input, decodetab[encoding], output)
167 else:
168 raise ValueError, \
169 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000170
171def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000172 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
173 if encoding == 'base64':
174 import base64
175 return base64.encode(input, output)
176 if encoding == 'quoted-printable':
177 import quopri
178 return quopri.encode(input, output, 0)
179 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
180 import uu
181 return uu.encode(input, output)
182 if encoding in ('7bit', '8bit'):
183 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000184 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000185 pipethrough(input, encodetab[encoding], output)
186 else:
187 raise ValueError, \
188 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000189
Guido van Rossume3cd1511997-07-11 16:33:26 +0000190# The following is no longer used for standard encodings
191
192# XXX This requires that uudecode and mmencode are in $PATH
193
Guido van Rossumb6775db1994-08-01 11:34:53 +0000194uudecode_pipe = '''(
195TEMP=/tmp/@uu.$$
196sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
197cat $TEMP
198rm $TEMP
199)'''
200
201decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000202 'uuencode': uudecode_pipe,
203 'x-uuencode': uudecode_pipe,
204 'uue': uudecode_pipe,
205 'x-uue': uudecode_pipe,
206 'quoted-printable': 'mmencode -u -q',
207 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000208}
209
210encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000211 'x-uuencode': 'uuencode tempfile',
212 'uuencode': 'uuencode tempfile',
213 'x-uue': 'uuencode tempfile',
214 'uue': 'uuencode tempfile',
215 'quoted-printable': 'mmencode -q',
216 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000217}
218
219def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000220 pipe = os.popen(command, 'w')
221 copyliteral(input, pipe)
222 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000223
224def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000225 (fd, tempname) = tempfile.mkstemp()
226 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000227 copyliteral(input, temp)
228 temp.close()
229 pipe = os.popen(command + ' <' + tempname, 'r')
230 copybinary(pipe, output)
231 pipe.close()
232 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000233
234def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000235 while 1:
236 line = input.readline()
237 if not line: break
238 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000239
240def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000241 BUFSIZE = 8192
242 while 1:
243 line = input.read(BUFSIZE)
244 if not line: break
245 output.write(line)