Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(328)

Side by Side Diff: third_party/twisted_8_1/twisted/mail/pop3.py

Issue 12261012: Remove third_party/twisted_8_1 (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 # -*- test-case-name: twisted.mail.test.test_pop3 -*-
2 #
3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories.
4 # See LICENSE for details.
5
6
7 """
8 Post-office Protocol version 3
9
10 @author: U{Glyph Lefkowitz<mailto:glyph@twistedmatrix.com>}
11 @author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>}
12 """
13
14 import string
15 import base64
16 import binascii
17 import md5
18 import warnings
19
20 from zope.interface import implements, Interface
21
22 from twisted.mail import smtp
23 from twisted.protocols import basic
24 from twisted.protocols import policies
25 from twisted.internet import task
26 from twisted.internet import defer
27 from twisted.internet import interfaces
28 from twisted.python import log
29
30 from twisted import cred
31 import twisted.cred.error
32 import twisted.cred.credentials
33
34 ##
35 ## Authentication
36 ##
37 class APOPCredentials:
38 implements(cred.credentials.IUsernamePassword)
39
40 def __init__(self, magic, username, digest):
41 self.magic = magic
42 self.username = username
43 self.digest = digest
44
45 def checkPassword(self, password):
46 seed = self.magic + password
47 myDigest = md5.new(seed).hexdigest()
48 return myDigest == self.digest
49
50
51 class _HeadersPlusNLines:
52 def __init__(self, f, n):
53 self.f = f
54 self.n = n
55 self.linecount = 0
56 self.headers = 1
57 self.done = 0
58 self.buf = ''
59
60 def read(self, bytes):
61 if self.done:
62 return ''
63 data = self.f.read(bytes)
64 if not data:
65 return data
66 if self.headers:
67 df, sz = data.find('\r\n\r\n'), 4
68 if df == -1:
69 df, sz = data.find('\n\n'), 2
70 if df != -1:
71 df += sz
72 val = data[:df]
73 data = data[df:]
74 self.linecount = 1
75 self.headers = 0
76 else:
77 val = ''
78 if self.linecount > 0:
79 dsplit = (self.buf+data).split('\n')
80 self.buf = dsplit[-1]
81 for ln in dsplit[:-1]:
82 if self.linecount > self.n:
83 self.done = 1
84 return val
85 val += (ln + '\n')
86 self.linecount += 1
87 return val
88 else:
89 return data
90
91
92
93 class _POP3MessageDeleted(Exception):
94 """
95 Internal control-flow exception. Indicates the file of a deleted message
96 was requested.
97 """
98
99
100 class POP3Error(Exception):
101 pass
102
103
104
105 class _IteratorBuffer(object):
106 bufSize = 0
107
108 def __init__(self, write, iterable, memoryBufferSize=None):
109 """
110 Create a _IteratorBuffer.
111
112 @param write: A one-argument callable which will be invoked with a list
113 of strings which have been buffered.
114
115 @param iterable: The source of input strings as any iterable.
116
117 @param memoryBufferSize: The upper limit on buffered string length,
118 beyond which the buffer will be flushed to the writer.
119 """
120 self.lines = []
121 self.write = write
122 self.iterator = iter(iterable)
123 if memoryBufferSize is None:
124 memoryBufferSize = 2 ** 16
125 self.memoryBufferSize = memoryBufferSize
126
127
128 def __iter__(self):
129 return self
130
131
132 def next(self):
133 try:
134 v = self.iterator.next()
135 except StopIteration:
136 if self.lines:
137 self.write(self.lines)
138 # Drop some references, in case they're edges in a cycle.
139 del self.iterator, self.lines, self.write
140 raise
141 else:
142 if v is not None:
143 self.lines.append(v)
144 self.bufSize += len(v)
145 if self.bufSize > self.memoryBufferSize:
146 self.write(self.lines)
147 self.lines = []
148 self.bufSize = 0
149
150
151
152 def iterateLineGenerator(proto, gen):
153 """
154 Hook the given protocol instance up to the given iterator with an
155 _IteratorBuffer and schedule the result to be exhausted via the protocol.
156
157 @type proto: L{POP3}
158 @type gen: iterator
159 @rtype: L{twisted.internet.defer.Deferred}
160 """
161 coll = _IteratorBuffer(proto.transport.writeSequence, gen)
162 return proto.schedule(coll)
163
164
165
166 def successResponse(response):
167 """
168 Format the given object as a positive response.
169 """
170 response = str(response)
171 return '+OK %s\r\n' % (response,)
172
173
174
175 def formatStatResponse(msgs):
176 """
177 Format the list of message sizes appropriately for a STAT response.
178
179 Yields None until it finishes computing a result, then yields a str
180 instance that is suitable for use as a response to the STAT command.
181 Intended to be used with a L{twisted.internet.task.Cooperator}.
182 """
183 i = 0
184 bytes = 0
185 for size in msgs:
186 i += 1
187 bytes += size
188 yield None
189 yield successResponse('%d %d' % (i, bytes))
190
191
192
193 def formatListLines(msgs):
194 """
195 Format a list of message sizes appropriately for the lines of a LIST
196 response.
197
198 Yields str instances formatted appropriately for use as lines in the
199 response to the LIST command. Does not include the trailing '.'.
200 """
201 i = 0
202 for size in msgs:
203 i += 1
204 yield '%d %d\r\n' % (i, size)
205
206
207
208 def formatListResponse(msgs):
209 """
210 Format a list of message sizes appropriately for a complete LIST response.
211
212 Yields str instances formatted appropriately for use as a LIST command
213 response.
214 """
215 yield successResponse(len(msgs))
216 for ele in formatListLines(msgs):
217 yield ele
218 yield '.\r\n'
219
220
221
222 def formatUIDListLines(msgs, getUidl):
223 """
224 Format the list of message sizes appropriately for the lines of a UIDL
225 response.
226
227 Yields str instances formatted appropriately for use as lines in the
228 response to the UIDL command. Does not include the trailing '.'.
229 """
230 for i, m in enumerate(msgs):
231 if m is not None:
232 uid = getUidl(i)
233 yield '%d %s\r\n' % (i + 1, uid)
234
235
236
237 def formatUIDListResponse(msgs, getUidl):
238 """
239 Format a list of message sizes appropriately for a complete UIDL response.
240
241 Yields str instances formatted appropriately for use as a UIDL command
242 response.
243 """
244 yield successResponse('')
245 for ele in formatUIDListLines(msgs, getUidl):
246 yield ele
247 yield '.\r\n'
248
249
250
251 class POP3(basic.LineOnlyReceiver, policies.TimeoutMixin):
252 """
253 POP3 server protocol implementation.
254
255 @ivar portal: A reference to the L{twisted.cred.portal.Portal} instance we
256 will authenticate through.
257
258 @ivar factory: A L{twisted.mail.pop3.IServerFactory} which will be used to
259 determine some extended behavior of the server.
260
261 @ivar timeOut: An integer which defines the minimum amount of time which
262 may elapse without receiving any traffic after which the client will be
263 disconnected.
264
265 @ivar schedule: A one-argument callable which should behave like
266 L{twisted.internet.task.coiterate}.
267 """
268 implements(interfaces.IProducer)
269
270 magic = None
271 _userIs = None
272 _onLogout = None
273
274 AUTH_CMDS = ['CAPA', 'USER', 'PASS', 'APOP', 'AUTH', 'RPOP', 'QUIT']
275
276 portal = None
277 factory = None
278
279 # The mailbox we're serving
280 mbox = None
281
282 # Set this pretty low -- POP3 clients are expected to log in, download
283 # everything, and log out.
284 timeOut = 300
285
286 # Current protocol state
287 state = "COMMAND"
288
289 # PIPELINE
290 blocked = None
291
292 # Cooperate and suchlike.
293 schedule = staticmethod(task.coiterate)
294
295 # Message index of the highest retrieved message.
296 _highest = 0
297
298 def connectionMade(self):
299 if self.magic is None:
300 self.magic = self.generateMagic()
301 self.successResponse(self.magic)
302 self.setTimeout(self.timeOut)
303 if getattr(self.factory, 'noisy', True):
304 log.msg("New connection from " + str(self.transport.getPeer()))
305
306
307 def connectionLost(self, reason):
308 if self._onLogout is not None:
309 self._onLogout()
310 self._onLogout = None
311 self.setTimeout(None)
312
313
314 def generateMagic(self):
315 return smtp.messageid()
316
317
318 def successResponse(self, message=''):
319 self.transport.write(successResponse(message))
320
321 def failResponse(self, message=''):
322 self.sendLine('-ERR ' + str(message))
323
324 # def sendLine(self, line):
325 # print 'S:', repr(line)
326 # basic.LineOnlyReceiver.sendLine(self, line)
327
328 def lineReceived(self, line):
329 # print 'C:', repr(line)
330 self.resetTimeout()
331 getattr(self, 'state_' + self.state)(line)
332
333 def _unblock(self, _):
334 commands = self.blocked
335 self.blocked = None
336 while commands and self.blocked is None:
337 cmd, args = commands.pop(0)
338 self.processCommand(cmd, *args)
339 if self.blocked is not None:
340 self.blocked.extend(commands)
341
342 def state_COMMAND(self, line):
343 try:
344 return self.processCommand(*line.split(' '))
345 except (ValueError, AttributeError, POP3Error, TypeError), e:
346 log.err()
347 self.failResponse('bad protocol or server: %s: %s' % (e.__class__.__ name__, e))
348
349 def processCommand(self, command, *args):
350 if self.blocked is not None:
351 self.blocked.append((command, args))
352 return
353
354 command = string.upper(command)
355 authCmd = command in self.AUTH_CMDS
356 if not self.mbox and not authCmd:
357 raise POP3Error("not authenticated yet: cannot do " + command)
358 f = getattr(self, 'do_' + command, None)
359 if f:
360 return f(*args)
361 raise POP3Error("Unknown protocol command: " + command)
362
363
364 def listCapabilities(self):
365 baseCaps = [
366 "TOP",
367 "USER",
368 "UIDL",
369 "PIPELINE",
370 "CELERITY",
371 "AUSPEX",
372 "POTENCE",
373 ]
374
375 if IServerFactory.providedBy(self.factory):
376 # Oh my god. We can't just loop over a list of these because
377 # each has spectacularly different return value semantics!
378 try:
379 v = self.factory.cap_IMPLEMENTATION()
380 except NotImplementedError:
381 pass
382 except:
383 log.err()
384 else:
385 baseCaps.append("IMPLEMENTATION " + str(v))
386
387 try:
388 v = self.factory.cap_EXPIRE()
389 except NotImplementedError:
390 pass
391 except:
392 log.err()
393 else:
394 if v is None:
395 v = "NEVER"
396 if self.factory.perUserExpiration():
397 if self.mbox:
398 v = str(self.mbox.messageExpiration)
399 else:
400 v = str(v) + " USER"
401 v = str(v)
402 baseCaps.append("EXPIRE " + v)
403
404 try:
405 v = self.factory.cap_LOGIN_DELAY()
406 except NotImplementedError:
407 pass
408 except:
409 log.err()
410 else:
411 if self.factory.perUserLoginDelay():
412 if self.mbox:
413 v = str(self.mbox.loginDelay)
414 else:
415 v = str(v) + " USER"
416 v = str(v)
417 baseCaps.append("LOGIN-DELAY " + v)
418
419 try:
420 v = self.factory.challengers
421 except AttributeError:
422 pass
423 except:
424 log.err()
425 else:
426 baseCaps.append("SASL " + ' '.join(v.keys()))
427 return baseCaps
428
429 def do_CAPA(self):
430 self.successResponse("I can do the following:")
431 for cap in self.listCapabilities():
432 self.sendLine(cap)
433 self.sendLine(".")
434
435 def do_AUTH(self, args=None):
436 if not getattr(self.factory, 'challengers', None):
437 self.failResponse("AUTH extension unsupported")
438 return
439
440 if args is None:
441 self.successResponse("Supported authentication methods:")
442 for a in self.factory.challengers:
443 self.sendLine(a.upper())
444 self.sendLine(".")
445 return
446
447 auth = self.factory.challengers.get(args.strip().upper())
448 if not self.portal or not auth:
449 self.failResponse("Unsupported SASL selected")
450 return
451
452 self._auth = auth()
453 chal = self._auth.getChallenge()
454
455 self.sendLine('+ ' + base64.encodestring(chal).rstrip('\n'))
456 self.state = 'AUTH'
457
458 def state_AUTH(self, line):
459 self.state = "COMMAND"
460 try:
461 parts = base64.decodestring(line).split(None, 1)
462 except binascii.Error:
463 self.failResponse("Invalid BASE64 encoding")
464 else:
465 if len(parts) != 2:
466 self.failResponse("Invalid AUTH response")
467 return
468 self._auth.username = parts[0]
469 self._auth.response = parts[1]
470 d = self.portal.login(self._auth, None, IMailbox)
471 d.addCallback(self._cbMailbox, parts[0])
472 d.addErrback(self._ebMailbox)
473 d.addErrback(self._ebUnexpected)
474
475 def do_APOP(self, user, digest):
476 d = defer.maybeDeferred(self.authenticateUserAPOP, user, digest)
477 d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,)
478 ).addErrback(self._ebUnexpected)
479
480 def _cbMailbox(self, (interface, avatar, logout), user):
481 if interface is not IMailbox:
482 self.failResponse('Authentication failed')
483 log.err("_cbMailbox() called with an interface other than IMailbox")
484 return
485
486 self.mbox = avatar
487 self._onLogout = logout
488 self.successResponse('Authentication succeeded')
489 if getattr(self.factory, 'noisy', True):
490 log.msg("Authenticated login for " + user)
491
492 def _ebMailbox(self, failure):
493 failure = failure.trap(cred.error.LoginDenied, cred.error.LoginFailed)
494 if issubclass(failure, cred.error.LoginDenied):
495 self.failResponse("Access denied: " + str(failure))
496 elif issubclass(failure, cred.error.LoginFailed):
497 self.failResponse('Authentication failed')
498 if getattr(self.factory, 'noisy', True):
499 log.msg("Denied login attempt from " + str(self.transport.getPeer()) )
500
501 def _ebUnexpected(self, failure):
502 self.failResponse('Server error: ' + failure.getErrorMessage())
503 log.err(failure)
504
505 def do_USER(self, user):
506 self._userIs = user
507 self.successResponse('USER accepted, send PASS')
508
509 def do_PASS(self, password):
510 if self._userIs is None:
511 self.failResponse("USER required before PASS")
512 return
513 user = self._userIs
514 self._userIs = None
515 d = defer.maybeDeferred(self.authenticateUserPASS, user, password)
516 d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,)
517 ).addErrback(self._ebUnexpected)
518
519
520 def _longOperation(self, d):
521 # Turn off timeouts and block further processing until the Deferred
522 # fires, then reverse those changes.
523 timeOut = self.timeOut
524 self.setTimeout(None)
525 self.blocked = []
526 d.addCallback(self._unblock)
527 d.addCallback(lambda ign: self.setTimeout(timeOut))
528 return d
529
530
531 def _coiterate(self, gen):
532 return self.schedule(_IteratorBuffer(self.transport.writeSequence, gen))
533
534
535 def do_STAT(self):
536 d = defer.maybeDeferred(self.mbox.listMessages)
537 def cbMessages(msgs):
538 return self._coiterate(formatStatResponse(msgs))
539 def ebMessages(err):
540 self.failResponse(err.getErrorMessage())
541 log.msg("Unexpected do_STAT failure:")
542 log.err(err)
543 return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
544
545
546 def do_LIST(self, i=None):
547 if i is None:
548 d = defer.maybeDeferred(self.mbox.listMessages)
549 def cbMessages(msgs):
550 return self._coiterate(formatListResponse(msgs))
551 def ebMessages(err):
552 self.failResponse(err.getErrorMessage())
553 log.msg("Unexpected do_LIST failure:")
554 log.err(err)
555 return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
556 else:
557 try:
558 i = int(i)
559 if i < 1:
560 raise ValueError()
561 except ValueError:
562 self.failResponse("Invalid message-number: %r" % (i,))
563 else:
564 d = defer.maybeDeferred(self.mbox.listMessages, i - 1)
565 def cbMessage(msg):
566 self.successResponse('%d %d' % (i, msg))
567 def ebMessage(err):
568 errcls = err.check(ValueError, IndexError)
569 if errcls is not None:
570 if errcls is IndexError:
571 # IndexError was supported for a while, but really
572 # shouldn't be. One error condition, one exception
573 # type.
574 warnings.warn(
575 "twisted.mail.pop3.IMailbox.listMessages may not "
576 "raise IndexError for out-of-bounds message numb ers: "
577 "raise ValueError instead.",
578 PendingDeprecationWarning)
579 self.failResponse("Invalid message-number: %r" % (i,))
580 else:
581 self.failResponse(err.getErrorMessage())
582 log.msg("Unexpected do_LIST failure:")
583 log.err(err)
584 return self._longOperation(d.addCallbacks(cbMessage, ebMessage))
585
586
587 def do_UIDL(self, i=None):
588 if i is None:
589 d = defer.maybeDeferred(self.mbox.listMessages)
590 def cbMessages(msgs):
591 return self._coiterate(formatUIDListResponse(msgs, self.mbox.get Uidl))
592 def ebMessages(err):
593 self.failResponse(err.getErrorMessage())
594 log.msg("Unexpected do_UIDL failure:")
595 log.err(err)
596 return self._longOperation(d.addCallbacks(cbMessages, ebMessages))
597 else:
598 try:
599 i = int(i)
600 if i < 1:
601 raise ValueError()
602 except ValueError:
603 self.failResponse("Bad message number argument")
604 else:
605 try:
606 msg = self.mbox.getUidl(i - 1)
607 except IndexError:
608 # XXX TODO See above comment regarding IndexError.
609 warnings.warn(
610 "twisted.mail.pop3.IMailbox.getUidl may not "
611 "raise IndexError for out-of-bounds message numbers: "
612 "raise ValueError instead.",
613 PendingDeprecationWarning)
614 self.failResponse("Bad message number argument")
615 except ValueError:
616 self.failResponse("Bad message number argument")
617 else:
618 self.successResponse(str(msg))
619
620
621 def _getMessageFile(self, i):
622 """
623 Retrieve the size and contents of a given message, as a two-tuple.
624
625 @param i: The number of the message to operate on. This is a base-ten
626 string representation starting at 1.
627
628 @return: A Deferred which fires with a two-tuple of an integer and a
629 file-like object.
630 """
631 try:
632 msg = int(i) - 1
633 if msg < 0:
634 raise ValueError()
635 except ValueError:
636 self.failResponse("Bad message number argument")
637 return defer.succeed(None)
638
639 sizeDeferred = defer.maybeDeferred(self.mbox.listMessages, msg)
640 def cbMessageSize(size):
641 if not size:
642 return defer.fail(_POP3MessageDeleted())
643 fileDeferred = defer.maybeDeferred(self.mbox.getMessage, msg)
644 fileDeferred.addCallback(lambda fObj: (size, fObj))
645 return fileDeferred
646
647 def ebMessageSomething(err):
648 errcls = err.check(_POP3MessageDeleted, ValueError, IndexError)
649 if errcls is _POP3MessageDeleted:
650 self.failResponse("message deleted")
651 elif errcls in (ValueError, IndexError):
652 if errcls is IndexError:
653 # XXX TODO See above comment regarding IndexError.
654 warnings.warn(
655 "twisted.mail.pop3.IMailbox.listMessages may not "
656 "raise IndexError for out-of-bounds message numbers: "
657 "raise ValueError instead.",
658 PendingDeprecationWarning)
659 self.failResponse("Bad message number argument")
660 else:
661 log.msg("Unexpected _getMessageFile failure:")
662 log.err(err)
663 return None
664
665 sizeDeferred.addCallback(cbMessageSize)
666 sizeDeferred.addErrback(ebMessageSomething)
667 return sizeDeferred
668
669
670 def _sendMessageContent(self, i, fpWrapper, successResponse):
671 d = self._getMessageFile(i)
672 def cbMessageFile(info):
673 if info is None:
674 # Some error occurred - a failure response has been sent
675 # already, just give up.
676 return
677
678 self._highest = max(self._highest, int(i))
679 resp, fp = info
680 fp = fpWrapper(fp)
681 self.successResponse(successResponse(resp))
682 s = basic.FileSender()
683 d = s.beginFileTransfer(fp, self.transport, self.transformChunk)
684
685 def cbFileTransfer(lastsent):
686 if lastsent != '\n':
687 line = '\r\n.'
688 else:
689 line = '.'
690 self.sendLine(line)
691
692 def ebFileTransfer(err):
693 self.transport.loseConnection()
694 log.msg("Unexpected error in _sendMessageContent:")
695 log.err(err)
696
697 d.addCallback(cbFileTransfer)
698 d.addErrback(ebFileTransfer)
699 return d
700 return self._longOperation(d.addCallback(cbMessageFile))
701
702
703 def do_TOP(self, i, size):
704 try:
705 size = int(size)
706 if size < 0:
707 raise ValueError
708 except ValueError:
709 self.failResponse("Bad line count argument")
710 else:
711 return self._sendMessageContent(
712 i,
713 lambda fp: _HeadersPlusNLines(fp, size),
714 lambda size: "Top of message follows")
715
716
717 def do_RETR(self, i):
718 return self._sendMessageContent(
719 i,
720 lambda fp: fp,
721 lambda size: "%d" % (size,))
722
723
724 def transformChunk(self, chunk):
725 return chunk.replace('\n', '\r\n').replace('\r\n.', '\r\n..')
726
727
728 def finishedFileTransfer(self, lastsent):
729 if lastsent != '\n':
730 line = '\r\n.'
731 else:
732 line = '.'
733 self.sendLine(line)
734
735
736 def do_DELE(self, i):
737 i = int(i)-1
738 self.mbox.deleteMessage(i)
739 self.successResponse()
740
741
742 def do_NOOP(self):
743 """Perform no operation. Return a success code"""
744 self.successResponse()
745
746
747 def do_RSET(self):
748 """Unset all deleted message flags"""
749 try:
750 self.mbox.undeleteMessages()
751 except:
752 log.err()
753 self.failResponse()
754 else:
755 self._highest = 0
756 self.successResponse()
757
758
759 def do_LAST(self):
760 """
761 Return the index of the highest message yet downloaded.
762 """
763 self.successResponse(self._highest)
764
765
766 def do_RPOP(self, user):
767 self.failResponse('permission denied, sucker')
768
769
770 def do_QUIT(self):
771 if self.mbox:
772 self.mbox.sync()
773 self.successResponse()
774 self.transport.loseConnection()
775
776
777 def authenticateUserAPOP(self, user, digest):
778 """Perform authentication of an APOP login.
779
780 @type user: C{str}
781 @param user: The name of the user attempting to log in.
782
783 @type digest: C{str}
784 @param digest: The response string with which the user replied.
785
786 @rtype: C{Deferred}
787 @return: A deferred whose callback is invoked if the login is
788 successful, and whose errback will be invoked otherwise. The
789 callback will be passed a 3-tuple consisting of IMailbox,
790 an object implementing IMailbox, and a zero-argument callable
791 to be invoked when this session is terminated.
792 """
793 if self.portal is not None:
794 return self.portal.login(
795 APOPCredentials(self.magic, user, digest),
796 None,
797 IMailbox
798 )
799 raise cred.error.UnauthorizedLogin()
800
801 def authenticateUserPASS(self, user, password):
802 """Perform authentication of a username/password login.
803
804 @type user: C{str}
805 @param user: The name of the user attempting to log in.
806
807 @type password: C{str}
808 @param password: The password to attempt to authenticate with.
809
810 @rtype: C{Deferred}
811 @return: A deferred whose callback is invoked if the login is
812 successful, and whose errback will be invoked otherwise. The
813 callback will be passed a 3-tuple consisting of IMailbox,
814 an object implementing IMailbox, and a zero-argument callable
815 to be invoked when this session is terminated.
816 """
817 if self.portal is not None:
818 return self.portal.login(
819 cred.credentials.UsernamePassword(user, password),
820 None,
821 IMailbox
822 )
823 raise cred.error.UnauthorizedLogin()
824
825
826 class IServerFactory(Interface):
827 """Interface for querying additional parameters of this POP3 server.
828
829 Any cap_* method may raise NotImplementedError if the particular
830 capability is not supported. If cap_EXPIRE() does not raise
831 NotImplementedError, perUserExpiration() must be implemented, otherwise
832 they are optional. If cap_LOGIN_DELAY() is implemented,
833 perUserLoginDelay() must be implemented, otherwise they are optional.
834
835 @ivar challengers: A dictionary mapping challenger names to classes
836 implementing C{IUsernameHashedPassword}.
837 """
838
839 def cap_IMPLEMENTATION():
840 """Return a string describing this POP3 server implementation."""
841
842 def cap_EXPIRE():
843 """Return the minimum number of days messages are retained."""
844
845 def perUserExpiration():
846 """Indicate whether message expiration is per-user.
847
848 @return: True if it is, false otherwise.
849 """
850
851 def cap_LOGIN_DELAY():
852 """Return the minimum number of seconds between client logins."""
853
854 def perUserLoginDelay():
855 """Indicate whether the login delay period is per-user.
856
857 @return: True if it is, false otherwise.
858 """
859
860 class IMailbox(Interface):
861 """
862 @type loginDelay: C{int}
863 @ivar loginDelay: The number of seconds between allowed logins for the
864 user associated with this mailbox. None
865
866 @type messageExpiration: C{int}
867 @ivar messageExpiration: The number of days messages in this mailbox will
868 remain on the server before being deleted.
869 """
870
871 def listMessages(index=None):
872 """Retrieve the size of one or more messages.
873
874 @type index: C{int} or C{None}
875 @param index: The number of the message for which to retrieve the
876 size (starting at 0), or None to retrieve the size of all messages.
877
878 @rtype: C{int} or any iterable of C{int} or a L{Deferred} which fires
879 with one of these.
880
881 @return: The number of octets in the specified message, or an iterable
882 of integers representing the number of octets in all the messages. Any
883 value which would have referred to a deleted message should be set to 0.
884
885 @raise ValueError: if C{index} is greater than the index of any message
886 in the mailbox.
887 """
888
889 def getMessage(index):
890 """Retrieve a file-like object for a particular message.
891
892 @type index: C{int}
893 @param index: The number of the message to retrieve
894
895 @rtype: A file-like object
896 @return: A file containing the message data with lines delimited by
897 C{\\n}.
898 """
899
900 def getUidl(index):
901 """Get a unique identifier for a particular message.
902
903 @type index: C{int}
904 @param index: The number of the message for which to retrieve a UIDL
905
906 @rtype: C{str}
907 @return: A string of printable characters uniquely identifying for all
908 time the specified message.
909
910 @raise ValueError: if C{index} is greater than the index of any message
911 in the mailbox.
912 """
913
914 def deleteMessage(index):
915 """Delete a particular message.
916
917 This must not change the number of messages in this mailbox. Further
918 requests for the size of deleted messages should return 0. Further
919 requests for the message itself may raise an exception.
920
921 @type index: C{int}
922 @param index: The number of the message to delete.
923 """
924
925 def undeleteMessages():
926 """
927 Undelete any messages which have been marked for deletion since the
928 most recent L{sync} call.
929
930 Any message which can be undeleted should be returned to its
931 original position in the message sequence and retain its original
932 UID.
933 """
934
935 def sync():
936 """Perform checkpointing.
937
938 This method will be called to indicate the mailbox should attempt to
939 clean up any remaining deleted messages.
940 """
941
942
943
944 class Mailbox:
945 implements(IMailbox)
946
947 def listMessages(self, i=None):
948 return []
949 def getMessage(self, i):
950 raise ValueError
951 def getUidl(self, i):
952 raise ValueError
953 def deleteMessage(self, i):
954 raise ValueError
955 def undeleteMessages(self):
956 pass
957 def sync(self):
958 pass
959
960
961 NONE, SHORT, FIRST_LONG, LONG = range(4)
962
963 NEXT = {}
964 NEXT[NONE] = NONE
965 NEXT[SHORT] = NONE
966 NEXT[FIRST_LONG] = LONG
967 NEXT[LONG] = NONE
968
969 class POP3Client(basic.LineOnlyReceiver):
970
971 mode = SHORT
972 command = 'WELCOME'
973 import re
974 welcomeRe = re.compile('<(.*)>')
975
976 def __init__(self):
977 import warnings
978 warnings.warn("twisted.mail.pop3.POP3Client is deprecated, "
979 "please use twisted.mail.pop3.AdvancedPOP3Client "
980 "instead.", DeprecationWarning,
981 stacklevel=3)
982
983 def sendShort(self, command, params=None):
984 if params is not None:
985 self.sendLine('%s %s' % (command, params))
986 else:
987 self.sendLine(command)
988 self.command = command
989 self.mode = SHORT
990
991 def sendLong(self, command, params):
992 if params:
993 self.sendLine('%s %s' % (command, params))
994 else:
995 self.sendLine(command)
996 self.command = command
997 self.mode = FIRST_LONG
998
999 def handle_default(self, line):
1000 if line[:-4] == '-ERR':
1001 self.mode = NONE
1002
1003 def handle_WELCOME(self, line):
1004 code, data = line.split(' ', 1)
1005 if code != '+OK':
1006 self.transport.loseConnection()
1007 else:
1008 m = self.welcomeRe.match(line)
1009 if m:
1010 self.welcomeCode = m.group(1)
1011
1012 def _dispatch(self, command, default, *args):
1013 try:
1014 method = getattr(self, 'handle_'+command, default)
1015 if method is not None:
1016 method(*args)
1017 except:
1018 log.err()
1019
1020 def lineReceived(self, line):
1021 if self.mode == SHORT or self.mode == FIRST_LONG:
1022 self.mode = NEXT[self.mode]
1023 self._dispatch(self.command, self.handle_default, line)
1024 elif self.mode == LONG:
1025 if line == '.':
1026 self.mode = NEXT[self.mode]
1027 self._dispatch(self.command+'_end', None)
1028 return
1029 if line[:1] == '.':
1030 line = line[1:]
1031 self._dispatch(self.command+"_continue", None, line)
1032
1033 def apopAuthenticate(self, user, password, magic):
1034 digest = md5.new(magic + password).hexdigest()
1035 self.apop(user, digest)
1036
1037 def apop(self, user, digest):
1038 self.sendLong('APOP', ' '.join((user, digest)))
1039 def retr(self, i):
1040 self.sendLong('RETR', i)
1041 def dele(self, i):
1042 self.sendShort('DELE', i)
1043 def list(self, i=''):
1044 self.sendLong('LIST', i)
1045 def uidl(self, i=''):
1046 self.sendLong('UIDL', i)
1047 def user(self, name):
1048 self.sendShort('USER', name)
1049 def pass_(self, pass_):
1050 self.sendShort('PASS', pass_)
1051 def quit(self):
1052 self.sendShort('QUIT')
1053
1054 from twisted.mail.pop3client import POP3Client as AdvancedPOP3Client
1055 from twisted.mail.pop3client import POP3ClientError
1056 from twisted.mail.pop3client import InsecureAuthenticationDisallowed
1057 from twisted.mail.pop3client import ServerErrorResponse
1058 from twisted.mail.pop3client import LineTooLong
1059
1060 __all__ = [
1061 # Interfaces
1062 'IMailbox', 'IServerFactory',
1063
1064 # Exceptions
1065 'POP3Error', 'POP3ClientError', 'InsecureAuthenticationDisallowed',
1066 'ServerErrorResponse', 'LineTooLong',
1067
1068 # Protocol classes
1069 'POP3', 'POP3Client', 'AdvancedPOP3Client',
1070
1071 # Misc
1072 'APOPCredentials', 'Mailbox']
OLDNEW
« no previous file with comments | « third_party/twisted_8_1/twisted/mail/pb.py ('k') | third_party/twisted_8_1/twisted/mail/pop3client.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698