blob: b519c54cdbe075ee28225804223514fd2eec8ee7 [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 Rossumb6775db1994-08-01 11:34:53 +0000143 if decodetab.has_key(encoding):
144 pipethrough(input, decodetab[encoding], output)
145 else:
146 raise ValueError, \
147 'unknown Content-Transfer-Encoding: %s' % encoding
148
149def encode(input, output, encoding):
Guido van Rossum54f22ed2000-02-04 15:10:34 +0000150 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
Guido van Rossume3cd1511997-07-11 16:33:26 +0000151 if encoding == 'base64':
152 import base64
153 return base64.encode(input, output)
154 if encoding == 'quoted-printable':
155 import quopri
156 return quopri.encode(input, output, 0)
Guido van Rossume44a8d91997-12-10 18:54:36 +0000157 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
Guido van Rossume3cd1511997-07-11 16:33:26 +0000158 import uu
159 return uu.encode(input, output)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000160 if encodetab.has_key(encoding):
161 pipethrough(input, encodetab[encoding], output)
162 else:
163 raise ValueError, \
164 'unknown Content-Transfer-Encoding: %s' % encoding
165
Guido van Rossume3cd1511997-07-11 16:33:26 +0000166# The following is no longer used for standard encodings
167
168# XXX This requires that uudecode and mmencode are in $PATH
169
Guido van Rossumb6775db1994-08-01 11:34:53 +0000170uudecode_pipe = '''(
171TEMP=/tmp/@uu.$$
172sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
173cat $TEMP
174rm $TEMP
175)'''
176
177decodetab = {
178 'uuencode': uudecode_pipe,
179 'x-uuencode': uudecode_pipe,
Guido van Rossume44a8d91997-12-10 18:54:36 +0000180 'uue': uudecode_pipe,
181 'x-uue': uudecode_pipe,
Guido van Rossumb6775db1994-08-01 11:34:53 +0000182 'quoted-printable': 'mmencode -u -q',
183 'base64': 'mmencode -u -b',
184}
185
186encodetab = {
187 'x-uuencode': 'uuencode tempfile',
188 'uuencode': 'uuencode tempfile',
Guido van Rossume44a8d91997-12-10 18:54:36 +0000189 'x-uue': 'uuencode tempfile',
190 'uue': 'uuencode tempfile',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000191 'quoted-printable': 'mmencode -q',
192 'base64': 'mmencode -b',
193}
194
195def pipeto(input, command):
196 pipe = os.popen(command, 'w')
197 copyliteral(input, pipe)
198 pipe.close()
199
200def pipethrough(input, command, output):
201 tempname = tempfile.mktemp()
202 try:
203 temp = open(tempname, 'w')
204 except IOError:
205 print '*** Cannot create temp file', `tempname`
206 return
207 copyliteral(input, temp)
208 temp.close()
209 pipe = os.popen(command + ' <' + tempname, 'r')
210 copybinary(pipe, output)
211 pipe.close()
212 os.unlink(tempname)
213
214def copyliteral(input, output):
215 while 1:
216 line = input.readline()
217 if not line: break
218 output.write(line)
219
220def copybinary(input, output):
221 BUFSIZE = 8192
222 while 1:
223 line = input.read(BUFSIZE)
224 if not line: break
225 output.write(line)