blob: 71ca8f8593f2b64d08ddeb02a38dee1fef827f1c [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
Brett Cannon1eaf0742008-09-02 01:25:16 +00005import sys
Guido van Rossumb6775db1994-08-01 11:34:53 +00006import tempfile
Brett Cannon1eaf0742008-09-02 01:25:16 +00007from warnings import filterwarnings, catch_warnings
Brett Cannon672237d2008-09-09 00:49:16 +00008with catch_warnings():
Brett Cannon1eaf0742008-09-02 01:25:16 +00009 if sys.py3kwarning:
10 filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
Brett Cannonbf3157b2008-08-14 05:00:03 +000011 import rfc822
Guido van Rossum01ca3361992-07-13 14:28:59 +000012
Benjamin Petersona03722f2008-06-12 14:23:49 +000013from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +000014warnpy3k("in 3.x, mimetools has been removed in favor of the email package",
15 stacklevel=2)
Benjamin Petersona03722f2008-06-12 14:23:49 +000016
Skip Montanaro03d90142001-01-25 15:29:22 +000017__all__ = ["Message","choose_boundary","encode","decode","copyliteral",
18 "copybinary"]
Guido van Rossum01ca3361992-07-13 14:28:59 +000019
Guido van Rossum01ca3361992-07-13 14:28:59 +000020class Message(rfc822.Message):
Tim Peters07e99cb2001-01-14 23:47:14 +000021 """A derived class of rfc822.Message that knows about MIME headers and
22 contains some hooks for decoding encoded and multipart messages."""
Guido van Rossum01ca3361992-07-13 14:28:59 +000023
Tim Peters07e99cb2001-01-14 23:47:14 +000024 def __init__(self, fp, seekable = 1):
25 rfc822.Message.__init__(self, fp, seekable)
26 self.encodingheader = \
27 self.getheader('content-transfer-encoding')
28 self.typeheader = \
29 self.getheader('content-type')
30 self.parsetype()
31 self.parseplist()
Guido van Rossum01ca3361992-07-13 14:28:59 +000032
Tim Peters07e99cb2001-01-14 23:47:14 +000033 def parsetype(self):
34 str = self.typeheader
35 if str is None:
36 str = 'text/plain'
37 if ';' in str:
38 i = str.index(';')
39 self.plisttext = str[i:]
40 str = str[:i]
41 else:
42 self.plisttext = ''
43 fields = str.split('/')
44 for i in range(len(fields)):
45 fields[i] = fields[i].strip().lower()
46 self.type = '/'.join(fields)
47 self.maintype = fields[0]
48 self.subtype = '/'.join(fields[1:])
Guido van Rossum01ca3361992-07-13 14:28:59 +000049
Tim Peters07e99cb2001-01-14 23:47:14 +000050 def parseplist(self):
51 str = self.plisttext
52 self.plist = []
53 while str[:1] == ';':
54 str = str[1:]
55 if ';' in str:
56 # XXX Should parse quotes!
57 end = str.index(';')
58 else:
59 end = len(str)
60 f = str[:end]
61 if '=' in f:
62 i = f.index('=')
63 f = f[:i].strip().lower() + \
64 '=' + f[i+1:].strip()
65 self.plist.append(f.strip())
66 str = str[end:]
Guido van Rossum01ca3361992-07-13 14:28:59 +000067
Tim Peters07e99cb2001-01-14 23:47:14 +000068 def getplist(self):
69 return self.plist
Guido van Rossum01ca3361992-07-13 14:28:59 +000070
Tim Peters07e99cb2001-01-14 23:47:14 +000071 def getparam(self, name):
72 name = name.lower() + '='
73 n = len(name)
74 for p in self.plist:
75 if p[:n] == name:
76 return rfc822.unquote(p[n:])
77 return None
Guido van Rossum01ca3361992-07-13 14:28:59 +000078
Tim Peters07e99cb2001-01-14 23:47:14 +000079 def getparamnames(self):
80 result = []
81 for p in self.plist:
82 i = p.find('=')
83 if i >= 0:
84 result.append(p[:i].lower())
85 return result
Guido van Rossum4be63d11996-10-04 20:14:02 +000086
Tim Peters07e99cb2001-01-14 23:47:14 +000087 def getencoding(self):
88 if self.encodingheader is None:
89 return '7bit'
90 return self.encodingheader.lower()
Guido van Rossum01ca3361992-07-13 14:28:59 +000091
Tim Peters07e99cb2001-01-14 23:47:14 +000092 def gettype(self):
93 return self.type
Guido van Rossum01ca3361992-07-13 14:28:59 +000094
Tim Peters07e99cb2001-01-14 23:47:14 +000095 def getmaintype(self):
96 return self.maintype
Guido van Rossum01ca3361992-07-13 14:28:59 +000097
Tim Peters07e99cb2001-01-14 23:47:14 +000098 def getsubtype(self):
99 return self.subtype
Guido van Rossum01ca3361992-07-13 14:28:59 +0000100
101
102
103
104# Utility functions
105# -----------------
106
Tim Peters080da282003-06-15 22:05:58 +0000107try:
108 import thread
109except ImportError:
110 import dummy_thread as thread
111_counter_lock = thread.allocate_lock()
112del thread
113
114_counter = 0
115def _get_next_counter():
116 global _counter
117 _counter_lock.acquire()
118 _counter += 1
119 result = _counter
120 _counter_lock.release()
121 return result
Guido van Rossum01ca3361992-07-13 14:28:59 +0000122
Guido van Rossum01ca3361992-07-13 14:28:59 +0000123_prefix = None
124
125def choose_boundary():
Tim Peters080da282003-06-15 22:05:58 +0000126 """Return a string usable as a multipart boundary.
127
128 The string chosen is unique within a single program run, and
129 incorporates the user id (if available), process id (if available),
130 and current time. So it's very unlikely the returned string appears
131 in message text, but there's no guarantee.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000132
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 The boundary contains dots so you have to quote it in the header."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000134
Tim Peters07e99cb2001-01-14 23:47:14 +0000135 global _prefix
136 import time
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 if _prefix is None:
138 import socket
Georg Brandldd2245f2006-03-31 17:18:06 +0000139 try:
140 hostid = socket.gethostbyname(socket.gethostname())
141 except socket.gaierror:
142 hostid = '127.0.0.1'
Tim Peters07e99cb2001-01-14 23:47:14 +0000143 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000144 uid = repr(os.getuid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000145 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 uid = '1'
147 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000148 pid = repr(os.getpid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000149 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000150 pid = '1'
151 _prefix = hostid + '.' + uid + '.' + pid
Tim Peters080da282003-06-15 22:05:58 +0000152 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000153
154
155# Subroutines for decoding some common content-transfer-types
156
Guido van Rossumb6775db1994-08-01 11:34:53 +0000157def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000158 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
159 if encoding == 'base64':
160 import base64
161 return base64.decode(input, output)
162 if encoding == 'quoted-printable':
163 import quopri
164 return quopri.decode(input, output)
165 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
166 import uu
167 return uu.decode(input, output)
168 if encoding in ('7bit', '8bit'):
169 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000170 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 pipethrough(input, decodetab[encoding], output)
172 else:
173 raise ValueError, \
174 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000175
176def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000177 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
178 if encoding == 'base64':
179 import base64
180 return base64.encode(input, output)
181 if encoding == 'quoted-printable':
182 import quopri
183 return quopri.encode(input, output, 0)
184 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
185 import uu
186 return uu.encode(input, output)
187 if encoding in ('7bit', '8bit'):
188 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000189 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000190 pipethrough(input, encodetab[encoding], output)
191 else:
192 raise ValueError, \
193 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000194
Guido van Rossume3cd1511997-07-11 16:33:26 +0000195# The following is no longer used for standard encodings
196
197# XXX This requires that uudecode and mmencode are in $PATH
198
Guido van Rossumb6775db1994-08-01 11:34:53 +0000199uudecode_pipe = '''(
200TEMP=/tmp/@uu.$$
201sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
202cat $TEMP
203rm $TEMP
204)'''
205
206decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 'uuencode': uudecode_pipe,
208 'x-uuencode': uudecode_pipe,
209 'uue': uudecode_pipe,
210 'x-uue': uudecode_pipe,
211 'quoted-printable': 'mmencode -u -q',
212 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000213}
214
215encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 'x-uuencode': 'uuencode tempfile',
217 'uuencode': 'uuencode tempfile',
218 'x-uue': 'uuencode tempfile',
219 'uue': 'uuencode tempfile',
220 'quoted-printable': 'mmencode -q',
221 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000222}
223
224def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000225 pipe = os.popen(command, 'w')
226 copyliteral(input, pipe)
227 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000228
229def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000230 (fd, tempname) = tempfile.mkstemp()
231 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000232 copyliteral(input, temp)
233 temp.close()
234 pipe = os.popen(command + ' <' + tempname, 'r')
235 copybinary(pipe, output)
236 pipe.close()
237 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000238
239def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000240 while 1:
241 line = input.readline()
242 if not line: break
243 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000244
245def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000246 BUFSIZE = 8192
247 while 1:
248 line = input.read(BUFSIZE)
249 if not line: break
250 output.write(line)