PageRenderTime 57ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/fstmerge/examples/SpamBayes/rev3103-3133/spambayes/spambayes/mboxutils.py

https://github.com/RoDaniel/featurehouse
Python | 371 lines | 345 code | 10 blank | 16 comment | 0 complexity | 787a10387ed0a0b718c94cdff56ac909 MD5 | raw file
  1. """Utilities for dealing with various types of mailboxes.
  2. This is mostly a wrapper around the various useful classes in the
  3. standard mailbox module, to do some intelligent guessing of the
  4. mailbox type given a mailbox argument.
  5. +foo -- MH mailbox +foo
  6. +foo,bar -- MH mailboxes +foo and +bar concatenated
  7. +ALL -- a shortcut for *all* MH mailboxes
  8. /foo/bar -- (existing file) a Unix-style mailbox
  9. /foo/bar/ -- (existing directory) a directory full of .txt and .lorien
  10. files
  11. /foo/bar/ -- (existing directory with a cur/ subdirectory)
  12. Maildir mailbox
  13. /foo/Mail/bar/ -- (existing directory with /Mail/ in its path)
  14. alternative way of spelling an MH mailbox
  15. """
  16. from __future__ import generators
  17. import os
  18. import sys
  19. import glob
  20. import email
  21. import mailbox
  22. import email.Message
  23. import re
  24. import traceback
  25. class DirOfTxtFileMailbox :
  26. """Directory of files each assumed to contain an RFC-822 message.
  27. If the filename ends with ".emlx", assumes that the file is an
  28. RFC-822 message wrapped in Apple Mail's proprietory .emlx format.
  29. The emlx format is simply the length of the message (as a string
  30. on the first line, then the raw message text, then the contents of
  31. a plist (XML) file that contains data that Mail uses (subject,
  32. flags, sender, and so forth). We ignore this plist data).
  33. Subdirectories are traversed recursively.
  34. """
  35. def __init__(self, dirname, factory):
  36. self.names = glob.glob(os.path.join(dirname, "*"))
  37. self.names.sort()
  38. self.factory = factory
  39. def __iter__(self):
  40. for name in self.names:
  41. if os.path.isdir(name):
  42. for mbox in DirOfTxtFileMailbox(name, self.factory):
  43. yield mbox
  44. elif os.path.splitext(name)[1] == ".emlx":
  45. f = open(name)
  46. length = int(f.readline().rstrip())
  47. yield self.factory(f.read(length))
  48. f.close()
  49. else:
  50. try:
  51. f = open(name)
  52. except IOError:
  53. continue
  54. yield self.factory(f)
  55. f.close()
  56. def full_messages(msgs):
  57. """A generator that transforms each message by calling its
  58. get_full_message() method. Used for IMAP messages since they don't really
  59. have their content by default.
  60. """
  61. for x in msgs:
  62. yield x.get_full_message()
  63. def _cat(seqs):
  64. for seq in seqs:
  65. for item in seq:
  66. yield item
  67. def getmbox(name):
  68. """Return an mbox iterator given a file/directory/folder name."""
  69. if name == "-":
  70. return [get_message(sys.stdin)]
  71. if name.startswith("+"):
  72. name = name[1:]
  73. import mhlib
  74. mh = mhlib.MH()
  75. if name == "ALL":
  76. names = mh.listfolders()
  77. elif ',' in name:
  78. names = name.split(',')
  79. else:
  80. names = [name]
  81. mboxes = []
  82. mhpath = mh.getpath()
  83. for name in names:
  84. filename = os.path.join(mhpath, name)
  85. mbox = mailbox.MHMailbox(filename, get_message)
  86. mboxes.append(mbox)
  87. if len(mboxes) == 1:
  88. return iter(mboxes[0])
  89. else:
  90. return _cat(mboxes)
  91. elif name.startswith(":"):
  92. parts = re.compile(
  93. ':(?P<user>[^@:]+):(?P<pwd>[^@]+)@(?P<server>[^:]+(:[0-9]+)?):(?P<name>[^:]+)'
  94. ).match(name).groupdict()
  95. from scripts.sb_imapfilter import IMAPSession, IMAPFolder
  96. from spambayes import Stats, message
  97. from spambayes.Options import options
  98. session = IMAPSession(parts['server'])
  99. session.login(parts['user'], parts['pwd'])
  100. folder_list = session.folder_list()
  101. if name == "ALL":
  102. names = folder_list
  103. else:
  104. names = parts['name'].split(',')
  105. message_db = message.Message().message_info_db
  106. stats = Stats.Stats(options, message_db)
  107. mboxes = [ IMAPFolder(n,session,stats) for n in names ]
  108. if len(mboxes) == 1:
  109. return full_messages(mboxes[0])
  110. else:
  111. return _cat([full_messages(x) for x in mboxes])
  112. if os.path.isdir(name):
  113. if os.path.exists(os.path.join(name, 'cur')):
  114. mbox = mailbox.Maildir(name, get_message)
  115. elif name.find("/Mail/") >= 0:
  116. mbox = mailbox.MHMailbox(name, get_message)
  117. else:
  118. mbox = DirOfTxtFileMailbox(name, get_message)
  119. else:
  120. fp = open(name, "rb")
  121. mbox = mailbox.PortableUnixMailbox(fp, get_message)
  122. return iter(mbox)
  123. def get_message(obj):
  124. """Return an email Message object.
  125. The argument may be a Message object already, in which case it's
  126. returned as-is.
  127. If the argument is a string or file-like object (supports read()),
  128. the email package is used to create a Message object from it. This
  129. can fail if the message is malformed. In that case, the headers
  130. (everything through the first blank line) are thrown out, and the
  131. rest of the text is wrapped in a bare email.Message.Message.
  132. Note that we can't use our own message class here, because this
  133. function is imported by tokenizer, and our message class imports
  134. tokenizer, so we get a circular import problem. In any case, this
  135. function does not need anything that our message class offers, so that
  136. shouldn't matter.
  137. """
  138. if isinstance(obj, email.Message.Message):
  139. return obj
  140. if hasattr(obj, "read"):
  141. obj = obj.read()
  142. try:
  143. msg = email.message_from_string(obj)
  144. except email.Errors.MessageParseError:
  145. headers = extract_headers(obj)
  146. obj = obj[len(headers):]
  147. msg = email.Message.Message()
  148. msg.set_payload(obj)
  149. return msg
  150. def as_string(msg, unixfrom=False):
  151. """Convert a Message object to a string in a safe-ish way.
  152. In email pkg version 2.5.4 and earlier, msg.as_string() can raise
  153. TypeError for some malformed messages. This catches that and attempts
  154. to return something approximating the original message.
  155. To Do: This really should be done by subclassing email.Message.Message
  156. and making this function the as_string() method. After 1.0.
  157. [Tony] Better: sb_filter & sb_mboxtrain should stop using this and
  158. start using the spambayes.Message classes. They might need a little
  159. bit of rearranging, but that should work nicely, and mean that all
  160. this code is together in one place.
  161. """
  162. if isinstance(msg, str):
  163. return msg
  164. try:
  165. return msg.as_string(unixfrom)
  166. except TypeError:
  167. ty, val, tb = sys.exc_info()
  168. exclines = traceback.format_exception(ty, val, tb)[1:]
  169. excstr = " ".join(exclines).strip()
  170. headers = []
  171. if unixfrom:
  172. headers.append(msg.get_unixfrom())
  173. for (hdr, val) in msg.items():
  174. headers.append("%s: %s" % (hdr, val))
  175. headers.append("X-Spambayes-Exception: %s" % excstr)
  176. parts = ["%s\n" % "\n".join(headers)]
  177. boundary = msg.get_boundary()
  178. for part in msg.get_payload():
  179. if boundary:
  180. parts.append(boundary)
  181. try:
  182. parts.append(part.as_string())
  183. except AttributeError:
  184. parts.append(str(part))
  185. if boundary:
  186. parts.append("--%s--" % boundary)
  187. return "\n".join(parts)+"\n"
  188. header_break_re = re.compile(r"\r?\n(\r?\n)")
  189. def extract_headers(text):
  190. """Very simple-minded header extraction: prefix of text up to blank line.
  191. A blank line is recognized via two adjacent line-ending sequences, where
  192. a line-ending sequence is a newline optionally preceded by a carriage
  193. return.
  194. If no blank line is found, all of text is considered to be a potential
  195. header section. If a blank line is found, the text up to (but not
  196. including) the blank line is considered to be a potential header section.
  197. The potential header section is returned, unless it doesn't contain a
  198. colon, in which case an empty string is returned.
  199. >>> extract_headers("abc")
  200. ''
  201. >>> extract_headers("abc\\n\\n\\n")
  202. ''
  203. >>> extract_headers("abc: xyz\\n\\n\\n")
  204. 'abc: xyz\\n'
  205. >>> extract_headers("abc: xyz\\r\\n\\r\\n\\r\\n")
  206. 'abc: xyz\\r\\n'
  207. >>> extract_headers("a: b\\ngibberish\\n\\nmore gibberish")
  208. 'a: b\\ngibberish\\n'
  209. """
  210. m = header_break_re.search(text)
  211. if m:
  212. eol = m.start(1)
  213. text = text[:eol]
  214. if ':' not in text:
  215. text = ""
  216. return text
  217. def _test():
  218. import doctest, mboxutils
  219. return doctest.testmod(mboxutils)
  220. if __name__ == "__main__":
  221. _test()
  222. if __name__ == "__main__":
  223. _test()