Home | History | Annotate | Download | only in Lib
      1 """Various tools used by MIME-reading or MIME-writing programs."""
      2 
      3 
      4 import os
      5 import sys
      6 import tempfile
      7 from warnings import filterwarnings, catch_warnings
      8 with catch_warnings():
      9     if sys.py3kwarning:
     10         filterwarnings("ignore", ".*rfc822 has been removed", DeprecationWarning)
     11     import rfc822
     12 
     13 from warnings import warnpy3k
     14 warnpy3k("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 
     20 class 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 
    107 try:
    108     import thread
    109 except ImportError:
    110     import dummy_thread as thread
    111 _counter_lock = thread.allocate_lock()
    112 del thread
    113 
    114 _counter = 0
    115 def _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 
    125 def 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 
    157 def 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 
    176 def 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 
    199 uudecode_pipe = '''(
    200 TEMP=/tmp/@uu.$$
    201 sed "s%^begin [0-7][0-7]* .*%begin 600 $TEMP%" | uudecode
    202 cat $TEMP
    203 rm $TEMP
    204 )'''
    205 
    206 decodetab = {
    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 
    215 encodetab = {
    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 
    224 def pipeto(input, command):
    225     pipe = os.popen(command, 'w')
    226     copyliteral(input, pipe)
    227     pipe.close()
    228 
    229 def 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 
    239 def copyliteral(input, output):
    240     while 1:
    241         line = input.readline()
    242         if not line: break
    243         output.write(line)
    244 
    245 def copybinary(input, output):
    246     BUFSIZE = 8192
    247     while 1:
    248         line = input.read(BUFSIZE)
    249         if not line: break
    250         output.write(line)
    251