blob: 5dd5396203ec65ce4735528565f6c90c78ce1b53 [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
Benjamin Petersona03722f2008-06-12 14:23:49 +00008from warnings import warnpy3k
9warnpy3k("in 3.x, mimetools has been removed in favor of the email package")
10
Skip Montanaro03d90142001-01-25 15:29:22 +000011__all__ = ["Message","choose_boundary","encode","decode","copyliteral",
12 "copybinary"]
Guido van Rossum01ca3361992-07-13 14:28:59 +000013
Guido van Rossum01ca3361992-07-13 14:28:59 +000014class Message(rfc822.Message):
Tim Peters07e99cb2001-01-14 23:47:14 +000015 """A derived class of rfc822.Message that knows about MIME headers and
16 contains some hooks for decoding encoded and multipart messages."""
Guido van Rossum01ca3361992-07-13 14:28:59 +000017
Tim Peters07e99cb2001-01-14 23:47:14 +000018 def __init__(self, fp, seekable = 1):
19 rfc822.Message.__init__(self, fp, seekable)
20 self.encodingheader = \
21 self.getheader('content-transfer-encoding')
22 self.typeheader = \
23 self.getheader('content-type')
24 self.parsetype()
25 self.parseplist()
Guido van Rossum01ca3361992-07-13 14:28:59 +000026
Tim Peters07e99cb2001-01-14 23:47:14 +000027 def parsetype(self):
28 str = self.typeheader
29 if str is None:
30 str = 'text/plain'
31 if ';' in str:
32 i = str.index(';')
33 self.plisttext = str[i:]
34 str = str[:i]
35 else:
36 self.plisttext = ''
37 fields = str.split('/')
38 for i in range(len(fields)):
39 fields[i] = fields[i].strip().lower()
40 self.type = '/'.join(fields)
41 self.maintype = fields[0]
42 self.subtype = '/'.join(fields[1:])
Guido van Rossum01ca3361992-07-13 14:28:59 +000043
Tim Peters07e99cb2001-01-14 23:47:14 +000044 def parseplist(self):
45 str = self.plisttext
46 self.plist = []
47 while str[:1] == ';':
48 str = str[1:]
49 if ';' in str:
50 # XXX Should parse quotes!
51 end = str.index(';')
52 else:
53 end = len(str)
54 f = str[:end]
55 if '=' in f:
56 i = f.index('=')
57 f = f[:i].strip().lower() + \
58 '=' + f[i+1:].strip()
59 self.plist.append(f.strip())
60 str = str[end:]
Guido van Rossum01ca3361992-07-13 14:28:59 +000061
Tim Peters07e99cb2001-01-14 23:47:14 +000062 def getplist(self):
63 return self.plist
Guido van Rossum01ca3361992-07-13 14:28:59 +000064
Tim Peters07e99cb2001-01-14 23:47:14 +000065 def getparam(self, name):
66 name = name.lower() + '='
67 n = len(name)
68 for p in self.plist:
69 if p[:n] == name:
70 return rfc822.unquote(p[n:])
71 return None
Guido van Rossum01ca3361992-07-13 14:28:59 +000072
Tim Peters07e99cb2001-01-14 23:47:14 +000073 def getparamnames(self):
74 result = []
75 for p in self.plist:
76 i = p.find('=')
77 if i >= 0:
78 result.append(p[:i].lower())
79 return result
Guido van Rossum4be63d11996-10-04 20:14:02 +000080
Tim Peters07e99cb2001-01-14 23:47:14 +000081 def getencoding(self):
82 if self.encodingheader is None:
83 return '7bit'
84 return self.encodingheader.lower()
Guido van Rossum01ca3361992-07-13 14:28:59 +000085
Tim Peters07e99cb2001-01-14 23:47:14 +000086 def gettype(self):
87 return self.type
Guido van Rossum01ca3361992-07-13 14:28:59 +000088
Tim Peters07e99cb2001-01-14 23:47:14 +000089 def getmaintype(self):
90 return self.maintype
Guido van Rossum01ca3361992-07-13 14:28:59 +000091
Tim Peters07e99cb2001-01-14 23:47:14 +000092 def getsubtype(self):
93 return self.subtype
Guido van Rossum01ca3361992-07-13 14:28:59 +000094
95
96
97
98# Utility functions
99# -----------------
100
Tim Peters080da282003-06-15 22:05:58 +0000101try:
102 import thread
103except ImportError:
104 import dummy_thread as thread
105_counter_lock = thread.allocate_lock()
106del thread
107
108_counter = 0
109def _get_next_counter():
110 global _counter
111 _counter_lock.acquire()
112 _counter += 1
113 result = _counter
114 _counter_lock.release()
115 return result
Guido van Rossum01ca3361992-07-13 14:28:59 +0000116
Guido van Rossum01ca3361992-07-13 14:28:59 +0000117_prefix = None
118
119def choose_boundary():
Tim Peters080da282003-06-15 22:05:58 +0000120 """Return a string usable as a multipart boundary.
121
122 The string chosen is unique within a single program run, and
123 incorporates the user id (if available), process id (if available),
124 and current time. So it's very unlikely the returned string appears
125 in message text, but there's no guarantee.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000126
Tim Peters07e99cb2001-01-14 23:47:14 +0000127 The boundary contains dots so you have to quote it in the header."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000128
Tim Peters07e99cb2001-01-14 23:47:14 +0000129 global _prefix
130 import time
Tim Peters07e99cb2001-01-14 23:47:14 +0000131 if _prefix is None:
132 import socket
Georg Brandldd2245f2006-03-31 17:18:06 +0000133 try:
134 hostid = socket.gethostbyname(socket.gethostname())
135 except socket.gaierror:
136 hostid = '127.0.0.1'
Tim Peters07e99cb2001-01-14 23:47:14 +0000137 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000138 uid = repr(os.getuid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000139 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000140 uid = '1'
141 try:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000142 pid = repr(os.getpid())
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000143 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000144 pid = '1'
145 _prefix = hostid + '.' + uid + '.' + pid
Tim Peters080da282003-06-15 22:05:58 +0000146 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000147
148
149# Subroutines for decoding some common content-transfer-types
150
Guido van Rossumb6775db1994-08-01 11:34:53 +0000151def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
153 if encoding == 'base64':
154 import base64
155 return base64.decode(input, output)
156 if encoding == 'quoted-printable':
157 import quopri
158 return quopri.decode(input, output)
159 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
160 import uu
161 return uu.decode(input, output)
162 if encoding in ('7bit', '8bit'):
163 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000164 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 pipethrough(input, decodetab[encoding], output)
166 else:
167 raise ValueError, \
168 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000169
170def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000171 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
172 if encoding == 'base64':
173 import base64
174 return base64.encode(input, output)
175 if encoding == 'quoted-printable':
176 import quopri
177 return quopri.encode(input, output, 0)
178 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
179 import uu
180 return uu.encode(input, output)
181 if encoding in ('7bit', '8bit'):
182 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000183 if encoding in encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000184 pipethrough(input, encodetab[encoding], output)
185 else:
186 raise ValueError, \
187 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000188
Guido van Rossume3cd1511997-07-11 16:33:26 +0000189# The following is no longer used for standard encodings
190
191# XXX This requires that uudecode and mmencode are in $PATH
192
Guido van Rossumb6775db1994-08-01 11:34:53 +0000193uudecode_pipe = '''(
194TEMP=/tmp/@uu.$$
195sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
196cat $TEMP
197rm $TEMP
198)'''
199
200decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000201 'uuencode': uudecode_pipe,
202 'x-uuencode': uudecode_pipe,
203 'uue': uudecode_pipe,
204 'x-uue': uudecode_pipe,
205 'quoted-printable': 'mmencode -u -q',
206 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000207}
208
209encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000210 'x-uuencode': 'uuencode tempfile',
211 'uuencode': 'uuencode tempfile',
212 'x-uue': 'uuencode tempfile',
213 'uue': 'uuencode tempfile',
214 'quoted-printable': 'mmencode -q',
215 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000216}
217
218def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000219 pipe = os.popen(command, 'w')
220 copyliteral(input, pipe)
221 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000222
223def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000224 (fd, tempname) = tempfile.mkstemp()
225 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000226 copyliteral(input, temp)
227 temp.close()
228 pipe = os.popen(command + ' <' + tempname, 'r')
229 copybinary(pipe, output)
230 pipe.close()
231 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000232
233def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000234 while 1:
235 line = input.readline()
236 if not line: break
237 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000238
239def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000240 BUFSIZE = 8192
241 while 1:
242 line = input.read(BUFSIZE)
243 if not line: break
244 output.write(line)