blob: 0b698ac679260b6c1140a175cc828776fea7b76c [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
Tim Peters07e99cb2001-01-14 23:47:14 +0000130 hostid = socket.gethostbyname(socket.gethostname())
131 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000132 uid = repr(os.getuid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000133 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 uid = '1'
135 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000136 pid = repr(os.getpid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000137 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000138 pid = '1'
139 _prefix = hostid + '.' + uid + '.' + pid
Tim Peters080da282003-06-15 22:05:58 +0000140 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000141
142
143# Subroutines for decoding some common content-transfer-types
144
Guido van Rossumb6775db1994-08-01 11:34:53 +0000145def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
147 if encoding == 'base64':
148 import base64
149 return base64.decode(input, output)
150 if encoding == 'quoted-printable':
151 import quopri
152 return quopri.decode(input, output)
153 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
154 import uu
155 return uu.decode(input, output)
156 if encoding in ('7bit', '8bit'):
157 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000158 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000159 pipethrough(input, decodetab[encoding], output)
160 else:
161 raise ValueError, \
162 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000163
164def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
166 if encoding == 'base64':
167 import base64
168 return base64.encode(input, output)
169 if encoding == 'quoted-printable':
170 import quopri
171 return quopri.encode(input, output, 0)
172 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
173 import uu
174 return uu.encode(input, output)
175 if encoding in ('7bit', '8bit'):
176 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000177 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000178 pipethrough(input, encodetab[encoding], output)
179 else:
180 raise ValueError, \
181 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000182
Guido van Rossume3cd1511997-07-11 16:33:26 +0000183# The following is no longer used for standard encodings
184
185# XXX This requires that uudecode and mmencode are in $PATH
186
Guido van Rossumb6775db1994-08-01 11:34:53 +0000187uudecode_pipe = '''(
188TEMP=/tmp/@uu.$$
189sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
190cat $TEMP
191rm $TEMP
192)'''
193
194decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000195 'uuencode': uudecode_pipe,
196 'x-uuencode': uudecode_pipe,
197 'uue': uudecode_pipe,
198 'x-uue': uudecode_pipe,
199 'quoted-printable': 'mmencode -u -q',
200 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000201}
202
203encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000204 'x-uuencode': 'uuencode tempfile',
205 'uuencode': 'uuencode tempfile',
206 'x-uue': 'uuencode tempfile',
207 'uue': 'uuencode tempfile',
208 'quoted-printable': 'mmencode -q',
209 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000210}
211
212def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000213 pipe = os.popen(command, 'w')
214 copyliteral(input, pipe)
215 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000216
217def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000218 (fd, tempname) = tempfile.mkstemp()
219 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000220 copyliteral(input, temp)
221 temp.close()
222 pipe = os.popen(command + ' <' + tempname, 'r')
223 copybinary(pipe, output)
224 pipe.close()
225 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000226
227def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000228 while 1:
229 line = input.readline()
230 if not line: break
231 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000232
233def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000234 BUFSIZE = 8192
235 while 1:
236 line = input.read(BUFSIZE)
237 if not line: break
238 output.write(line)