blob: 81913bdf3d7c1898b8d1b2f7d3a05407b1725983 [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()`
119 except:
120 uid = '1'
121 try:
122 pid = `os.getpid()`
123 except:
124 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()
207 try:
208 temp = open(tempname, 'w')
209 except IOError:
210 print '*** Cannot create temp file', `tempname`
211 return
212 copyliteral(input, temp)
213 temp.close()
214 pipe = os.popen(command + ' <' + tempname, 'r')
215 copybinary(pipe, output)
216 pipe.close()
217 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000218
219def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000220 while 1:
221 line = input.readline()
222 if not line: break
223 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000224
225def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000226 BUFSIZE = 8192
227 while 1:
228 line = input.read(BUFSIZE)
229 if not line: break
230 output.write(line)