You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mimetools.py 6.6KB

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