11199451d1682114815a93bda4ade7d8b74e00f0
[imap-fix-internaldate] / mail_iterator.py
1 '''
2 mail_iterator.py - The module contains the MailIterator class.
3
4 Copyright (c) 2012 Intra2net AG
5 Author: Plamen Dimitrov
6
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.
11
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.
16
17 Add '-t' argument when running the module for a test mode.
18 For a detailed list of each message with a date conflict change
19 the 'log_level' in the configuration file from '30' to '20'.
20 '''
21
22 import imaplib
23 import re
24 import time
25
26 MAILBOX_RESP = re.compile(r'\((?P<flags>.*?)\) "(?P<delimiter>.*)" (?P<name>.*)')
27 UIDVAL_RESP = re.compile(r'(?P<name>.*) \(UIDVALIDITY (?P<uidval>.*)\)')
28
29 class MailIterator:
30     """This class communicates with the e-mail server."""
31
32     def __init__(self, server, username, password):
33         """Creates a connection and a user session."""
34         self.mail_con = imaplib.IMAP4_SSL(server)
35         result, data = self.mail_con.login(username, password)
36         if(result!="OK"):
37             raise UserWarning("Could not log in as user " + username + ". " + data)
38         result, self.mailboxes = self.mail_con.list()
39         if(result!="OK"):
40             raise UserWarning("Could not retrieve mailboxes for user " + username + ".")
41
42     def __del__(self):
43         """Closes the connection and the user session."""
44         self.mail_con.close()
45         self.mail_con.logout()
46
47     def __iter__(self):
48         """Iterates through all mailboxes, returns (uidval,name)."""
49         for mailbox in self.mailboxes:
50             #print("Checking mailbox ", mailbox)
51             mailbox = MAILBOX_RESP.match(mailbox.decode("utf-8")).groups()
52             result, data = self.mail_con.status(mailbox[2], '(UIDVALIDITY)')
53             if(result!="OK"):
54                 raise UserWarning("Could not retrieve mailbox uidvalidity.")
55             uidval = UIDVAL_RESP.match(data[0].decode("utf-8")).groups()
56             #print(data[0], uidval)
57             self.mail_con.select(mailbox[2])
58             yield (mailbox[2], uidval[1])
59
60     def fetch_messages(self):
61         """Fetches the messages from the current mailbox, return list of uids."""
62         result, data = self.mail_con.uid('search', None, "ALL")
63         if(result!="OK"):
64             raise UserWarning("Could not fetch messages.")
65         #print("E-mail list for user ", row['username'], " is ", data[0])
66         mailid_list = data[0].split()
67         return mailid_list
68
69     def fetch_internal_date(self, mid):
70         """Fetches the internal date of a message, returns a time tuple."""
71         result, data = self.mail_con.uid('fetch', mid, '(INTERNALDATE)')
72         if(result!="OK"):
73             raise UserWarning("Could not fetch the internal date of message" + mid + ".")
74         internal_date = imaplib.Internaldate2tuple(data[0])
75         return internal_date
76
77     def fetch_received_date(self, mid):
78         """Fetches the received date of a message, returns bytes reponse."""
79         result, data = self.mail_con.uid('fetch', mid, '(BODY.PEEK[HEADER.FIELDS (RECEIVED)])')
80         if(result!="OK"):
81             raise UserWarning("Could not fetch the received header of message" + mid + ".")
82         return data
83
84     def update_message(self, mid, mailbox, internal_date):
85         """Replaces a message with one with correct internal date."""
86         internal_date_seconds = time.mktime(internal_date.timetuple())
87         internal_date_str = imaplib.Time2Internaldate(internal_date_seconds)
88         result, data = self.mail_con.uid('fetch', mid, '(RFC822)')
89         if(result!="OK"):
90             raise UserWarning("Could not retrieve the entire e-mail" + mid + ".")
91         #print("Entire e-mail is: ", data[0][1])
92
93         fetched_flags = self.mail_con.uid('fetch', mid, '(FLAGS)')[1][0]
94         parsed_flags = imaplib.ParseFlags(fetched_flags)
95         flags_str = " ".join(flag.decode("utf-8") for flag in parsed_flags)
96         result, data = self.mail_con.append(mailbox, flags_str,
97                                             internal_date_str, data[0][1])
98         #print(result, data)
99         if(result!="OK"):
100             raise UserWarning("Could not replace the e-mail" + mid + ".")
101         else:
102             result, data = self.mail_con.uid('STORE', mid, '+FLAGS', r'(\Deleted)')
103             if(result!="OK"):
104                 raise UserWarning("Could not delete the e-mail" + mid + ".")
105             else: self.mail_con.expunge()
106         return