blob: 097eda4a75bd2fcbd4fef71a6c6e223594a6094c [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 Rossumb6775db1994-08-01 11:34:53 +00005import tempfile
Brett Cannonbf3157b2008-08-14 05:00:03 +00006from test.test_support import catch_warning
7from warnings import filterwarnings
8with catch_warning(record=False):
9 filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
10 import rfc822
Guido van Rossum01ca3361992-07-13 14:28:59 +000011
Benjamin Petersona03722f2008-06-12 14:23:49 +000012from warnings import warnpy3k
Benjamin Petersona6864e02008-07-14 17:42:17 +000013warnpy3k("in 3.x, mimetools has been removed in favor of the email package",
14 stacklevel=2)
Benjamin Petersona03722f2008-06-12 14:23:49 +000015
Skip Montanaro03d90142001-01-25 15:29:22 +000016__all__ = ["Message","choose_boundary","encode","decode","copyliteral",
17 "copybinary"]
Guido van Rossum01ca3361992-07-13 14:28:59 +000018
Guido van Rossum01ca3361992-07-13 14:28:59 +000019class Message(rfc822.Message):
Tim Peters07e99cb2001-01-14 23:47:14 +000020 """A derived class of rfc822.Message that knows about MIME headers and
21 contains some hooks for decoding encoded and multipart messages."""
Guido van Rossum01ca3361992-07-13 14:28:59 +000022
Tim Peters07e99cb2001-01-14 23:47:14 +000023 def __init__(self, fp, seekable = 1):
24 rfc822.Message.__init__(self, fp, seekable)
25 self.encodingheader = \
26 self.getheader('content-transfer-encoding')
27 self.typeheader = \
28 self.getheader('content-type')
29 self.parsetype()
30 self.parseplist()
Guido van Rossum01ca3361992-07-13 14:28:59 +000031
Tim Peters07e99cb2001-01-14 23:47:14 +000032 def parsetype(self):
33 str = self.typeheader
34 if str is None:
35 str = 'text/plain'
36 if ';' in str:
37 i = str.index(';')
38 self.plisttext = str[i:]
39 str = str[:i]
40 else:
41 self.plisttext = ''
42 fields = str.split('/')
43 for i in range(len(fields)):
44 fields[i] = fields[i].strip().lower()
45 self.type = '/'.join(fields)
46 self.maintype = fields[0]
47 self.subtype = '/'.join(fields[1:])
Guido van Rossum01ca3361992-07-13 14:28:59 +000048
Tim Peters07e99cb2001-01-14 23:47:14 +000049 def parseplist(self):
50 str = self.plisttext
51 self.plist = []
52 while str[:1] == ';':
53 str = str[1:]
54 if ';' in str:
55 # XXX Should parse quotes!
56 end = str.index(';')
57 else:
58 end = len(str)
59 f = str[:end]
60 if '=' in f:
61 i = f.index('=')
62 f = f[:i].strip().lower() + \
63 '=' + f[i+1:].strip()
64 self.plist.append(f.strip())
65 str = str[end:]
Guido van Rossum01ca3361992-07-13 14:28:59 +000066
Tim Peters07e99cb2001-01-14 23:47:14 +000067 def getplist(self):
68 return self.plist
Guido van Rossum01ca3361992-07-13 14:28:59 +000069
Tim Peters07e99cb2001-01-14 23:47:14 +000070 def getparam(self, name):
71 name = name.lower() + '='
72 n = len(name)
73 for p in self.plist:
74 if p[:n] == name:
75 return rfc822.unquote(p[n:])
76 return None
Guido van Rossum01ca3361992-07-13 14:28:59 +000077
Tim Peters07e99cb2001-01-14 23:47:14 +000078 def getparamnames(self):
79 result = []
80 for p in self.plist:
81 i = p.find('=')
82 if i >= 0:
83 result.append(p[:i].lower())
84 return result
Guido van Rossum4be63d11996-10-04 20:14:02 +000085
Tim Peters07e99cb2001-01-14 23:47:14 +000086 def getencoding(self):
87 if self.encodingheader is None:
88 return '7bit'
89 return self.encodingheader.lower()
Guido van Rossum01ca3361992-07-13 14:28:59 +000090
Tim Peters07e99cb2001-01-14 23:47:14 +000091 def gettype(self):
92 return self.type
Guido van Rossum01ca3361992-07-13 14:28:59 +000093
Tim Peters07e99cb2001-01-14 23:47:14 +000094 def getmaintype(self):
95 return self.maintype
Guido van Rossum01ca3361992-07-13 14:28:59 +000096
Tim Peters07e99cb2001-01-14 23:47:14 +000097 def getsubtype(self):
98 return self.subtype
Guido van Rossum01ca3361992-07-13 14:28:59 +000099
100
101
102
103# Utility functions
104# -----------------
105
Tim Peters080da282003-06-15 22:05:58 +0000106try:
107 import thread
108except ImportError:
109 import dummy_thread as thread
110_counter_lock = thread.allocate_lock()
111del thread
112
113_counter = 0
114def _get_next_counter():
115 global _counter
116 _counter_lock.acquire()
117 _counter += 1
118 result = _counter
119 _counter_lock.release()
120 return result
Guido van Rossum01ca3361992-07-13 14:28:59 +0000121
Guido van Rossum01ca3361992-07-13 14:28:59 +0000122_prefix = None
123
124def choose_boundary():
Tim Peters080da282003-06-15 22:05:58 +0000125 """Return a string usable as a multipart boundary.
126
127 The string chosen is unique within a single program run, and
128 incorporates the user id (if available), process id (if available),
129 and current time. So it's very unlikely the returned string appears
130 in message text, but there's no guarantee.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000131
Tim Peters07e99cb2001-01-14 23:47:14 +0000132 The boundary contains dots so you have to quote it in the header."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000133
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 global _prefix
135 import time
Tim Peters07e99cb2001-01-14 23:47:14 +0000136 if _prefix is None:
137 import socket
Georg Brandldd2245f2006-03-31 17:18:06 +0000138 try:
139 hostid = socket.gethostbyname(socket.gethostname())
140 except socket.gaierror:
141 hostid = '127.0.0.1'
Tim Peters07e99cb2001-01-14 23:47:14 +0000142 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000143 uid = repr(os.getuid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000144 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000145 uid = '1'
146 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000147 pid = repr(os.getpid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000148 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 pid = '1'
150 _prefix = hostid + '.' + uid + '.' + pid
Tim Peters080da282003-06-15 22:05:58 +0000151 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000152
153
154# Subroutines for decoding some common content-transfer-types
155
Guido van Rossumb6775db1994-08-01 11:34:53 +0000156def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000157 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
158 if encoding == 'base64':
159 import base64
160 return base64.decode(input, output)
161 if encoding == 'quoted-printable':
162 import quopri
163 return quopri.decode(input, output)
164 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
165 import uu
166 return uu.decode(input, output)
167 if encoding in ('7bit', '8bit'):
168 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000169 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000170 pipethrough(input, decodetab[encoding], output)
171 else:
172 raise ValueError, \
173 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000174
175def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000176 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
177 if encoding == 'base64':
178 import base64
179 return base64.encode(input, output)
180 if encoding == 'quoted-printable':
181 import quopri
182 return quopri.encode(input, output, 0)
183 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
184 import uu
185 return uu.encode(input, output)
186 if encoding in ('7bit', '8bit'):
187 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000188 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000189 pipethrough(input, encodetab[encoding], output)
190 else:
191 raise ValueError, \
192 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000193
Guido van Rossume3cd1511997-07-11 16:33:26 +0000194# The following is no longer used for standard encodings
195
196# XXX This requires that uudecode and mmencode are in $PATH
197
Guido van Rossumb6775db1994-08-01 11:34:53 +0000198uudecode_pipe = '''(
199TEMP=/tmp/@uu.$$
200sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
201cat $TEMP
202rm $TEMP
203)'''
204
205decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 'uuencode': uudecode_pipe,
207 'x-uuencode': uudecode_pipe,
208 'uue': uudecode_pipe,
209 'x-uue': uudecode_pipe,
210 'quoted-printable': 'mmencode -u -q',
211 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000212}
213
214encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 'x-uuencode': 'uuencode tempfile',
216 'uuencode': 'uuencode tempfile',
217 'x-uue': 'uuencode tempfile',
218 'uue': 'uuencode tempfile',
219 'quoted-printable': 'mmencode -q',
220 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000221}
222
223def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 pipe = os.popen(command, 'w')
225 copyliteral(input, pipe)
226 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000227
228def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000229 (fd, tempname) = tempfile.mkstemp()
230 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000231 copyliteral(input, temp)
232 temp.close()
233 pipe = os.popen(command + ' <' + tempname, 'r')
234 copybinary(pipe, output)
235 pipe.close()
236 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000237
238def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000239 while 1:
240 line = input.readline()
241 if not line: break
242 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000243
244def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000245 BUFSIZE = 8192
246 while 1:
247 line = input.read(BUFSIZE)
248 if not line: break
249 output.write(line)