2 mail_iterator.py - The module contains the MailIterator class.
4 Copyright (c) 2012 Intra2net AG
5 Author: Plamen Dimitrov
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
23 MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
24 UIDVAL_RESP = re.compile(r'(?P<name>.*) \(UIDVALIDITY (?P<uidval>.*)\)')
27 """This class communicates with the e-mail server."""
30 # IMAP4_SSL for connection with an IMAP server
32 # list of tuples (uidvalidity, mailboxname) for the retrieved mailboxes
37 skip_shared_folders = None
39 def __init__(self, server, username, password, skip_shared_folders = False):
40 """Creates a connection and a user session."""
42 self.mail_con = imaplib.IMAP4_SSL(server)
43 self.mail_con.login(username, password)
44 logging.info("Logged in as %s.", username)
46 self.logged_in = False
47 raise UserWarning("Could not log in as user " + username + ".")
50 result, self.mailboxes = self.mail_con.list()
52 raise UserWarning("Could not retrieve mailboxes for user " + username + ".")
53 self.skip_shared_folders = skip_shared_folders
56 """Closes the connection and the user session."""
60 self.mail_con.logout()
65 """Iterates through all mailboxes, returns (uidval,name)."""
66 for mailbox in self.mailboxes:
67 logging.debug("Checking mailbox %s.", mailbox)
68 mailbox = MAILBOX_RESP.match(mailbox.decode('iso-8859-1')).groups()
69 # detect if mailbox is shared and if skip flag is set iterate further
70 if(self.skip_shared_folders and mailbox[2].split(mailbox[1])[0] == '"user'):
71 logging.info("Mailbox %s is shared and therefore skipped.", mailbox[2])
73 # retrieve uidvalidity
75 result, data = self.mail_con.status(mailbox[2], '(UIDVALIDITY)')
77 raise UserWarning("Could not retrieve mailbox uidvalidity.")
78 uidval = UIDVAL_RESP.match(data[0].decode('iso-8859-1')).groups()
79 logging.debug("Extracted mailbox info is %s %s.", data[0], uidval)
80 # select mailbox if writable
82 self.mail_con.select(mailbox[2])
83 except self.mail_con.readonly:
84 logging.warning("Mailbox %s is not writable and therefore skipped.", mailbox[2])
86 yield (mailbox[2], uidval[1])
88 def fetch_messages(self):
89 """Fetches the messages from the current mailbox, return list of uids."""
91 result, data = self.mail_con.uid('search', None, "ALL")
93 raise UserWarning("Could not fetch messages.")
94 mailid_list = data[0].split()
97 def fetch_internal_date(self, mid):
98 """Fetches the internal date of a message, returns a time tuple."""
100 result, data = self.mail_con.uid('fetch', mid, '(INTERNALDATE)')
102 raise UserWarning("Could not fetch the internal date of message" + mid.decode('iso-8859-1') + ".")
103 internal_date = imaplib.Internaldate2tuple(data[0])
106 def fetch_received_date(self, mid):
107 """Fetches the received date of a message, returns bytes reponse."""
109 result, data = self.mail_con.uid('fetch', mid, '(BODY.PEEK[HEADER.FIELDS (RECEIVED)])')
111 raise UserWarning("Could not fetch the received header of message" + mid.decode('iso-8859-1') + ".")
112 return data[0][1].decode('iso-8859-1')
114 def fetch_basic_date(self, mid):
115 """Fetches the basic date of a message, returns bytes reponse."""
117 result, data = self.mail_con.uid('fetch', mid, '(BODY.PEEK[HEADER.FIELDS (DATE)])')
119 raise UserWarning("Could not fetch the date header of message" + mid.decode('iso-8859-1') + ".")
120 return data[0][1].decode('iso-8859-1')
122 def update_message(self, mid, mailbox, internal_date):
123 """Replaces a message with one with correct internal date."""
124 internal_date_seconds = time.mktime(internal_date.timetuple())
125 internal_date_str = imaplib.Time2Internaldate(internal_date_seconds)
127 result, data = self.mail_con.uid('fetch', mid, '(RFC822)')
128 #logging.debug("Entire e-mail is: %s", data[0][1])
130 fetched_flags = self.mail_con.uid('fetch', mid, '(FLAGS)')[1][0]
131 parsed_flags = imaplib.ParseFlags(fetched_flags)
132 flags_str = " ".join(flag.decode('iso-8859-1') for flag in parsed_flags)
133 result, data = self.mail_con.append(mailbox, flags_str,
134 internal_date_str, data[0][1])
135 logging.debug("Adding corrected copy of the message reponse: %s %s", result, data)
137 raise UserWarning("Could not replace the e-mail" + mid.decode('iso-8859-1') + ".")
139 result, data = self.mail_con.uid('STORE', mid, '+FLAGS', r'(\Deleted)')
140 logging.debug("Removing old copy of the message reponse: %s %s", result, data)
142 raise UserWarning("Could not delete the e-mail" + mid.decode('iso-8859-1') + ".")
143 self.mail_con.expunge()