blob: eb0ab713109cf9bc4b0c3f081aec00ca5e636cbd [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
98
Guido van Rossum01ca3361992-07-13 14:28:59 +000099_prefix = None
100
101def choose_boundary():
Tim Peters07e99cb2001-01-14 23:47:14 +0000102 """Return a random string usable as a multipart boundary.
103 The method used is so that it is *very* unlikely that the same
104 string of characters will every occur again in the Universe,
105 so the caller needn't check the data it is packing for the
106 occurrence of the boundary.
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000107
Tim Peters07e99cb2001-01-14 23:47:14 +0000108 The boundary contains dots so you have to quote it in the header."""
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000109
Tim Peters07e99cb2001-01-14 23:47:14 +0000110 global _prefix
111 import time
112 import random
113 if _prefix is None:
114 import socket
115 import os
116 hostid = socket.gethostbyname(socket.gethostname())
117 try:
118 uid = `os.getuid()`
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000119 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000120 uid = '1'
121 try:
122 pid = `os.getpid()`
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000123 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000124 pid = '1'
125 _prefix = hostid + '.' + uid + '.' + pid
126 timestamp = '%.3f' % time.time()
127 seed = `random.randint(0, 32767)`
128 return _prefix + '.' + timestamp + '.' + seed
Guido van Rossumb6775db1994-08-01 11:34:53 +0000129
130
131# Subroutines for decoding some common content-transfer-types
132
Guido van Rossumb6775db1994-08-01 11:34:53 +0000133def decode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000134 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
135 if encoding == 'base64':
136 import base64
137 return base64.decode(input, output)
138 if encoding == 'quoted-printable':
139 import quopri
140 return quopri.decode(input, output)
141 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
142 import uu
143 return uu.decode(input, output)
144 if encoding in ('7bit', '8bit'):
145 return output.write(input.read())
146 if decodetab.has_key(encoding):
147 pipethrough(input, decodetab[encoding], output)
148 else:
149 raise ValueError, \
150 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000151
152def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000153 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
154 if encoding == 'base64':
155 import base64
156 return base64.encode(input, output)
157 if encoding == 'quoted-printable':
158 import quopri
159 return quopri.encode(input, output, 0)
160 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
161 import uu
162 return uu.encode(input, output)
163 if encoding in ('7bit', '8bit'):
164 return output.write(input.read())
165 if encodetab.has_key(encoding):
166 pipethrough(input, encodetab[encoding], output)
167 else:
168 raise ValueError, \
169 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000170
Guido van Rossume3cd1511997-07-11 16:33:26 +0000171# The following is no longer used for standard encodings
172
173# XXX This requires that uudecode and mmencode are in $PATH
174
Guido van Rossumb6775db1994-08-01 11:34:53 +0000175uudecode_pipe = '''(
176TEMP=/tmp/@uu.$$
177sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
178cat $TEMP
179rm $TEMP
180)'''
181
182decodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000183 'uuencode': uudecode_pipe,
184 'x-uuencode': uudecode_pipe,
185 'uue': uudecode_pipe,
186 'x-uue': uudecode_pipe,
187 'quoted-printable': 'mmencode -u -q',
188 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000189}
190
191encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000192 'x-uuencode': 'uuencode tempfile',
193 'uuencode': 'uuencode tempfile',
194 'x-uue': 'uuencode tempfile',
195 'uue': 'uuencode tempfile',
196 'quoted-printable': 'mmencode -q',
197 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000198}
199
200def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000201 pipe = os.popen(command, 'w')
202 copyliteral(input, pipe)
203 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000204
205def pipethrough(input, command, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000206 tempname = tempfile.mktemp()
Fred Drake6fd08ba2001-02-23 20:04:54 +0000207 temp = open(tempname, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000208 copyliteral(input, temp)
209 temp.close()
210 pipe = os.popen(command + ' <' + tempname, 'r')
211 copybinary(pipe, output)
212 pipe.close()
213 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000214
215def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000216 while 1:
217 line = input.readline()
218 if not line: break
219 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000220
221def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000222 BUFSIZE = 8192
223 while 1:
224 line = input.read(BUFSIZE)
225 if not line: break
226 output.write(line)