blob: 27996e04cb4b3766e74591bcd3f58d22040ca2b1 [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 string
7import tempfile
Guido van Rossum01ca3361992-07-13 14:28:59 +00008
9
Guido van Rossum01ca3361992-07-13 14:28:59 +000010class Message(rfc822.Message):
Guido van Rossum54f22ed2000-02-04 15:10:34 +000011 """A derived class of rfc822.Message that knows about MIME headers and
12 contains some hooks for decoding encoded and multipart messages."""
Guido van Rossum01ca3361992-07-13 14:28:59 +000013
Guido van Rossum87555821995-08-07 20:13:56 +000014 def __init__(self, fp, seekable = 1):
15 rfc822.Message.__init__(self, fp, seekable)
Guido van Rossum01ca3361992-07-13 14:28:59 +000016 self.encodingheader = \
Guido van Rossum43245361995-08-29 19:25:11 +000017 self.getheader('content-transfer-encoding')
Guido van Rossum01ca3361992-07-13 14:28:59 +000018 self.typeheader = \
19 self.getheader('content-type')
20 self.parsetype()
21 self.parseplist()
Guido van Rossum01ca3361992-07-13 14:28:59 +000022
23 def parsetype(self):
24 str = self.typeheader
25 if str == None:
26 str = 'text/plain'
27 if ';' in str:
28 i = string.index(str, ';')
29 self.plisttext = str[i:]
30 str = str[:i]
31 else:
32 self.plisttext = ''
33 fields = string.splitfields(str, '/')
34 for i in range(len(fields)):
35 fields[i] = string.lower(string.strip(fields[i]))
36 self.type = string.joinfields(fields, '/')
37 self.maintype = fields[0]
38 self.subtype = string.joinfields(fields[1:], '/')
39
40 def parseplist(self):
41 str = self.plisttext
42 self.plist = []
43 while str[:1] == ';':
44 str = str[1:]
45 if ';' in str:
46 # XXX Should parse quotes!
47 end = string.index(str, ';')
48 else:
49 end = len(str)
50 f = str[:end]
51 if '=' in f:
52 i = string.index(f, '=')
53 f = string.lower(string.strip(f[:i])) + \
54 '=' + string.strip(f[i+1:])
55 self.plist.append(string.strip(f))
Guido van Rossumeacce121996-01-25 18:07:08 +000056 str = str[end:]
Guido van Rossum01ca3361992-07-13 14:28:59 +000057
58 def getplist(self):
59 return self.plist
60
61 def getparam(self, name):
62 name = string.lower(name) + '='
63 n = len(name)
64 for p in self.plist:
65 if p[:n] == name:
66 return rfc822.unquote(p[n:])
67 return None
68
Guido van Rossum4be63d11996-10-04 20:14:02 +000069 def getparamnames(self):
70 result = []
71 for p in self.plist:
72 i = string.find(p, '=')
73 if i >= 0:
74 result.append(string.lower(p[:i]))
75 return result
76
Guido van Rossum01ca3361992-07-13 14:28:59 +000077 def getencoding(self):
78 if self.encodingheader == None:
79 return '7bit'
Guido van Rossumb6775db1994-08-01 11:34:53 +000080 return string.lower(self.encodingheader)
Guido van Rossum01ca3361992-07-13 14:28:59 +000081
82 def gettype(self):
83 return self.type
84
85 def getmaintype(self):
86 return self.maintype
87
88 def getsubtype(self):
89 return self.subtype
90
91
92
93
94# Utility functions
95# -----------------
96
97
Guido van Rossum01ca3361992-07-13 14:28:59 +000098_prefix = None
99
100def choose_boundary():
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000101 """Return a random string usable as a multipart boundary.
102 The method used is so that it is *very* unlikely that the same
103 string of characters will every occur again in the Universe,
104 so the caller needn't check the data it is packing for the
105 occurrence of the boundary.
106
107 The boundary contains dots so you have to quote it in the header."""
108
Guido van Rossum8460b941996-05-28 22:59:58 +0000109 global _prefix
Guido van Rossum01ca3361992-07-13 14:28:59 +0000110 import time
Guido van Rossumb26a1b41998-05-20 17:05:52 +0000111 import random
Guido van Rossum01ca3361992-07-13 14:28:59 +0000112 if _prefix == None:
113 import socket
114 import os
115 hostid = socket.gethostbyname(socket.gethostname())
Guido van Rossum0c8cf881996-08-26 16:40:20 +0000116 try:
117 uid = `os.getuid()`
118 except:
119 uid = '1'
120 try:
121 pid = `os.getpid()`
122 except:
123 pid = '1'
Guido van Rossum01ca3361992-07-13 14:28:59 +0000124 _prefix = hostid + '.' + uid + '.' + pid
Guido van Rossum5c7e8cd1998-04-11 03:06:02 +0000125 timestamp = '%.3f' % time.time()
Guido van Rossumb26a1b41998-05-20 17:05:52 +0000126 seed = `random.randint(0, 32767)`
Guido van Rossum01ca3361992-07-13 14:28:59 +0000127 return _prefix + '.' + timestamp + '.' + seed
Guido van Rossumb6775db1994-08-01 11:34:53 +0000128
129
130# Subroutines for decoding some common content-transfer-types
131
Guido van Rossumb6775db1994-08-01 11:34:53 +0000132def decode(input, output, encoding):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000133 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
Guido van Rossume3cd1511997-07-11 16:33:26 +0000134 if encoding == 'base64':
135 import base64
136 return base64.decode(input, output)
137 if encoding == 'quoted-printable':
138 import quopri
139 return quopri.decode(input, output)
Guido van Rossume44a8d91997-12-10 18:54:36 +0000140 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
Guido van Rossume3cd1511997-07-11 16:33:26 +0000141 import uu
142 return uu.decode(input, output)
Guido van Rossum13c8c022000-04-04 20:53:07 +0000143 if encoding in ('7bit', '8bit'):
144 output.write(input.read())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000145 if decodetab.has_key(encoding):
146 pipethrough(input, decodetab[encoding], output)
147 else:
148 raise ValueError, \
149 'unknown Content-Transfer-Encoding: %s' % encoding
150
151def encode(input, output, encoding):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000152 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
Guido van Rossume3cd1511997-07-11 16:33:26 +0000153 if encoding == 'base64':
154 import base64
155 return base64.encode(input, output)
156 if encoding == 'quoted-printable':
157 import quopri
158 return quopri.encode(input, output, 0)
Guido van Rossume44a8d91997-12-10 18:54:36 +0000159 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
Guido van Rossume3cd1511997-07-11 16:33:26 +0000160 import uu
161 return uu.encode(input, output)
Guido van Rossum13c8c022000-04-04 20:53:07 +0000162 if encoding in ('7bit', '8bit'):
163 output.write(input.read())
Guido van Rossumb6775db1994-08-01 11:34:53 +0000164 if encodetab.has_key(encoding):
165 pipethrough(input, encodetab[encoding], output)
166 else:
167 raise ValueError, \
168 'unknown Content-Transfer-Encoding: %s' % encoding
169
Guido van Rossume3cd1511997-07-11 16:33:26 +0000170# The following is no longer used for standard encodings
171
172# XXX This requires that uudecode and mmencode are in $PATH
173
Guido van Rossumb6775db1994-08-01 11:34:53 +0000174uudecode_pipe = '''(
175TEMP=/tmp/@uu.$$
176sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
177cat $TEMP
178rm $TEMP
179)'''
180
181decodetab = {
182 'uuencode': uudecode_pipe,
183 'x-uuencode': uudecode_pipe,
Guido van Rossume44a8d91997-12-10 18:54:36 +0000184 'uue': uudecode_pipe,
185 'x-uue': uudecode_pipe,
Guido van Rossumb6775db1994-08-01 11:34:53 +0000186 'quoted-printable': 'mmencode -u -q',
187 'base64': 'mmencode -u -b',
188}
189
190encodetab = {
191 'x-uuencode': 'uuencode tempfile',
192 'uuencode': 'uuencode tempfile',
Guido van Rossume44a8d91997-12-10 18:54:36 +0000193 'x-uue': 'uuencode tempfile',
194 'uue': 'uuencode tempfile',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000195 'quoted-printable': 'mmencode -q',
196 'base64': 'mmencode -b',
197}
198
199def pipeto(input, command):
200 pipe = os.popen(command, 'w')
201 copyliteral(input, pipe)
202 pipe.close()
203
204def pipethrough(input, command, output):
205 tempname = tempfile.mktemp()
206 try:
207 temp = open(tempname, 'w')
208 except IOError:
209 print '*** Cannot create temp file', `tempname`
210 return
211 copyliteral(input, temp)
212 temp.close()
213 pipe = os.popen(command + ' <' + tempname, 'r')
214 copybinary(pipe, output)
215 pipe.close()
216 os.unlink(tempname)
217
218def copyliteral(input, output):
219 while 1:
220 line = input.readline()
221 if not line: break
222 output.write(line)
223
224def copybinary(input, output):
225 BUFSIZE = 8192
226 while 1:
227 line = input.read(BUFSIZE)
228 if not line: break
229 output.write(line)