blob: 0553989cb4513d7bbc3bde6babaef3012023194d [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
Skip Montanaro03d90142001-01-25 15:29:22 +00008__all__ = ["Message","choose_boundary","encode","decode","copyliteral",
9 "copybinary"]
Guido van Rossum01ca3361992-07-13 14:28:59 +000010
Guido van Rossum01ca3361992-07-13 14:28:59 +000011class Message(rfc822.Message):
Tim Peters07e99cb2001-01-14 23:47:14 +000012 """A derived class of rfc822.Message that knows about MIME headers and
13 contains some hooks for decoding encoded and multipart messages."""
Guido van Rossum01ca3361992-07-13 14:28:59 +000014
Tim Peters07e99cb2001-01-14 23:47:14 +000015 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()
Guido van Rossum01ca3361992-07-13 14:28:59 +000023
Tim Peters07e99cb2001-01-14 23:47:14 +000024 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:])
Guido van Rossum01ca3361992-07-13 14:28:59 +000040
Tim Peters07e99cb2001-01-14 23:47:14 +000041 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:]
Guido van Rossum01ca3361992-07-13 14:28:59 +000058
Tim Peters07e99cb2001-01-14 23:47:14 +000059 def getplist(self):
60 return self.plist
Guido van Rossum01ca3361992-07-13 14:28:59 +000061
Tim Peters07e99cb2001-01-14 23:47:14 +000062 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
Guido van Rossum01ca3361992-07-13 14:28:59 +000069
Tim Peters07e99cb2001-01-14 23:47:14 +000070 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
Guido van Rossum4be63d11996-10-04 20:14:02 +000077
Tim Peters07e99cb2001-01-14 23:47:14 +000078 def getencoding(self):
79 if self.encodingheader is None:
80 return '7bit'
81 return self.encodingheader.lower()
Guido van Rossum01ca3361992-07-13 14:28:59 +000082
Tim Peters07e99cb2001-01-14 23:47:14 +000083 def gettype(self):
84 return self.type
Guido van Rossum01ca3361992-07-13 14:28:59 +000085
Tim Peters07e99cb2001-01-14 23:47:14 +000086 def getmaintype(self):
87 return self.maintype
Guido van Rossum01ca3361992-07-13 14:28:59 +000088
Tim Peters07e99cb2001-01-14 23:47:14 +000089 def getsubtype(self):
90 return self.subtype
Guido van Rossum01ca3361992-07-13 14:28:59 +000091
92
93
94
95# Utility functions
96# -----------------
97
Tim Peters080da282003-06-15 22:05:58 +000098try:
99 import thread
100except ImportError:
101 import dummy_thread as thread
102_counter_lock = thread.allocate_lock()
103del thread
104
105_counter = 0
106def _get_next_counter():
107 global _counter
108 _counter_lock.acquire()
109 _counter += 1
110 result = _counter
111 _counter_lock.release()
112 return result
Guido van Rossum01ca3361992-07-13 14:28:59 +0000113
Guido van Rossum01ca3361992-07-13 14:28:59 +0000114_prefix = None
115
116def choose_boundary():
Tim Peters080da282003-06-15 22:05:58 +0000117 """Return a string usable as a multipart boundary.
118
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.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000123
Tim Peters07e99cb2001-01-14 23:47:14 +0000124 The boundary contains dots so you have to quote it in the header."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000125
Tim Peters07e99cb2001-01-14 23:47:14 +0000126 global _prefix
127 import time
Tim Peters07e99cb2001-01-14 23:47:14 +0000128 if _prefix is None:
129 import socket
Thomas Wouters49fd7fa2006-04-21 10:40:58 +0000130 try:
131 hostid = socket.gethostbyname(socket.gethostname())
132 except socket.gaierror:
133 hostid = '127.0.0.1'
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000135 uid = repr(os.getuid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000136 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 uid = '1'
138 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000139 pid = repr(os.getpid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000140 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000141 pid = '1'
142 _prefix = hostid + '.' + uid + '.' + pid
Tim Peters080da282003-06-15 22:05:58 +0000143 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000144
145
146# Subroutines for decoding some common content-transfer-types
Guido van Rossum34d19282007-08-09 01:03:29 +0000147# Input and output must be files opened in binary mode
Guido van Rossumb6775db1994-08-01 11:34:53 +0000148
Guido van Rossumb6775db1994-08-01 11:34:53 +0000149def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000150 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
151 if encoding == 'base64':
152 import base64
153 return base64.decode(input, output)
154 if encoding == 'quoted-printable':
155 import quopri
156 return quopri.decode(input, output)
157 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
158 import uu
159 return uu.decode(input, output)
160 if encoding in ('7bit', '8bit'):
Guido van Rossum39478e82007-08-27 17:23:59 +0000161 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000162 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000163 pipethrough(input, decodetab[encoding], output)
164 else:
165 raise ValueError, \
166 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000167
168def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000169 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
170 if encoding == 'base64':
171 import base64
172 return base64.encode(input, output)
173 if encoding == 'quoted-printable':
174 import quopri
175 return quopri.encode(input, output, 0)
176 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
177 import uu
178 return uu.encode(input, output)
179 if encoding in ('7bit', '8bit'):
180 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000181 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 pipethrough(input, encodetab[encoding], output)
183 else:
184 raise ValueError, \
185 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000186
Guido van Rossume3cd1511997-07-11 16:33:26 +0000187# The following is no longer used for standard encodings
188
189# XXX This requires that uudecode and mmencode are in $PATH
190
Guido van Rossumb6775db1994-08-01 11:34:53 +0000191uudecode_pipe = '''(
192TEMP=/tmp/@uu.$$
193sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
194cat $TEMP
195rm $TEMP
196)'''
197
198decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000199 'uuencode': uudecode_pipe,
200 'x-uuencode': uudecode_pipe,
201 'uue': uudecode_pipe,
202 'x-uue': uudecode_pipe,
203 'quoted-printable': 'mmencode -u -q',
204 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000205}
206
207encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 'x-uuencode': 'uuencode tempfile',
209 'uuencode': 'uuencode tempfile',
210 'x-uue': 'uuencode tempfile',
211 'uue': 'uuencode tempfile',
212 'quoted-printable': 'mmencode -q',
213 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000214}
215
216def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000217 pipe = os.popen(command, 'w')
218 copyliteral(input, pipe)
219 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000220
221def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000222 (fd, tempname) = tempfile.mkstemp()
223 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000224 copyliteral(input, temp)
225 temp.close()
226 pipe = os.popen(command + ' <' + tempname, 'r')
227 copybinary(pipe, output)
228 pipe.close()
229 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000230
231def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000232 while 1:
233 line = input.readline()
234 if not line: break
235 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000236
237def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000238 BUFSIZE = 8192
239 while 1:
240 line = input.read(BUFSIZE)
241 if not line: break
242 output.write(line)