Index: third_party/twisted_8_1/twisted/mail/pop3.py |
diff --git a/third_party/twisted_8_1/twisted/mail/pop3.py b/third_party/twisted_8_1/twisted/mail/pop3.py |
deleted file mode 100644 |
index 5c3dd1373bce3a4e3f62017a179b9f6cb2481717..0000000000000000000000000000000000000000 |
--- a/third_party/twisted_8_1/twisted/mail/pop3.py |
+++ /dev/null |
@@ -1,1072 +0,0 @@ |
-# -*- test-case-name: twisted.mail.test.test_pop3 -*- |
-# |
-# Copyright (c) 2001-2004 Twisted Matrix Laboratories. |
-# See LICENSE for details. |
- |
- |
-""" |
-Post-office Protocol version 3 |
- |
-@author: U{Glyph Lefkowitz<mailto:glyph@twistedmatrix.com>} |
-@author: U{Jp Calderone<mailto:exarkun@twistedmatrix.com>} |
-""" |
- |
-import string |
-import base64 |
-import binascii |
-import md5 |
-import warnings |
- |
-from zope.interface import implements, Interface |
- |
-from twisted.mail import smtp |
-from twisted.protocols import basic |
-from twisted.protocols import policies |
-from twisted.internet import task |
-from twisted.internet import defer |
-from twisted.internet import interfaces |
-from twisted.python import log |
- |
-from twisted import cred |
-import twisted.cred.error |
-import twisted.cred.credentials |
- |
-## |
-## Authentication |
-## |
-class APOPCredentials: |
- implements(cred.credentials.IUsernamePassword) |
- |
- def __init__(self, magic, username, digest): |
- self.magic = magic |
- self.username = username |
- self.digest = digest |
- |
- def checkPassword(self, password): |
- seed = self.magic + password |
- myDigest = md5.new(seed).hexdigest() |
- return myDigest == self.digest |
- |
- |
-class _HeadersPlusNLines: |
- def __init__(self, f, n): |
- self.f = f |
- self.n = n |
- self.linecount = 0 |
- self.headers = 1 |
- self.done = 0 |
- self.buf = '' |
- |
- def read(self, bytes): |
- if self.done: |
- return '' |
- data = self.f.read(bytes) |
- if not data: |
- return data |
- if self.headers: |
- df, sz = data.find('\r\n\r\n'), 4 |
- if df == -1: |
- df, sz = data.find('\n\n'), 2 |
- if df != -1: |
- df += sz |
- val = data[:df] |
- data = data[df:] |
- self.linecount = 1 |
- self.headers = 0 |
- else: |
- val = '' |
- if self.linecount > 0: |
- dsplit = (self.buf+data).split('\n') |
- self.buf = dsplit[-1] |
- for ln in dsplit[:-1]: |
- if self.linecount > self.n: |
- self.done = 1 |
- return val |
- val += (ln + '\n') |
- self.linecount += 1 |
- return val |
- else: |
- return data |
- |
- |
- |
-class _POP3MessageDeleted(Exception): |
- """ |
- Internal control-flow exception. Indicates the file of a deleted message |
- was requested. |
- """ |
- |
- |
-class POP3Error(Exception): |
- pass |
- |
- |
- |
-class _IteratorBuffer(object): |
- bufSize = 0 |
- |
- def __init__(self, write, iterable, memoryBufferSize=None): |
- """ |
- Create a _IteratorBuffer. |
- |
- @param write: A one-argument callable which will be invoked with a list |
- of strings which have been buffered. |
- |
- @param iterable: The source of input strings as any iterable. |
- |
- @param memoryBufferSize: The upper limit on buffered string length, |
- beyond which the buffer will be flushed to the writer. |
- """ |
- self.lines = [] |
- self.write = write |
- self.iterator = iter(iterable) |
- if memoryBufferSize is None: |
- memoryBufferSize = 2 ** 16 |
- self.memoryBufferSize = memoryBufferSize |
- |
- |
- def __iter__(self): |
- return self |
- |
- |
- def next(self): |
- try: |
- v = self.iterator.next() |
- except StopIteration: |
- if self.lines: |
- self.write(self.lines) |
- # Drop some references, in case they're edges in a cycle. |
- del self.iterator, self.lines, self.write |
- raise |
- else: |
- if v is not None: |
- self.lines.append(v) |
- self.bufSize += len(v) |
- if self.bufSize > self.memoryBufferSize: |
- self.write(self.lines) |
- self.lines = [] |
- self.bufSize = 0 |
- |
- |
- |
-def iterateLineGenerator(proto, gen): |
- """ |
- Hook the given protocol instance up to the given iterator with an |
- _IteratorBuffer and schedule the result to be exhausted via the protocol. |
- |
- @type proto: L{POP3} |
- @type gen: iterator |
- @rtype: L{twisted.internet.defer.Deferred} |
- """ |
- coll = _IteratorBuffer(proto.transport.writeSequence, gen) |
- return proto.schedule(coll) |
- |
- |
- |
-def successResponse(response): |
- """ |
- Format the given object as a positive response. |
- """ |
- response = str(response) |
- return '+OK %s\r\n' % (response,) |
- |
- |
- |
-def formatStatResponse(msgs): |
- """ |
- Format the list of message sizes appropriately for a STAT response. |
- |
- Yields None until it finishes computing a result, then yields a str |
- instance that is suitable for use as a response to the STAT command. |
- Intended to be used with a L{twisted.internet.task.Cooperator}. |
- """ |
- i = 0 |
- bytes = 0 |
- for size in msgs: |
- i += 1 |
- bytes += size |
- yield None |
- yield successResponse('%d %d' % (i, bytes)) |
- |
- |
- |
-def formatListLines(msgs): |
- """ |
- Format a list of message sizes appropriately for the lines of a LIST |
- response. |
- |
- Yields str instances formatted appropriately for use as lines in the |
- response to the LIST command. Does not include the trailing '.'. |
- """ |
- i = 0 |
- for size in msgs: |
- i += 1 |
- yield '%d %d\r\n' % (i, size) |
- |
- |
- |
-def formatListResponse(msgs): |
- """ |
- Format a list of message sizes appropriately for a complete LIST response. |
- |
- Yields str instances formatted appropriately for use as a LIST command |
- response. |
- """ |
- yield successResponse(len(msgs)) |
- for ele in formatListLines(msgs): |
- yield ele |
- yield '.\r\n' |
- |
- |
- |
-def formatUIDListLines(msgs, getUidl): |
- """ |
- Format the list of message sizes appropriately for the lines of a UIDL |
- response. |
- |
- Yields str instances formatted appropriately for use as lines in the |
- response to the UIDL command. Does not include the trailing '.'. |
- """ |
- for i, m in enumerate(msgs): |
- if m is not None: |
- uid = getUidl(i) |
- yield '%d %s\r\n' % (i + 1, uid) |
- |
- |
- |
-def formatUIDListResponse(msgs, getUidl): |
- """ |
- Format a list of message sizes appropriately for a complete UIDL response. |
- |
- Yields str instances formatted appropriately for use as a UIDL command |
- response. |
- """ |
- yield successResponse('') |
- for ele in formatUIDListLines(msgs, getUidl): |
- yield ele |
- yield '.\r\n' |
- |
- |
- |
-class POP3(basic.LineOnlyReceiver, policies.TimeoutMixin): |
- """ |
- POP3 server protocol implementation. |
- |
- @ivar portal: A reference to the L{twisted.cred.portal.Portal} instance we |
- will authenticate through. |
- |
- @ivar factory: A L{twisted.mail.pop3.IServerFactory} which will be used to |
- determine some extended behavior of the server. |
- |
- @ivar timeOut: An integer which defines the minimum amount of time which |
- may elapse without receiving any traffic after which the client will be |
- disconnected. |
- |
- @ivar schedule: A one-argument callable which should behave like |
- L{twisted.internet.task.coiterate}. |
- """ |
- implements(interfaces.IProducer) |
- |
- magic = None |
- _userIs = None |
- _onLogout = None |
- |
- AUTH_CMDS = ['CAPA', 'USER', 'PASS', 'APOP', 'AUTH', 'RPOP', 'QUIT'] |
- |
- portal = None |
- factory = None |
- |
- # The mailbox we're serving |
- mbox = None |
- |
- # Set this pretty low -- POP3 clients are expected to log in, download |
- # everything, and log out. |
- timeOut = 300 |
- |
- # Current protocol state |
- state = "COMMAND" |
- |
- # PIPELINE |
- blocked = None |
- |
- # Cooperate and suchlike. |
- schedule = staticmethod(task.coiterate) |
- |
- # Message index of the highest retrieved message. |
- _highest = 0 |
- |
- def connectionMade(self): |
- if self.magic is None: |
- self.magic = self.generateMagic() |
- self.successResponse(self.magic) |
- self.setTimeout(self.timeOut) |
- if getattr(self.factory, 'noisy', True): |
- log.msg("New connection from " + str(self.transport.getPeer())) |
- |
- |
- def connectionLost(self, reason): |
- if self._onLogout is not None: |
- self._onLogout() |
- self._onLogout = None |
- self.setTimeout(None) |
- |
- |
- def generateMagic(self): |
- return smtp.messageid() |
- |
- |
- def successResponse(self, message=''): |
- self.transport.write(successResponse(message)) |
- |
- def failResponse(self, message=''): |
- self.sendLine('-ERR ' + str(message)) |
- |
-# def sendLine(self, line): |
-# print 'S:', repr(line) |
-# basic.LineOnlyReceiver.sendLine(self, line) |
- |
- def lineReceived(self, line): |
-# print 'C:', repr(line) |
- self.resetTimeout() |
- getattr(self, 'state_' + self.state)(line) |
- |
- def _unblock(self, _): |
- commands = self.blocked |
- self.blocked = None |
- while commands and self.blocked is None: |
- cmd, args = commands.pop(0) |
- self.processCommand(cmd, *args) |
- if self.blocked is not None: |
- self.blocked.extend(commands) |
- |
- def state_COMMAND(self, line): |
- try: |
- return self.processCommand(*line.split(' ')) |
- except (ValueError, AttributeError, POP3Error, TypeError), e: |
- log.err() |
- self.failResponse('bad protocol or server: %s: %s' % (e.__class__.__name__, e)) |
- |
- def processCommand(self, command, *args): |
- if self.blocked is not None: |
- self.blocked.append((command, args)) |
- return |
- |
- command = string.upper(command) |
- authCmd = command in self.AUTH_CMDS |
- if not self.mbox and not authCmd: |
- raise POP3Error("not authenticated yet: cannot do " + command) |
- f = getattr(self, 'do_' + command, None) |
- if f: |
- return f(*args) |
- raise POP3Error("Unknown protocol command: " + command) |
- |
- |
- def listCapabilities(self): |
- baseCaps = [ |
- "TOP", |
- "USER", |
- "UIDL", |
- "PIPELINE", |
- "CELERITY", |
- "AUSPEX", |
- "POTENCE", |
- ] |
- |
- if IServerFactory.providedBy(self.factory): |
- # Oh my god. We can't just loop over a list of these because |
- # each has spectacularly different return value semantics! |
- try: |
- v = self.factory.cap_IMPLEMENTATION() |
- except NotImplementedError: |
- pass |
- except: |
- log.err() |
- else: |
- baseCaps.append("IMPLEMENTATION " + str(v)) |
- |
- try: |
- v = self.factory.cap_EXPIRE() |
- except NotImplementedError: |
- pass |
- except: |
- log.err() |
- else: |
- if v is None: |
- v = "NEVER" |
- if self.factory.perUserExpiration(): |
- if self.mbox: |
- v = str(self.mbox.messageExpiration) |
- else: |
- v = str(v) + " USER" |
- v = str(v) |
- baseCaps.append("EXPIRE " + v) |
- |
- try: |
- v = self.factory.cap_LOGIN_DELAY() |
- except NotImplementedError: |
- pass |
- except: |
- log.err() |
- else: |
- if self.factory.perUserLoginDelay(): |
- if self.mbox: |
- v = str(self.mbox.loginDelay) |
- else: |
- v = str(v) + " USER" |
- v = str(v) |
- baseCaps.append("LOGIN-DELAY " + v) |
- |
- try: |
- v = self.factory.challengers |
- except AttributeError: |
- pass |
- except: |
- log.err() |
- else: |
- baseCaps.append("SASL " + ' '.join(v.keys())) |
- return baseCaps |
- |
- def do_CAPA(self): |
- self.successResponse("I can do the following:") |
- for cap in self.listCapabilities(): |
- self.sendLine(cap) |
- self.sendLine(".") |
- |
- def do_AUTH(self, args=None): |
- if not getattr(self.factory, 'challengers', None): |
- self.failResponse("AUTH extension unsupported") |
- return |
- |
- if args is None: |
- self.successResponse("Supported authentication methods:") |
- for a in self.factory.challengers: |
- self.sendLine(a.upper()) |
- self.sendLine(".") |
- return |
- |
- auth = self.factory.challengers.get(args.strip().upper()) |
- if not self.portal or not auth: |
- self.failResponse("Unsupported SASL selected") |
- return |
- |
- self._auth = auth() |
- chal = self._auth.getChallenge() |
- |
- self.sendLine('+ ' + base64.encodestring(chal).rstrip('\n')) |
- self.state = 'AUTH' |
- |
- def state_AUTH(self, line): |
- self.state = "COMMAND" |
- try: |
- parts = base64.decodestring(line).split(None, 1) |
- except binascii.Error: |
- self.failResponse("Invalid BASE64 encoding") |
- else: |
- if len(parts) != 2: |
- self.failResponse("Invalid AUTH response") |
- return |
- self._auth.username = parts[0] |
- self._auth.response = parts[1] |
- d = self.portal.login(self._auth, None, IMailbox) |
- d.addCallback(self._cbMailbox, parts[0]) |
- d.addErrback(self._ebMailbox) |
- d.addErrback(self._ebUnexpected) |
- |
- def do_APOP(self, user, digest): |
- d = defer.maybeDeferred(self.authenticateUserAPOP, user, digest) |
- d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,) |
- ).addErrback(self._ebUnexpected) |
- |
- def _cbMailbox(self, (interface, avatar, logout), user): |
- if interface is not IMailbox: |
- self.failResponse('Authentication failed') |
- log.err("_cbMailbox() called with an interface other than IMailbox") |
- return |
- |
- self.mbox = avatar |
- self._onLogout = logout |
- self.successResponse('Authentication succeeded') |
- if getattr(self.factory, 'noisy', True): |
- log.msg("Authenticated login for " + user) |
- |
- def _ebMailbox(self, failure): |
- failure = failure.trap(cred.error.LoginDenied, cred.error.LoginFailed) |
- if issubclass(failure, cred.error.LoginDenied): |
- self.failResponse("Access denied: " + str(failure)) |
- elif issubclass(failure, cred.error.LoginFailed): |
- self.failResponse('Authentication failed') |
- if getattr(self.factory, 'noisy', True): |
- log.msg("Denied login attempt from " + str(self.transport.getPeer())) |
- |
- def _ebUnexpected(self, failure): |
- self.failResponse('Server error: ' + failure.getErrorMessage()) |
- log.err(failure) |
- |
- def do_USER(self, user): |
- self._userIs = user |
- self.successResponse('USER accepted, send PASS') |
- |
- def do_PASS(self, password): |
- if self._userIs is None: |
- self.failResponse("USER required before PASS") |
- return |
- user = self._userIs |
- self._userIs = None |
- d = defer.maybeDeferred(self.authenticateUserPASS, user, password) |
- d.addCallbacks(self._cbMailbox, self._ebMailbox, callbackArgs=(user,) |
- ).addErrback(self._ebUnexpected) |
- |
- |
- def _longOperation(self, d): |
- # Turn off timeouts and block further processing until the Deferred |
- # fires, then reverse those changes. |
- timeOut = self.timeOut |
- self.setTimeout(None) |
- self.blocked = [] |
- d.addCallback(self._unblock) |
- d.addCallback(lambda ign: self.setTimeout(timeOut)) |
- return d |
- |
- |
- def _coiterate(self, gen): |
- return self.schedule(_IteratorBuffer(self.transport.writeSequence, gen)) |
- |
- |
- def do_STAT(self): |
- d = defer.maybeDeferred(self.mbox.listMessages) |
- def cbMessages(msgs): |
- return self._coiterate(formatStatResponse(msgs)) |
- def ebMessages(err): |
- self.failResponse(err.getErrorMessage()) |
- log.msg("Unexpected do_STAT failure:") |
- log.err(err) |
- return self._longOperation(d.addCallbacks(cbMessages, ebMessages)) |
- |
- |
- def do_LIST(self, i=None): |
- if i is None: |
- d = defer.maybeDeferred(self.mbox.listMessages) |
- def cbMessages(msgs): |
- return self._coiterate(formatListResponse(msgs)) |
- def ebMessages(err): |
- self.failResponse(err.getErrorMessage()) |
- log.msg("Unexpected do_LIST failure:") |
- log.err(err) |
- return self._longOperation(d.addCallbacks(cbMessages, ebMessages)) |
- else: |
- try: |
- i = int(i) |
- if i < 1: |
- raise ValueError() |
- except ValueError: |
- self.failResponse("Invalid message-number: %r" % (i,)) |
- else: |
- d = defer.maybeDeferred(self.mbox.listMessages, i - 1) |
- def cbMessage(msg): |
- self.successResponse('%d %d' % (i, msg)) |
- def ebMessage(err): |
- errcls = err.check(ValueError, IndexError) |
- if errcls is not None: |
- if errcls is IndexError: |
- # IndexError was supported for a while, but really |
- # shouldn't be. One error condition, one exception |
- # type. |
- warnings.warn( |
- "twisted.mail.pop3.IMailbox.listMessages may not " |
- "raise IndexError for out-of-bounds message numbers: " |
- "raise ValueError instead.", |
- PendingDeprecationWarning) |
- self.failResponse("Invalid message-number: %r" % (i,)) |
- else: |
- self.failResponse(err.getErrorMessage()) |
- log.msg("Unexpected do_LIST failure:") |
- log.err(err) |
- return self._longOperation(d.addCallbacks(cbMessage, ebMessage)) |
- |
- |
- def do_UIDL(self, i=None): |
- if i is None: |
- d = defer.maybeDeferred(self.mbox.listMessages) |
- def cbMessages(msgs): |
- return self._coiterate(formatUIDListResponse(msgs, self.mbox.getUidl)) |
- def ebMessages(err): |
- self.failResponse(err.getErrorMessage()) |
- log.msg("Unexpected do_UIDL failure:") |
- log.err(err) |
- return self._longOperation(d.addCallbacks(cbMessages, ebMessages)) |
- else: |
- try: |
- i = int(i) |
- if i < 1: |
- raise ValueError() |
- except ValueError: |
- self.failResponse("Bad message number argument") |
- else: |
- try: |
- msg = self.mbox.getUidl(i - 1) |
- except IndexError: |
- # XXX TODO See above comment regarding IndexError. |
- warnings.warn( |
- "twisted.mail.pop3.IMailbox.getUidl may not " |
- "raise IndexError for out-of-bounds message numbers: " |
- "raise ValueError instead.", |
- PendingDeprecationWarning) |
- self.failResponse("Bad message number argument") |
- except ValueError: |
- self.failResponse("Bad message number argument") |
- else: |
- self.successResponse(str(msg)) |
- |
- |
- def _getMessageFile(self, i): |
- """ |
- Retrieve the size and contents of a given message, as a two-tuple. |
- |
- @param i: The number of the message to operate on. This is a base-ten |
- string representation starting at 1. |
- |
- @return: A Deferred which fires with a two-tuple of an integer and a |
- file-like object. |
- """ |
- try: |
- msg = int(i) - 1 |
- if msg < 0: |
- raise ValueError() |
- except ValueError: |
- self.failResponse("Bad message number argument") |
- return defer.succeed(None) |
- |
- sizeDeferred = defer.maybeDeferred(self.mbox.listMessages, msg) |
- def cbMessageSize(size): |
- if not size: |
- return defer.fail(_POP3MessageDeleted()) |
- fileDeferred = defer.maybeDeferred(self.mbox.getMessage, msg) |
- fileDeferred.addCallback(lambda fObj: (size, fObj)) |
- return fileDeferred |
- |
- def ebMessageSomething(err): |
- errcls = err.check(_POP3MessageDeleted, ValueError, IndexError) |
- if errcls is _POP3MessageDeleted: |
- self.failResponse("message deleted") |
- elif errcls in (ValueError, IndexError): |
- if errcls is IndexError: |
- # XXX TODO See above comment regarding IndexError. |
- warnings.warn( |
- "twisted.mail.pop3.IMailbox.listMessages may not " |
- "raise IndexError for out-of-bounds message numbers: " |
- "raise ValueError instead.", |
- PendingDeprecationWarning) |
- self.failResponse("Bad message number argument") |
- else: |
- log.msg("Unexpected _getMessageFile failure:") |
- log.err(err) |
- return None |
- |
- sizeDeferred.addCallback(cbMessageSize) |
- sizeDeferred.addErrback(ebMessageSomething) |
- return sizeDeferred |
- |
- |
- def _sendMessageContent(self, i, fpWrapper, successResponse): |
- d = self._getMessageFile(i) |
- def cbMessageFile(info): |
- if info is None: |
- # Some error occurred - a failure response has been sent |
- # already, just give up. |
- return |
- |
- self._highest = max(self._highest, int(i)) |
- resp, fp = info |
- fp = fpWrapper(fp) |
- self.successResponse(successResponse(resp)) |
- s = basic.FileSender() |
- d = s.beginFileTransfer(fp, self.transport, self.transformChunk) |
- |
- def cbFileTransfer(lastsent): |
- if lastsent != '\n': |
- line = '\r\n.' |
- else: |
- line = '.' |
- self.sendLine(line) |
- |
- def ebFileTransfer(err): |
- self.transport.loseConnection() |
- log.msg("Unexpected error in _sendMessageContent:") |
- log.err(err) |
- |
- d.addCallback(cbFileTransfer) |
- d.addErrback(ebFileTransfer) |
- return d |
- return self._longOperation(d.addCallback(cbMessageFile)) |
- |
- |
- def do_TOP(self, i, size): |
- try: |
- size = int(size) |
- if size < 0: |
- raise ValueError |
- except ValueError: |
- self.failResponse("Bad line count argument") |
- else: |
- return self._sendMessageContent( |
- i, |
- lambda fp: _HeadersPlusNLines(fp, size), |
- lambda size: "Top of message follows") |
- |
- |
- def do_RETR(self, i): |
- return self._sendMessageContent( |
- i, |
- lambda fp: fp, |
- lambda size: "%d" % (size,)) |
- |
- |
- def transformChunk(self, chunk): |
- return chunk.replace('\n', '\r\n').replace('\r\n.', '\r\n..') |
- |
- |
- def finishedFileTransfer(self, lastsent): |
- if lastsent != '\n': |
- line = '\r\n.' |
- else: |
- line = '.' |
- self.sendLine(line) |
- |
- |
- def do_DELE(self, i): |
- i = int(i)-1 |
- self.mbox.deleteMessage(i) |
- self.successResponse() |
- |
- |
- def do_NOOP(self): |
- """Perform no operation. Return a success code""" |
- self.successResponse() |
- |
- |
- def do_RSET(self): |
- """Unset all deleted message flags""" |
- try: |
- self.mbox.undeleteMessages() |
- except: |
- log.err() |
- self.failResponse() |
- else: |
- self._highest = 0 |
- self.successResponse() |
- |
- |
- def do_LAST(self): |
- """ |
- Return the index of the highest message yet downloaded. |
- """ |
- self.successResponse(self._highest) |
- |
- |
- def do_RPOP(self, user): |
- self.failResponse('permission denied, sucker') |
- |
- |
- def do_QUIT(self): |
- if self.mbox: |
- self.mbox.sync() |
- self.successResponse() |
- self.transport.loseConnection() |
- |
- |
- def authenticateUserAPOP(self, user, digest): |
- """Perform authentication of an APOP login. |
- |
- @type user: C{str} |
- @param user: The name of the user attempting to log in. |
- |
- @type digest: C{str} |
- @param digest: The response string with which the user replied. |
- |
- @rtype: C{Deferred} |
- @return: A deferred whose callback is invoked if the login is |
- successful, and whose errback will be invoked otherwise. The |
- callback will be passed a 3-tuple consisting of IMailbox, |
- an object implementing IMailbox, and a zero-argument callable |
- to be invoked when this session is terminated. |
- """ |
- if self.portal is not None: |
- return self.portal.login( |
- APOPCredentials(self.magic, user, digest), |
- None, |
- IMailbox |
- ) |
- raise cred.error.UnauthorizedLogin() |
- |
- def authenticateUserPASS(self, user, password): |
- """Perform authentication of a username/password login. |
- |
- @type user: C{str} |
- @param user: The name of the user attempting to log in. |
- |
- @type password: C{str} |
- @param password: The password to attempt to authenticate with. |
- |
- @rtype: C{Deferred} |
- @return: A deferred whose callback is invoked if the login is |
- successful, and whose errback will be invoked otherwise. The |
- callback will be passed a 3-tuple consisting of IMailbox, |
- an object implementing IMailbox, and a zero-argument callable |
- to be invoked when this session is terminated. |
- """ |
- if self.portal is not None: |
- return self.portal.login( |
- cred.credentials.UsernamePassword(user, password), |
- None, |
- IMailbox |
- ) |
- raise cred.error.UnauthorizedLogin() |
- |
- |
-class IServerFactory(Interface): |
- """Interface for querying additional parameters of this POP3 server. |
- |
- Any cap_* method may raise NotImplementedError if the particular |
- capability is not supported. If cap_EXPIRE() does not raise |
- NotImplementedError, perUserExpiration() must be implemented, otherwise |
- they are optional. If cap_LOGIN_DELAY() is implemented, |
- perUserLoginDelay() must be implemented, otherwise they are optional. |
- |
- @ivar challengers: A dictionary mapping challenger names to classes |
- implementing C{IUsernameHashedPassword}. |
- """ |
- |
- def cap_IMPLEMENTATION(): |
- """Return a string describing this POP3 server implementation.""" |
- |
- def cap_EXPIRE(): |
- """Return the minimum number of days messages are retained.""" |
- |
- def perUserExpiration(): |
- """Indicate whether message expiration is per-user. |
- |
- @return: True if it is, false otherwise. |
- """ |
- |
- def cap_LOGIN_DELAY(): |
- """Return the minimum number of seconds between client logins.""" |
- |
- def perUserLoginDelay(): |
- """Indicate whether the login delay period is per-user. |
- |
- @return: True if it is, false otherwise. |
- """ |
- |
-class IMailbox(Interface): |
- """ |
- @type loginDelay: C{int} |
- @ivar loginDelay: The number of seconds between allowed logins for the |
- user associated with this mailbox. None |
- |
- @type messageExpiration: C{int} |
- @ivar messageExpiration: The number of days messages in this mailbox will |
- remain on the server before being deleted. |
- """ |
- |
- def listMessages(index=None): |
- """Retrieve the size of one or more messages. |
- |
- @type index: C{int} or C{None} |
- @param index: The number of the message for which to retrieve the |
- size (starting at 0), or None to retrieve the size of all messages. |
- |
- @rtype: C{int} or any iterable of C{int} or a L{Deferred} which fires |
- with one of these. |
- |
- @return: The number of octets in the specified message, or an iterable |
- of integers representing the number of octets in all the messages. Any |
- value which would have referred to a deleted message should be set to 0. |
- |
- @raise ValueError: if C{index} is greater than the index of any message |
- in the mailbox. |
- """ |
- |
- def getMessage(index): |
- """Retrieve a file-like object for a particular message. |
- |
- @type index: C{int} |
- @param index: The number of the message to retrieve |
- |
- @rtype: A file-like object |
- @return: A file containing the message data with lines delimited by |
- C{\\n}. |
- """ |
- |
- def getUidl(index): |
- """Get a unique identifier for a particular message. |
- |
- @type index: C{int} |
- @param index: The number of the message for which to retrieve a UIDL |
- |
- @rtype: C{str} |
- @return: A string of printable characters uniquely identifying for all |
- time the specified message. |
- |
- @raise ValueError: if C{index} is greater than the index of any message |
- in the mailbox. |
- """ |
- |
- def deleteMessage(index): |
- """Delete a particular message. |
- |
- This must not change the number of messages in this mailbox. Further |
- requests for the size of deleted messages should return 0. Further |
- requests for the message itself may raise an exception. |
- |
- @type index: C{int} |
- @param index: The number of the message to delete. |
- """ |
- |
- def undeleteMessages(): |
- """ |
- Undelete any messages which have been marked for deletion since the |
- most recent L{sync} call. |
- |
- Any message which can be undeleted should be returned to its |
- original position in the message sequence and retain its original |
- UID. |
- """ |
- |
- def sync(): |
- """Perform checkpointing. |
- |
- This method will be called to indicate the mailbox should attempt to |
- clean up any remaining deleted messages. |
- """ |
- |
- |
- |
-class Mailbox: |
- implements(IMailbox) |
- |
- def listMessages(self, i=None): |
- return [] |
- def getMessage(self, i): |
- raise ValueError |
- def getUidl(self, i): |
- raise ValueError |
- def deleteMessage(self, i): |
- raise ValueError |
- def undeleteMessages(self): |
- pass |
- def sync(self): |
- pass |
- |
- |
-NONE, SHORT, FIRST_LONG, LONG = range(4) |
- |
-NEXT = {} |
-NEXT[NONE] = NONE |
-NEXT[SHORT] = NONE |
-NEXT[FIRST_LONG] = LONG |
-NEXT[LONG] = NONE |
- |
-class POP3Client(basic.LineOnlyReceiver): |
- |
- mode = SHORT |
- command = 'WELCOME' |
- import re |
- welcomeRe = re.compile('<(.*)>') |
- |
- def __init__(self): |
- import warnings |
- warnings.warn("twisted.mail.pop3.POP3Client is deprecated, " |
- "please use twisted.mail.pop3.AdvancedPOP3Client " |
- "instead.", DeprecationWarning, |
- stacklevel=3) |
- |
- def sendShort(self, command, params=None): |
- if params is not None: |
- self.sendLine('%s %s' % (command, params)) |
- else: |
- self.sendLine(command) |
- self.command = command |
- self.mode = SHORT |
- |
- def sendLong(self, command, params): |
- if params: |
- self.sendLine('%s %s' % (command, params)) |
- else: |
- self.sendLine(command) |
- self.command = command |
- self.mode = FIRST_LONG |
- |
- def handle_default(self, line): |
- if line[:-4] == '-ERR': |
- self.mode = NONE |
- |
- def handle_WELCOME(self, line): |
- code, data = line.split(' ', 1) |
- if code != '+OK': |
- self.transport.loseConnection() |
- else: |
- m = self.welcomeRe.match(line) |
- if m: |
- self.welcomeCode = m.group(1) |
- |
- def _dispatch(self, command, default, *args): |
- try: |
- method = getattr(self, 'handle_'+command, default) |
- if method is not None: |
- method(*args) |
- except: |
- log.err() |
- |
- def lineReceived(self, line): |
- if self.mode == SHORT or self.mode == FIRST_LONG: |
- self.mode = NEXT[self.mode] |
- self._dispatch(self.command, self.handle_default, line) |
- elif self.mode == LONG: |
- if line == '.': |
- self.mode = NEXT[self.mode] |
- self._dispatch(self.command+'_end', None) |
- return |
- if line[:1] == '.': |
- line = line[1:] |
- self._dispatch(self.command+"_continue", None, line) |
- |
- def apopAuthenticate(self, user, password, magic): |
- digest = md5.new(magic + password).hexdigest() |
- self.apop(user, digest) |
- |
- def apop(self, user, digest): |
- self.sendLong('APOP', ' '.join((user, digest))) |
- def retr(self, i): |
- self.sendLong('RETR', i) |
- def dele(self, i): |
- self.sendShort('DELE', i) |
- def list(self, i=''): |
- self.sendLong('LIST', i) |
- def uidl(self, i=''): |
- self.sendLong('UIDL', i) |
- def user(self, name): |
- self.sendShort('USER', name) |
- def pass_(self, pass_): |
- self.sendShort('PASS', pass_) |
- def quit(self): |
- self.sendShort('QUIT') |
- |
-from twisted.mail.pop3client import POP3Client as AdvancedPOP3Client |
-from twisted.mail.pop3client import POP3ClientError |
-from twisted.mail.pop3client import InsecureAuthenticationDisallowed |
-from twisted.mail.pop3client import ServerErrorResponse |
-from twisted.mail.pop3client import LineTooLong |
- |
-__all__ = [ |
- # Interfaces |
- 'IMailbox', 'IServerFactory', |
- |
- # Exceptions |
- 'POP3Error', 'POP3ClientError', 'InsecureAuthenticationDisallowed', |
- 'ServerErrorResponse', 'LineTooLong', |
- |
- # Protocol classes |
- 'POP3', 'POP3Client', 'AdvancedPOP3Client', |
- |
- # Misc |
- 'APOPCredentials', 'Mailbox'] |