blob: 8c1cc199031cf0e5a86520e9549b8dc88e0f1179 [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
Georg Brandldd2245f2006-03-31 17:18:06 +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
147
Guido van Rossumb6775db1994-08-01 11:34:53 +0000148def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000149 """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())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000161 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000162 pipethrough(input, decodetab[encoding], output)
163 else:
164 raise ValueError, \
165 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000166
167def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000168 """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())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000180 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000181 pipethrough(input, encodetab[encoding], output)
182 else:
183 raise ValueError, \
184 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000185
Guido van Rossume3cd1511997-07-11 16:33:26 +0000186# The following is no longer used for standard encodings
187
188# XXX This requires that uudecode and mmencode are in $PATH
189
Guido van Rossumb6775db1994-08-01 11:34:53 +0000190uudecode_pipe = '''(
191TEMP=/tmp/@uu.$$
192sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
193cat $TEMP
194rm $TEMP
195)'''
196
197decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000198 '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',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000204}
205
206encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 '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',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000213}
214
215def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 pipe = os.popen(command, 'w')
217 copyliteral(input, pipe)
218 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000219
220def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000221 (fd, tempname) = tempfile.mkstemp()
222 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000223 copyliteral(input, temp)
224 temp.close()
225 pipe = os.popen(command + ' <' + tempname, 'r')
226 copybinary(pipe, output)
227 pipe.close()
228 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000229
230def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000231 while 1:
232 line = input.readline()
233 if not line: break
234 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000235
236def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000237 BUFSIZE = 8192
238 while 1:
239 line = input.read(BUFSIZE)
240 if not line: break
241 output.write(line)