blob: f1e20d444f41868deb9fb059ced0273e8e3ce7f5 [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
Tim Peters07e99cb2001-01-14 23:47:14 +0000115 hostid = socket.gethostbyname(socket.gethostname())
116 try:
117 uid = `os.getuid()`
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000118 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000119 uid = '1'
120 try:
121 pid = `os.getpid()`
Skip Montanaro91cc17d2002-03-23 05:58:52 +0000122 except AttributeError:
Tim Peters07e99cb2001-01-14 23:47:14 +0000123 pid = '1'
124 _prefix = hostid + '.' + uid + '.' + pid
125 timestamp = '%.3f' % time.time()
126 seed = `random.randint(0, 32767)`
127 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):
Tim Peters07e99cb2001-01-14 23:47:14 +0000133 """Decode common content-transfer-encodings (base64, quopri, uuencode)."""
134 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)
140 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
141 import uu
142 return uu.decode(input, output)
143 if encoding in ('7bit', '8bit'):
144 return output.write(input.read())
Raymond Hettinger54f02222002-06-01 14:18:47 +0000145 if encoding in decodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000146 pipethrough(input, decodetab[encoding], output)
147 else:
148 raise ValueError, \
149 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000150
151def encode(input, output, encoding):
Tim Peters07e99cb2001-01-14 23:47:14 +0000152 """Encode common content-transfer-encodings (base64, quopri, uuencode)."""
153 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)
159 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'):
160 import uu
161 return uu.encode(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 encodetab:
Tim Peters07e99cb2001-01-14 23:47:14 +0000165 pipethrough(input, encodetab[encoding], output)
166 else:
167 raise ValueError, \
168 'unknown Content-Transfer-Encoding: %s' % encoding
Guido van Rossumb6775db1994-08-01 11:34:53 +0000169
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 = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000182 'uuencode': uudecode_pipe,
183 'x-uuencode': uudecode_pipe,
184 'uue': uudecode_pipe,
185 'x-uue': uudecode_pipe,
186 'quoted-printable': 'mmencode -u -q',
187 'base64': 'mmencode -u -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000188}
189
190encodetab = {
Tim Peters07e99cb2001-01-14 23:47:14 +0000191 'x-uuencode': 'uuencode tempfile',
192 'uuencode': 'uuencode tempfile',
193 'x-uue': 'uuencode tempfile',
194 'uue': 'uuencode tempfile',
195 'quoted-printable': 'mmencode -q',
196 'base64': 'mmencode -b',
Guido van Rossumb6775db1994-08-01 11:34:53 +0000197}
198
199def pipeto(input, command):
Tim Peters07e99cb2001-01-14 23:47:14 +0000200 pipe = os.popen(command, 'w')
201 copyliteral(input, pipe)
202 pipe.close()
Guido van Rossumb6775db1994-08-01 11:34:53 +0000203
204def pipethrough(input, command, output):
Guido van Rossum3b0a3292002-08-09 16:38:32 +0000205 (fd, tempname) = tempfile.mkstemp()
206 temp = os.fdopen(fd, 'w')
Tim Peters07e99cb2001-01-14 23:47:14 +0000207 copyliteral(input, temp)
208 temp.close()
209 pipe = os.popen(command + ' <' + tempname, 'r')
210 copybinary(pipe, output)
211 pipe.close()
212 os.unlink(tempname)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000213
214def copyliteral(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000215 while 1:
216 line = input.readline()
217 if not line: break
218 output.write(line)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000219
220def copybinary(input, output):
Tim Peters07e99cb2001-01-14 23:47:14 +0000221 BUFSIZE = 8192
222 while 1:
223 line = input.read(BUFSIZE)
224 if not line: break
225 output.write(line)