| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 1 | # Various tools used by MIME-reading or MIME-writing programs. | 
|  | 2 |  | 
|  | 3 |  | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 4 | import os | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 5 | import rfc822 | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 6 | import string | 
|  | 7 | import tempfile | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 8 |  | 
|  | 9 |  | 
|  | 10 | # A derived class of rfc822.Message that knows about MIME headers and | 
|  | 11 | # contains some hooks for decoding encoded and multipart messages. | 
|  | 12 |  | 
|  | 13 | class Message(rfc822.Message): | 
|  | 14 |  | 
| Guido van Rossum | 8755582 | 1995-08-07 20:13:56 +0000 | [diff] [blame] | 15 | def __init__(self, fp, seekable = 1): | 
|  | 16 | rfc822.Message.__init__(self, fp, seekable) | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 17 | self.encodingheader = \ | 
| Guido van Rossum | 4324536 | 1995-08-29 19:25:11 +0000 | [diff] [blame] | 18 | self.getheader('content-transfer-encoding') | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 19 | self.typeheader = \ | 
|  | 20 | self.getheader('content-type') | 
|  | 21 | self.parsetype() | 
|  | 22 | self.parseplist() | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 23 |  | 
|  | 24 | def parsetype(self): | 
|  | 25 | str = self.typeheader | 
|  | 26 | if str == None: | 
|  | 27 | str = 'text/plain' | 
|  | 28 | if ';' in str: | 
|  | 29 | i = string.index(str, ';') | 
|  | 30 | self.plisttext = str[i:] | 
|  | 31 | str = str[:i] | 
|  | 32 | else: | 
|  | 33 | self.plisttext = '' | 
|  | 34 | fields = string.splitfields(str, '/') | 
|  | 35 | for i in range(len(fields)): | 
|  | 36 | fields[i] = string.lower(string.strip(fields[i])) | 
|  | 37 | self.type = string.joinfields(fields, '/') | 
|  | 38 | self.maintype = fields[0] | 
|  | 39 | self.subtype = string.joinfields(fields[1:], '/') | 
|  | 40 |  | 
|  | 41 | 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 = string.index(str, ';') | 
|  | 49 | else: | 
|  | 50 | end = len(str) | 
|  | 51 | f = str[:end] | 
|  | 52 | if '=' in f: | 
|  | 53 | i = string.index(f, '=') | 
|  | 54 | f = string.lower(string.strip(f[:i])) + \ | 
|  | 55 | '=' + string.strip(f[i+1:]) | 
|  | 56 | self.plist.append(string.strip(f)) | 
| Guido van Rossum | eacce12 | 1996-01-25 18:07:08 +0000 | [diff] [blame] | 57 | str = str[end:] | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 58 |  | 
|  | 59 | def getplist(self): | 
|  | 60 | return self.plist | 
|  | 61 |  | 
|  | 62 | def getparam(self, name): | 
|  | 63 | name = string.lower(name) + '=' | 
|  | 64 | n = len(name) | 
|  | 65 | for p in self.plist: | 
|  | 66 | if p[:n] == name: | 
|  | 67 | return rfc822.unquote(p[n:]) | 
|  | 68 | return None | 
|  | 69 |  | 
| Guido van Rossum | 4be63d1 | 1996-10-04 20:14:02 +0000 | [diff] [blame] | 70 | def getparamnames(self): | 
|  | 71 | result = [] | 
|  | 72 | for p in self.plist: | 
|  | 73 | i = string.find(p, '=') | 
|  | 74 | if i >= 0: | 
|  | 75 | result.append(string.lower(p[:i])) | 
|  | 76 | return result | 
|  | 77 |  | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 78 | def getencoding(self): | 
|  | 79 | if self.encodingheader == None: | 
|  | 80 | return '7bit' | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 81 | return string.lower(self.encodingheader) | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 82 |  | 
|  | 83 | def gettype(self): | 
|  | 84 | return self.type | 
|  | 85 |  | 
|  | 86 | def getmaintype(self): | 
|  | 87 | return self.maintype | 
|  | 88 |  | 
|  | 89 | def getsubtype(self): | 
|  | 90 | return self.subtype | 
|  | 91 |  | 
|  | 92 |  | 
|  | 93 |  | 
|  | 94 |  | 
|  | 95 | # Utility functions | 
|  | 96 | # ----------------- | 
|  | 97 |  | 
|  | 98 |  | 
|  | 99 | # Return a random string usable as a multipart boundary. | 
|  | 100 | # The method used is so that it is *very* unlikely that the same | 
|  | 101 | # string of characters will every occur again in the Universe, | 
|  | 102 | # so the caller needn't check the data it is packing for the | 
|  | 103 | # occurrence of the boundary. | 
|  | 104 | # | 
|  | 105 | # The boundary contains dots so you have to quote it in the header. | 
|  | 106 |  | 
|  | 107 | _prefix = None | 
|  | 108 |  | 
|  | 109 | def choose_boundary(): | 
| Guido van Rossum | 8460b94 | 1996-05-28 22:59:58 +0000 | [diff] [blame] | 110 | global _prefix | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 111 | import time | 
| Guido van Rossum | b26a1b4 | 1998-05-20 17:05:52 +0000 | [diff] [blame] | 112 | import random | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 113 | if _prefix == None: | 
|  | 114 | import socket | 
|  | 115 | import os | 
|  | 116 | hostid = socket.gethostbyname(socket.gethostname()) | 
| Guido van Rossum | 0c8cf88 | 1996-08-26 16:40:20 +0000 | [diff] [blame] | 117 | try: | 
|  | 118 | uid = `os.getuid()` | 
|  | 119 | except: | 
|  | 120 | uid = '1' | 
|  | 121 | try: | 
|  | 122 | pid = `os.getpid()` | 
|  | 123 | except: | 
|  | 124 | pid = '1' | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 125 | _prefix = hostid + '.' + uid + '.' + pid | 
| Guido van Rossum | 5c7e8cd | 1998-04-11 03:06:02 +0000 | [diff] [blame] | 126 | timestamp = '%.3f' % time.time() | 
| Guido van Rossum | b26a1b4 | 1998-05-20 17:05:52 +0000 | [diff] [blame] | 127 | seed = `random.randint(0, 32767)` | 
| Guido van Rossum | 01ca336 | 1992-07-13 14:28:59 +0000 | [diff] [blame] | 128 | return _prefix + '.' + timestamp + '.' + seed | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 129 |  | 
|  | 130 |  | 
|  | 131 | # Subroutines for decoding some common content-transfer-types | 
|  | 132 |  | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 133 | def decode(input, output, encoding): | 
| Guido van Rossum | e3cd151 | 1997-07-11 16:33:26 +0000 | [diff] [blame] | 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) | 
| Guido van Rossum | e44a8d9 | 1997-12-10 18:54:36 +0000 | [diff] [blame] | 140 | if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'): | 
| Guido van Rossum | e3cd151 | 1997-07-11 16:33:26 +0000 | [diff] [blame] | 141 | import uu | 
|  | 142 | return uu.decode(input, output) | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 143 | if decodetab.has_key(encoding): | 
|  | 144 | pipethrough(input, decodetab[encoding], output) | 
|  | 145 | else: | 
|  | 146 | raise ValueError, \ | 
|  | 147 | 'unknown Content-Transfer-Encoding: %s' % encoding | 
|  | 148 |  | 
|  | 149 | def encode(input, output, encoding): | 
| Guido van Rossum | e3cd151 | 1997-07-11 16:33:26 +0000 | [diff] [blame] | 150 | if encoding == 'base64': | 
|  | 151 | import base64 | 
|  | 152 | return base64.encode(input, output) | 
|  | 153 | if encoding == 'quoted-printable': | 
|  | 154 | import quopri | 
|  | 155 | return quopri.encode(input, output, 0) | 
| Guido van Rossum | e44a8d9 | 1997-12-10 18:54:36 +0000 | [diff] [blame] | 156 | if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'): | 
| Guido van Rossum | e3cd151 | 1997-07-11 16:33:26 +0000 | [diff] [blame] | 157 | import uu | 
|  | 158 | return uu.encode(input, output) | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 159 | if encodetab.has_key(encoding): | 
|  | 160 | pipethrough(input, encodetab[encoding], output) | 
|  | 161 | else: | 
|  | 162 | raise ValueError, \ | 
|  | 163 | 'unknown Content-Transfer-Encoding: %s' % encoding | 
|  | 164 |  | 
| Guido van Rossum | e3cd151 | 1997-07-11 16:33:26 +0000 | [diff] [blame] | 165 | # The following is no longer used for standard encodings | 
|  | 166 |  | 
|  | 167 | # XXX This requires that uudecode and mmencode are in $PATH | 
|  | 168 |  | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 169 | uudecode_pipe = '''( | 
|  | 170 | TEMP=/tmp/@uu.$$ | 
|  | 171 | sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode | 
|  | 172 | cat $TEMP | 
|  | 173 | rm $TEMP | 
|  | 174 | )''' | 
|  | 175 |  | 
|  | 176 | decodetab = { | 
|  | 177 | 'uuencode':		uudecode_pipe, | 
|  | 178 | 'x-uuencode':		uudecode_pipe, | 
| Guido van Rossum | e44a8d9 | 1997-12-10 18:54:36 +0000 | [diff] [blame] | 179 | 'uue':			uudecode_pipe, | 
|  | 180 | 'x-uue':		uudecode_pipe, | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 181 | 'quoted-printable':	'mmencode -u -q', | 
|  | 182 | 'base64':		'mmencode -u -b', | 
|  | 183 | } | 
|  | 184 |  | 
|  | 185 | encodetab = { | 
|  | 186 | 'x-uuencode':		'uuencode tempfile', | 
|  | 187 | 'uuencode':		'uuencode tempfile', | 
| Guido van Rossum | e44a8d9 | 1997-12-10 18:54:36 +0000 | [diff] [blame] | 188 | 'x-uue':		'uuencode tempfile', | 
|  | 189 | 'uue':			'uuencode tempfile', | 
| Guido van Rossum | b6775db | 1994-08-01 11:34:53 +0000 | [diff] [blame] | 190 | 'quoted-printable':	'mmencode -q', | 
|  | 191 | 'base64':		'mmencode -b', | 
|  | 192 | } | 
|  | 193 |  | 
|  | 194 | def pipeto(input, command): | 
|  | 195 | pipe = os.popen(command, 'w') | 
|  | 196 | copyliteral(input, pipe) | 
|  | 197 | pipe.close() | 
|  | 198 |  | 
|  | 199 | def pipethrough(input, command, output): | 
|  | 200 | tempname = tempfile.mktemp() | 
|  | 201 | try: | 
|  | 202 | temp = open(tempname, 'w') | 
|  | 203 | except IOError: | 
|  | 204 | print '*** Cannot create temp file', `tempname` | 
|  | 205 | return | 
|  | 206 | copyliteral(input, temp) | 
|  | 207 | temp.close() | 
|  | 208 | pipe = os.popen(command + ' <' + tempname, 'r') | 
|  | 209 | copybinary(pipe, output) | 
|  | 210 | pipe.close() | 
|  | 211 | os.unlink(tempname) | 
|  | 212 |  | 
|  | 213 | def copyliteral(input, output): | 
|  | 214 | while 1: | 
|  | 215 | line = input.readline() | 
|  | 216 | if not line: break | 
|  | 217 | output.write(line) | 
|  | 218 |  | 
|  | 219 | def copybinary(input, output): | 
|  | 220 | BUFSIZE = 8192 | 
|  | 221 | while 1: | 
|  | 222 | line = input.read(BUFSIZE) | 
|  | 223 | if not line: break | 
|  | 224 | output.write(line) |