1"""Various tools used by MIME-reading or MIME-writing programs.""" 2 3 4import os 5import sys 6import tempfile 7from warnings import filterwarnings, catch_warnings 8with catch_warnings(): 9 if sys.py3kwarning: 10 filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning) 11 import rfc822 12 13from warnings import warnpy3k 14warnpy3k("in 3.x, mimetools has been removed in favor of the email package", 15 stacklevel=2) 16 17__all__ = ["Message","choose_boundary","encode","decode","copyliteral", 18 "copybinary"] 19 20class Message(rfc822.Message): 21 """A derived class of rfc822.Message that knows about MIME headers and 22 contains some hooks for decoding encoded and multipart messages.""" 23 24 def __init__(self, fp, seekable = 1): 25 rfc822.Message.__init__(self, fp, seekable) 26 self.encodingheader = \ 27 self.getheader('content-transfer-encoding') 28 self.typeheader = \ 29 self.getheader('content-type') 30 self.parsetype() 31 self.parseplist() 32 33 def parsetype(self): 34 str = self.typeheader 35 if str is None: 36 str = 'text/plain' 37 if ';' in str: 38 i = str.index(';') 39 self.plisttext = str[i:] 40 str = str[:i] 41 else: 42 self.plisttext = '' 43 fields = str.split('/') 44 for i in range(len(fields)): 45 fields[i] = fields[i].strip().lower() 46 self.type = '/'.join(fields) 47 self.maintype = fields[0] 48 self.subtype = '/'.join(fields[1:]) 49 50 def parseplist(self): 51 str = self.plisttext 52 self.plist = [] 53 while str[:1] == ';': 54 str = str[1:] 55 if ';' in str: 56 # XXX Should parse quotes! 57 end = str.index(';') 58 else: 59 end = len(str) 60 f = str[:end] 61 if '=' in f: 62 i = f.index('=') 63 f = f[:i].strip().lower() + \ 64 '=' + f[i+1:].strip() 65 self.plist.append(f.strip()) 66 str = str[end:] 67 68 def getplist(self): 69 return self.plist 70 71 def getparam(self, name): 72 name = name.lower() + '=' 73 n = len(name) 74 for p in self.plist: 75 if p[:n] == name: 76 return rfc822.unquote(p[n:]) 77 return None 78 79 def getparamnames(self): 80 result = [] 81 for p in self.plist: 82 i = p.find('=') 83 if i >= 0: 84 result.append(p[:i].lower()) 85 return result 86 87 def getencoding(self): 88 if self.encodingheader is None: 89 return '7bit' 90 return self.encodingheader.lower() 91 92 def gettype(self): 93 return self.type 94 95 def getmaintype(self): 96 return self.maintype 97 98 def getsubtype(self): 99 return self.subtype 100 101 102 103 104# Utility functions 105# ----------------- 106 107try: 108 import thread 109except ImportError: 110 import dummy_thread as thread 111_counter_lock = thread.allocate_lock() 112del thread 113 114_counter = 0 115def _get_next_counter(): 116 global _counter 117 _counter_lock.acquire() 118 _counter += 1 119 result = _counter 120 _counter_lock.release() 121 return result 122 123_prefix = None 124 125def choose_boundary(): 126 """Return a string usable as a multipart boundary. 127 128 The string chosen is unique within a single program run, and 129 incorporates the user id (if available), process id (if available), 130 and current time. So it's very unlikely the returned string appears 131 in message text, but there's no guarantee. 132 133 The boundary contains dots so you have to quote it in the header.""" 134 135 global _prefix 136 import time 137 if _prefix is None: 138 import socket 139 try: 140 hostid = socket.gethostbyname(socket.gethostname()) 141 except socket.gaierror: 142 hostid = '127.0.0.1' 143 try: 144 uid = repr(os.getuid()) 145 except AttributeError: 146 uid = '1' 147 try: 148 pid = repr(os.getpid()) 149 except AttributeError: 150 pid = '1' 151 _prefix = hostid + '.' + uid + '.' + pid 152 return "%s.%.3f.%d" % (_prefix, time.time(), _get_next_counter()) 153 154 155# Subroutines for decoding some common content-transfer-types 156 157def decode(input, output, encoding): 158 """Decode common content-transfer-encodings (base64, quopri, uuencode).""" 159 if encoding == 'base64': 160 import base64 161 return base64.decode(input, output) 162 if encoding == 'quoted-printable': 163 import quopri 164 return quopri.decode(input, output) 165 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'): 166 import uu 167 return uu.decode(input, output) 168 if encoding in ('7bit', '8bit'): 169 return output.write(input.read()) 170 if encoding in decodetab: 171 pipethrough(input, decodetab[encoding], output) 172 else: 173 raise ValueError, \ 174 'unknown Content-Transfer-Encoding: %s' % encoding 175 176def encode(input, output, encoding): 177 """Encode common content-transfer-encodings (base64, quopri, uuencode).""" 178 if encoding == 'base64': 179 import base64 180 return base64.encode(input, output) 181 if encoding == 'quoted-printable': 182 import quopri 183 return quopri.encode(input, output, 0) 184 if encoding in ('uuencode', 'x-uuencode', 'uue', 'x-uue'): 185 import uu 186 return uu.encode(input, output) 187 if encoding in ('7bit', '8bit'): 188 return output.write(input.read()) 189 if encoding in encodetab: 190 pipethrough(input, encodetab[encoding], output) 191 else: 192 raise ValueError, \ 193 'unknown Content-Transfer-Encoding: %s' % encoding 194 195# The following is no longer used for standard encodings 196 197# XXX This requires that uudecode and mmencode are in $PATH 198 199uudecode_pipe = '''( 200TEMP=/tmp/@uu.$$ 201sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode 202cat $TEMP 203rm $TEMP 204)''' 205 206decodetab = { 207 'uuencode': uudecode_pipe, 208 'x-uuencode': uudecode_pipe, 209 'uue': uudecode_pipe, 210 'x-uue': uudecode_pipe, 211 'quoted-printable': 'mmencode -u -q', 212 'base64': 'mmencode -u -b', 213} 214 215encodetab = { 216 'x-uuencode': 'uuencode tempfile', 217 'uuencode': 'uuencode tempfile', 218 'x-uue': 'uuencode tempfile', 219 'uue': 'uuencode tempfile', 220 'quoted-printable': 'mmencode -q', 221 'base64': 'mmencode -b', 222} 223 224def pipeto(input, command): 225 pipe = os.popen(command, 'w') 226 copyliteral(input, pipe) 227 pipe.close() 228 229def pipethrough(input, command, output): 230 (fd, tempname) = tempfile.mkstemp() 231 temp = os.fdopen(fd, 'w') 232 copyliteral(input, temp) 233 temp.close() 234 pipe = os.popen(command + ' <' + tempname, 'r') 235 copybinary(pipe, output) 236 pipe.close() 237 os.unlink(tempname) 238 239def copyliteral(input, output): 240 while 1: 241 line = input.readline() 242 if not line: break 243 output.write(line) 244 245def copybinary(input, output): 246 BUFSIZE = 8192 247 while 1: 248 line = input.read(BUFSIZE) 249 if not line: break 250 output.write(line) 251