comparison src/plugins/plugin_misc_imap.py @ 594:e629371a28d3

Fix pep8 support in src/plugins.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Fri, 18 Jan 2013 17:55:35 +0100
parents beaf6bec2fcd
children 84a6e83157c2
comparison
equal deleted inserted replaced
593:70bae685d05c 594:e629371a28d3
19 along with this program. If not, see <http://www.gnu.org/licenses/>. 19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 """ 20 """
21 21
22 from logging import debug, info, error 22 from logging import debug, info, error
23 import warnings 23 import warnings
24 from twisted.internet import protocol,defer 24 from twisted.internet import protocol, defer
25 from twisted.words.protocols.jabber import error as jab_error 25 from twisted.words.protocols.jabber import error as jab_error
26 from twisted.cred import portal,checkers,credentials 26 from twisted.cred import portal, checkers, credentials
27 from twisted.cred import error as cred_error 27 from twisted.cred import error as cred_error
28 from twisted.mail import imap4 28 from twisted.mail import imap4
29 from twisted.python import failure 29 from twisted.python import failure
30 from email.parser import Parser 30 from email.parser import Parser
31 import email.message 31 import email.message
32 import os,os.path 32 import os
33 from cStringIO import StringIO 33 from cStringIO import StringIO
34 from twisted.internet import reactor 34 from twisted.internet import reactor
35 import pdb 35 import pdb
36 36
37
38 from zope.interface import implements 37 from zope.interface import implements
39 38
40
41 PLUGIN_INFO = { 39 PLUGIN_INFO = {
42 "name": "IMAP server Plugin", 40 "name": "IMAP server Plugin",
43 "import_name": "IMAP", 41 "import_name": "IMAP",
44 "type": "Misc", 42 "type": "Misc",
45 "protocols": [], 43 "protocols": [],
46 "dependencies": ["Maildir"], 44 "dependencies": ["Maildir"],
47 "main": "IMAP_server", 45 "main": "IMAP_server",
48 "handler": "no", 46 "handler": "no",
49 "description": _("""Create an Imap server that you can use to read your "normal" type messages""") 47 "description": _("""Create an Imap server that you can use to read your "normal" type messages""")
50 } 48 }
49
51 50
52 class IMAP_server(object): 51 class IMAP_server(object):
53 #TODO: connect profile on mailbox request, once password is accepted 52 #TODO: connect profile on mailbox request, once password is accepted
54 53
55 params = """ 54 params = """
73 info(_("Launching IMAP server on port %d"), port) 72 info(_("Launching IMAP server on port %d"), port)
74 73
75 self.server_factory = ImapServerFactory(self.host) 74 self.server_factory = ImapServerFactory(self.host)
76 reactor.listenTCP(port, self.server_factory) 75 reactor.listenTCP(port, self.server_factory)
77 76
77
78 class Message(object): 78 class Message(object):
79 implements(imap4.IMessage) 79 implements(imap4.IMessage)
80 80
81 def __init__(self, uid, flags, mess_fp): 81 def __init__(self, uid, flags, mess_fp):
82 debug('Message Init') 82 debug('Message Init')
83 self.uid=uid 83 self.uid = uid
84 self.flags=flags 84 self.flags = flags
85 self.mess_fp=mess_fp 85 self.mess_fp = mess_fp
86 self.message=Parser().parse(mess_fp) 86 self.message = Parser().parse(mess_fp)
87 87
88 def getUID(self): 88 def getUID(self):
89 """Retrieve the unique identifier associated with this message. 89 """Retrieve the unique identifier associated with this message.
90 """ 90 """
91 debug('getUID (message)') 91 debug('getUID (message)')
102 """Retrieve the date internally associated with this message. 102 """Retrieve the date internally associated with this message.
103 @return: An RFC822-formatted date string. 103 @return: An RFC822-formatted date string.
104 """ 104 """
105 debug('getInternalDate') 105 debug('getInternalDate')
106 return self.message['Date'] 106 return self.message['Date']
107
108 107
109 def getHeaders(self, negate, *names): 108 def getHeaders(self, negate, *names):
110 """Retrieve a group of message headers. 109 """Retrieve a group of message headers.
111 @param names: The names of the headers to retrieve or omit. 110 @param names: The names of the headers to retrieve or omit.
112 @param negate: If True, indicates that the headers listed in names 111 @param negate: If True, indicates that the headers listed in names
113 should be omitted from the return value, rather than included. 112 should be omitted from the return value, rather than included.
114 @return: A mapping of header field names to header field values 113 @return: A mapping of header field names to header field values
115 """ 114 """
116 debug('getHeaders %s - %s' % (negate, names)) 115 debug('getHeaders %s - %s' % (negate, names))
117 final_dict={} 116 final_dict = {}
118 to_check=[name.lower() for name in names] 117 to_check = [name.lower() for name in names]
119 for header in self.message.keys(): 118 for header in self.message.keys():
120 if (negate and not header.lower() in to_check) or \ 119 if (negate and not header.lower() in to_check) or \
121 (not negate and header.lower() in to_check): 120 (not negate and header.lower() in to_check):
122 final_dict[header]=self.message[header] 121 final_dict[header] = self.message[header]
123 return final_dict 122 return final_dict
124 123
125 def getBodyFile(self): 124 def getBodyFile(self):
126 """Retrieve a file object containing only the body of this message. 125 """Retrieve a file object containing only the body of this message.
127 """ 126 """
130 129
131 def getSize(self): 130 def getSize(self):
132 """Retrieve the total size, in octets, of this message. 131 """Retrieve the total size, in octets, of this message.
133 """ 132 """
134 debug('getSize') 133 debug('getSize')
135 self.mess_fp.seek(0,os.SEEK_END) 134 self.mess_fp.seek(0, os.SEEK_END)
136 return self.mess_fp.tell() 135 return self.mess_fp.tell()
137
138 136
139 def isMultipart(self): 137 def isMultipart(self):
140 """Indicate whether this message has subparts. 138 """Indicate whether this message has subparts.
141 """ 139 """
142 debug('isMultipart') 140 debug('isMultipart')
143 return False 141 return False
144 142
145 def getSubPart(self,part): 143 def getSubPart(self, part):
146 """Retrieve a MIME sub-message 144 """Retrieve a MIME sub-message
147 @param part: The number of the part to retrieve, indexed from 0. 145 @param part: The number of the part to retrieve, indexed from 0.
148 @return: The specified sub-part. 146 @return: The specified sub-part.
149 """ 147 """
150 debug('getSubPart') 148 debug('getSubPart')
152 150
153 151
154 class SatMailbox(object): 152 class SatMailbox(object):
155 implements(imap4.IMailbox) 153 implements(imap4.IMailbox)
156 154
157 def __init__(self,host,name,profile): 155 def __init__(self, host, name, profile):
158 self.host = host 156 self.host = host
159 self.listeners=set() 157 self.listeners = set()
160 debug ('Mailbox init (%s)', name) 158 debug('Mailbox init (%s)', name)
161 if name!="INBOX": 159 if name != "INBOX":
162 raise imap4.MailboxException("Only INBOX is managed for the moment") 160 raise imap4.MailboxException("Only INBOX is managed for the moment")
163 self.mailbox=self.host.plugins["Maildir"].accessMessageBox(name,self.newMessage, profile) 161 self.mailbox = self.host.plugins["Maildir"].accessMessageBox(name, self.newMessage, profile)
164 162
165 def newMessage(self): 163 def newMessage(self):
166 """Called when a new message is in the mailbox""" 164 """Called when a new message is in the mailbox"""
167 debug ("newMessage signal received") 165 debug("newMessage signal received")
168 nb_messages=self.getMessageCount() 166 nb_messages = self.getMessageCount()
169 for listener in self.listeners: 167 for listener in self.listeners:
170 listener.newMessages(nb_messages,None) 168 listener.newMessages(nb_messages, None)
171 169
172 def getUIDValidity(self): 170 def getUIDValidity(self):
173 """Return the unique validity identifier for this mailbox. 171 """Return the unique validity identifier for this mailbox.
174 """ 172 """
175 debug ('getUIDValidity') 173 debug('getUIDValidity')
176 return 0 174 return 0
177 175
178 def getUIDNext(self): 176 def getUIDNext(self):
179 """Return the likely UID for the next message added to this mailbox. 177 """Return the likely UID for the next message added to this mailbox.
180 """ 178 """
181 debug ('getUIDNext') 179 debug('getUIDNext')
182 return self.mailbox.getNextUid() 180 return self.mailbox.getNextUid()
183 181
184 def getUID(self,message): 182 def getUID(self, message):
185 """Return the UID of a message in the mailbox 183 """Return the UID of a message in the mailbox
186 @param message: The message sequence number 184 @param message: The message sequence number
187 @return: The UID of the message. 185 @return: The UID of the message.
188 """ 186 """
189 debug ('getUID (%i)' % message) 187 debug('getUID (%i)' % message)
190 #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number 188 #return self.mailbox.getUid(message-1) #XXX: it seems that this method get uid and not message sequence number
191 return message 189 return message
192 190
193 def getMessageCount(self): 191 def getMessageCount(self):
194 """Return the number of messages in this mailbox. 192 """Return the number of messages in this mailbox.
206 204
207 def getUnseenCount(self): 205 def getUnseenCount(self):
208 """Return the number of messages with the 'Unseen' flag. 206 """Return the number of messages with the 'Unseen' flag.
209 """ 207 """
210 debug('getUnseenCount') 208 debug('getUnseenCount')
211 return self.getMessageCount()-len(self.mailbox.getMessageIdsWithFlag('\\SEEN')) 209 return self.getMessageCount() - len(self.mailbox.getMessageIdsWithFlag('\\SEEN'))
212 210
213 def isWriteable(self): 211 def isWriteable(self):
214 """Get the read/write status of the mailbox. 212 """Get the read/write status of the mailbox.
215 @return: A true value if write permission is allowed, a false value otherwise. 213 @return: A true value if write permission is allowed, a false value otherwise.
216 """ 214 """
219 217
220 def destroy(self): 218 def destroy(self):
221 """Called before this mailbox is deleted, permanently. 219 """Called before this mailbox is deleted, permanently.
222 """ 220 """
223 debug('destroy') 221 debug('destroy')
224
225 222
226 def requestStatus(self, names): 223 def requestStatus(self, names):
227 """Return status information about this mailbox. 224 """Return status information about this mailbox.
228 @param names: The status names to return information regarding. 225 @param names: The status names to return information regarding.
229 The possible values for each name are: MESSAGES, RECENT, UIDNEXT, 226 The possible values for each name are: MESSAGES, RECENT, UIDNEXT,
260 if listener in self.listeners: 257 if listener in self.listeners:
261 self.listeners.remove(listener) 258 self.listeners.remove(listener)
262 else: 259 else:
263 raise imap4.MailboxException('Trying to remove an unknown listener') 260 raise imap4.MailboxException('Trying to remove an unknown listener')
264 261
265 def addMessage(self, message, flags = (), date = None): 262 def addMessage(self, message, flags=(), date=None):
266 """Add the given message to this mailbox. 263 """Add the given message to this mailbox.
267 @param message: The RFC822 formatted message 264 @param message: The RFC822 formatted message
268 @param flags: The flags to associate with this message 265 @param flags: The flags to associate with this message
269 @param date: If specified, the date to associate with this 266 @param date: If specified, the date to associate with this
270 @return: A deferred whose callback is invoked with the message 267 @return: A deferred whose callback is invoked with the message
286 """Retrieve one or more messages. 283 """Retrieve one or more messages.
287 @param messages: The identifiers of messages to retrieve information 284 @param messages: The identifiers of messages to retrieve information
288 about 285 about
289 @param uid: If true, the IDs specified in the query are UIDs; 286 @param uid: If true, the IDs specified in the query are UIDs;
290 """ 287 """
291 debug('fetch (%s, %s)'%(messages,uid)) 288 debug('fetch (%s, %s)' % (messages, uid))
292 if uid: 289 if uid:
293 messages.last = self.mailbox.getMaxUid() 290 messages.last = self.mailbox.getMaxUid()
294 messages.getnext = self.mailbox.getNextExistingUid 291 messages.getnext = self.mailbox.getNextExistingUid
295 for mess_uid in messages: 292 for mess_uid in messages:
296 if mess_uid == None: 293 if mess_uid is None:
297 debug ('stopping iteration') 294 debug('stopping iteration')
298 raise StopIteration 295 raise StopIteration
299 try: 296 try:
300 yield (mess_uid,Message(mess_uid,self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid))) 297 yield (mess_uid, Message(mess_uid, self.mailbox.getFlagsUid(mess_uid), self.mailbox.getMessageUid(mess_uid)))
301 except IndexError: 298 except IndexError:
302 continue 299 continue
303 else: 300 else:
304 messages.last = self.getMessageCount() 301 messages.last = self.getMessageCount()
305 for mess_idx in messages: 302 for mess_idx in messages:
306 if mess_idx>self.getMessageCount(): 303 if mess_idx > self.getMessageCount():
307 raise StopIteration 304 raise StopIteration
308 yield (mess_idx,Message(mess_idx,self.mailbox.getFlags(mess_idx),self.mailbox.getMessage(mess_idx-1))) 305 yield (mess_idx, Message(mess_idx, self.mailbox.getFlags(mess_idx), self.mailbox.getMessage(mess_idx - 1)))
309 306
310 def store(self, messages, flags, mode, uid): 307 def store(self, messages, flags, mode, uid):
311 """Set the flags of one or more messages. 308 """Set the flags of one or more messages.
312 @param messages: The identifiers of the messages to set the flags of. 309 @param messages: The identifiers of the messages to set the flags of.
313 @param flags: The flags to set, unset, or add. 310 @param flags: The flags to set, unset, or add.
322 been performed, or a Deferred whose callback will be invoked with 319 been performed, or a Deferred whose callback will be invoked with
323 such a dict. 320 such a dict.
324 """ 321 """
325 debug('store') 322 debug('store')
326 323
327 flags=[flag.upper() for flag in flags] 324 flags = [flag.upper() for flag in flags]
328 325
329 def updateFlags(getF,setF): 326 def updateFlags(getF, setF):
330 ret = {} 327 ret = {}
331 for mess_id in messages: 328 for mess_id in messages:
332 if (uid and mess_id == None) or (not uid and mess_id>self.getMessageCount()): 329 if (uid and mess_id is None) or (not uid and mess_id > self.getMessageCount()):
333 break 330 break
334 _flags=set(getF(mess_id) if mode else []) 331 _flags = set(getF(mess_id) if mode else [])
335 if mode==-1: 332 if mode == -1:
336 _flags.difference_update(set(flags)) 333 _flags.difference_update(set(flags))
337 else: 334 else:
338 _flags.update(set(flags)) 335 _flags.update(set(flags))
339 new_flags=list(_flags) 336 new_flags = list(_flags)
340 setF(mess_id, new_flags) 337 setF(mess_id, new_flags)
341 ret[mess_id] = tuple(new_flags) 338 ret[mess_id] = tuple(new_flags)
342 return ret 339 return ret
343 340
344 if uid: 341 if uid:
345 messages.last = self.mailbox.getMaxUid() 342 messages.last = self.mailbox.getMaxUid()
346 messages.getnext = self.mailbox.getNextExistingUid 343 messages.getnext = self.mailbox.getNextExistingUid
347 ret = updateFlags(self.mailbox.getFlagsUid,self.mailbox.setFlagsUid) 344 ret = updateFlags(self.mailbox.getFlagsUid, self.mailbox.setFlagsUid)
348 for listener in self.listeners: 345 for listener in self.listeners:
349 listener.flagsChanged(ret) 346 listener.flagsChanged(ret)
350 return ret 347 return ret
351 348
352 else: 349 else:
353 messages.last = self.getMessageCount() 350 messages.last = self.getMessageCount()
354 ret = updateFlags(self.mailbox.getFlags,self.mailbox.setFlags) 351 ret = updateFlags(self.mailbox.getFlags, self.mailbox.setFlags)
355 newFlags={} 352 newFlags = {}
356 for idx in ret: 353 for idx in ret:
357 #we have to convert idx to uid for the listeners 354 #we have to convert idx to uid for the listeners
358 newFlags[self.mailbox.getUid(idx)] = ret[idx] 355 newFlags[self.mailbox.getUid(idx)] = ret[idx]
359 for listener in self.listeners: 356 for listener in self.listeners:
360 listener.flagsChanged(newFlags) 357 listener.flagsChanged(newFlags)
364 """Return the flags defined in this mailbox 361 """Return the flags defined in this mailbox
365 Flags with the \\ prefix are reserved for use as system flags. 362 Flags with the \\ prefix are reserved for use as system flags.
366 @return: A list of the flags that can be set on messages in this mailbox. 363 @return: A list of the flags that can be set on messages in this mailbox.
367 """ 364 """
368 debug('getFlags') 365 debug('getFlags')
369 return ['\\SEEN','\\ANSWERED','\\FLAGGED','\\DELETED','\\DRAFT'] #TODO: add '\\RECENT' 366 return ['\\SEEN', '\\ANSWERED', '\\FLAGGED', '\\DELETED', '\\DRAFT'] # TODO: add '\\RECENT'
370 367
371 def getHierarchicalDelimiter(self): 368 def getHierarchicalDelimiter(self):
372 """Get the character which delimits namespaces for in this mailbox. 369 """Get the character which delimits namespaces for in this mailbox.
373 """ 370 """
374 debug('getHierarchicalDelimiter') 371 debug('getHierarchicalDelimiter')
375 return '.' 372 return '.'
376 373
374
377 class ImapSatAccount(imap4.MemoryAccount): 375 class ImapSatAccount(imap4.MemoryAccount):
378 #implements(imap4.IAccount) 376 #implements(imap4.IAccount)
379 377
380 def __init__(self, host, profile): 378 def __init__(self, host, profile):
381 debug("ImapAccount init") 379 debug("ImapAccount init")
382 self.host=host 380 self.host = host
383 self.profile=profile 381 self.profile = profile
384 imap4.MemoryAccount.__init__(self,profile) 382 imap4.MemoryAccount.__init__(self, profile)
385 self.addMailbox("Inbox") #We only manage Inbox for the moment 383 self.addMailbox("Inbox") # We only manage Inbox for the moment
386 debug ('INBOX added') 384 debug('INBOX added')
387 385
388 def _emptyMailbox(self, name, id): 386 def _emptyMailbox(self, name, id):
389 return SatMailbox(self.host,name,self.profile) 387 return SatMailbox(self.host, name, self.profile)
390 388
391 389
392 class ImapRealm(object): 390 class ImapRealm(object):
393 implements(portal.IRealm) 391 implements(portal.IRealm)
394 392
395 def __init__(self,host): 393 def __init__(self, host):
396 self.host = host 394 self.host = host
397 395
398 def requestAvatar(self, avatarID, mind, *interfaces): 396 def requestAvatar(self, avatarID, mind, *interfaces):
399 debug('requestAvatar') 397 debug('requestAvatar')
400 profile=avatarID.decode('utf-8') 398 profile = avatarID.decode('utf-8')
401 if imap4.IAccount not in interfaces: 399 if imap4.IAccount not in interfaces:
402 raise NotImplementedError 400 raise NotImplementedError
403 return imap4.IAccount, ImapSatAccount(self.host,profile), lambda:None 401 return imap4.IAccount, ImapSatAccount(self.host, profile), lambda: None
402
404 403
405 class SatProfileCredentialChecker(object): 404 class SatProfileCredentialChecker(object):
406 """ 405 """
407 This credential checker check against SàT's profile and associated jabber's password 406 This credential checker check against SàT's profile and associated jabber's password
408 Check if the profile exists, and if the password is OK 407 Check if the profile exists, and if the password is OK
410 """ 409 """
411 implements(checkers.ICredentialsChecker) 410 implements(checkers.ICredentialsChecker)
412 credentialInterfaces = (credentials.IUsernamePassword, 411 credentialInterfaces = (credentials.IUsernamePassword,
413 credentials.IUsernameHashedPassword) 412 credentials.IUsernameHashedPassword)
414 413
415
416 def __init__(self, host): 414 def __init__(self, host):
417 self.host = host 415 self.host = host
418 416
419 def _cbPasswordMatch(self, matched, profile): 417 def _cbPasswordMatch(self, matched, profile):
420 if matched: 418 if matched:
429 d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username) 427 d = self.host.memory.asyncGetParamA("Password", "Connection", profile_key=credentials.username)
430 d.addCallback(lambda password: credentials.checkPassword(password)) 428 d.addCallback(lambda password: credentials.checkPassword(password))
431 d.addCallback(self._cbPasswordMatch, credentials.username) 429 d.addCallback(self._cbPasswordMatch, credentials.username)
432 return d 430 return d
433 431
432
434 class ImapServerFactory(protocol.ServerFactory): 433 class ImapServerFactory(protocol.ServerFactory):
435 protocol = imap4.IMAP4Server 434 protocol = imap4.IMAP4Server
436 435
437 def __init__(self, host): 436 def __init__(self, host):
438 self.host=host 437 self.host = host
439 438
440 def startedConnecting(self, connector): 439 def startedConnecting(self, connector):
441 debug (_("IMAP server connection started")) 440 debug(_("IMAP server connection started"))
442 441
443 def clientConnectionLost(self, connector, reason): 442 def clientConnectionLost(self, connector, reason):
444 debug (_("IMAP server connection lost (reason: %s)"), reason) 443 debug(_("IMAP server connection lost (reason: %s)"), reason)
445 444
446 def buildProtocol(self, addr): 445 def buildProtocol(self, addr):
447 debug ("Building protocol") 446 debug("Building protocol")
448 prot = protocol.ServerFactory.buildProtocol(self, addr) 447 prot = protocol.ServerFactory.buildProtocol(self, addr)
449 prot.portal = portal.Portal(ImapRealm(self.host)) 448 prot.portal = portal.Portal(ImapRealm(self.host))
450 prot.portal.registerChecker(SatProfileCredentialChecker(self.host)) 449 prot.portal.registerChecker(SatProfileCredentialChecker(self.host))
451 return prot 450 return prot