1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """ This module contains a wrapper around an IMAP Server. It only implements
22 the minimum methods needed to work with UID. You'll have to use the
23 uid command for almost everything, the commands for the "regular" IDs
24 are not supported.
25 """
26
27 STANDARD_IMAPLIB = True
28
29
30
31
32
33
34 IDLE_TIMEOUT = 120
35
36
37 if STANDARD_IMAPLIB:
38 import imaplib
39 IDLE_TIMEOUT = 5
40 else:
41
42
43 import imaplib2 as imaplib
44
45 import time
46 import re
47
48
50 """ Raised if a method is called on a closed mailbox """
51 pass
52
54 """ Raised if a non-existing mailbox is opened """
55 pass
56
58 """ A small lowlevel representation of an imap server
59
60 Public attributes are:
61 servername address of the server
62 username authentication username
63 password authentication password
64 port server port
65 mailboxname currently active mailbox on the server
66 """
67
68 - def __init__(self, servername, username, password, ssl=True, port=None):
69 """ Initialize the IMAP Server, connect and log in.
70 If you leave the port unspecified, the default port will be used.
71 This is port 143 is ssl is disabled, and port 933 if ssl is
72 enabled.
73 """
74 self.servername = servername
75 self.username = username
76 self.password = password
77 self.port = port
78 self.ssl = ssl
79 self._server = None
80 self._flags = {
81 'connected' : False,
82 'logged_in' : False,
83 'open' : False
84 }
85 self.mailboxname = None
86 self.connect()
87 self.login()
88
90 """ Return a new instance of ImapServer that points to the same server,
91 in a connected and authenticated state.
92
93 >>> server1 = ImapServer('localhost','user','secret')
94 >>> server2 = ImapServer('localhost','user','secret')
95 is equivalant to
96 >>> server1 = ImapServer('localhost','user','secret')
97 >>> server2 = server1.clone()
98 """
99 return ImapServer(self.servername, self.username,
100 self.password, self.ssl, self.port)
101
103 """ Connect to servername """
104 if self.ssl:
105 if self.port is None:
106 self.port = 993
107 self._server = imaplib.IMAP4_SSL(self.servername, self.port)
108 else:
109 if self.port is None:
110 self.port = 143
111 self._server = imaplib.IMAP4(self.servername, self.port)
112 self._flags['connected'] = True
113
115 """ Disconnect from the server
116 If looged in, log out
117 """
118 if self._flags['logged_in']:
119 self.logout()
120 self._server = None
121 self._flags['connected'] = False
122
123
125 """ Identify the client using a plaintext password.
126 The password will be quoted.
127 Connect to the server is not connected already. If any problems
128 occur during a login attempt, this method may cause a reconnect
129 to the server.
130 """
131 if not self._flags['connected']:
132 self.connect()
133 if not self._flags['logged_in']:
134 try:
135 result = self._server.login(self.username, self.password)
136 except:
137 self.reconnect()
138 result = self._server.login(self.username, self.password)
139 self._flags['logged_in'] = True
140 return result
141
143 """ Close and then reopen the connection to the server """
144 try:
145 self.disconnect()
146 except:
147 pass
148 self.connect()
149
151 """ Put server into IDLE mode until server notifies some change,
152 or 'timeout' (secs) occurs (default: 29 minutes),
153 or another IMAP4 command is scheduled.
154 """
155 if STANDARD_IMAPLIB:
156 time.sleep(IDLE_TIMEOUT)
157 else:
158 return self._server.idle(timeout)
159
161 """ Create new mailbox """
162 return self._server.create(name)
163
165 """ Delete old mailbox """
166 return self._server.delete(name)
167
169 """ Subscribe to new mailbox """
170 return self._server.subscribe(name)
171
173 """ Unsubscribe from old mailbox """
174 return self._server.unsubscribe(name)
175
177 """ Shutdown connection to server. Returns server "BYE"
178 response.
179 """
180 if self._flags['open']:
181 self.close()
182 self._flags['logged_in'] = False
183 return self._server.logout()
184
185 - def append(self, mailbox, flags, date_time, messagestr):
186 """ Append message to named mailbox. All parameters are strings which
187 need to be in the appropriate format as described in RFC3501"""
188 if not self._flags['open']:
189 raise ClosedMailboxError, "called append on closed mailbox"
190 flags = flags.replace("\\Recent", '')
191 return self._server.append(mailbox, flags, date_time, messagestr)
192
193 - def uid(self, command, *args):
194 """ uid(command, arg[, ...])
195 Execute command with messages identified by UID.
196 Returns response appropriate to command.
197 """
198 if not self._flags['open']:
199 raise ClosedMailboxError, "called uid on closed mailbox"
200 return self._server.uid(command, *args)
201
203 """ Permanently remove deleted items from selected mailbox.
204 Generates an "EXPUNGE" response for each deleted message.
205 Returned data contains a list of "EXPUNGE" message numbers
206 in order received.
207 """
208 if not self._flags['open']:
209 raise ClosedMailboxError, "called expunge on closed mailbox"
210 return self._server.expunge()
211
213 """ Close currently selected mailbox. Deleted messages are
214 removed from writable mailbox. This is the recommended
215 command before "LOGOUT"."""
216 self._flags['open'] = False
217 self.mailboxname = None
218 return self._server.close()
219
220 - def select(self, mailbox = 'INBOX', create=False):
221 """ Select a mailbox. Log in if not logged in already.
222 Return number of messages in mailbox if successful.
223 If the mailbox does not exist, create it if 'create' is True,
224 else raise NoSuchMailboxError.
225 The name of the mailbox will be stored in the mailboxname
226 attribute if selection was successful
227 """
228 if not self._flags['logged_in']:
229 self.login()
230 data = self._server.select(mailbox)
231 code = data[0]
232 count = data[1][0]
233 if code == 'OK':
234 self._flags['open'] = True
235 self.mailboxname = mailbox
236 return int(count)
237 else:
238 if create:
239 self.create(mailbox)
240 self.select(mailbox, create=False)
241 else:
242 raise NoSuchMailboxError, "mailbox %s does not exist." \
243 % mailbox
244
246 """ Return list mailbox names, or None if the server does
247 not send an 'OK' reply.
248 """
249 if not self._flags['logged_in']:
250 raise ClosedMailboxError, "called list before logging in"
251 mailbox_pattern = re.compile(
252 r'\(\\HasNoChildren\) "/" "(?P<mailboxname>.+)"')
253 code, mailboxlist = self._server.list()
254 result = []
255 if code == 'OK':
256 for raw_mailbox in mailboxlist:
257 mailbox_match = mailbox_pattern.match(raw_mailbox)
258 if mailbox_match:
259 result.append(mailbox_match.group('mailboxname'))
260 else:
261 return None
262 return result
263
265 """ List subscribed mailbox names """
266 if not self._flags['logged_in']:
267 raise ClosedMailboxError, "called lsub before logging in"
268 return self._server.lsub()
269
271 """ Equality test:
272 servers are equal if they are equal in servername, username,
273 password, port, and ssl.
274 """
275 return ( (self.servername == other.servername) \
276 and (self.username == other.username) \
277 and (self.password == other.password) \
278 and (self.port == other.port) \
279 and (self.ssl == other.ssl) \
280 )
281
283 """ Inequality test:
284 servers are unequal if they are not equal
285 """
286 return (not (self == other))
287