aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/Twisted/py3/twisted/mail/_pop3client.py
diff options
context:
space:
mode:
authorshmel1k <shmel1k@ydb.tech>2023-11-26 18:16:14 +0300
committershmel1k <shmel1k@ydb.tech>2023-11-26 18:43:30 +0300
commitb8cf9e88f4c5c64d9406af533d8948deb050d695 (patch)
tree218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/mail/_pop3client.py
parent523f645a83a0ec97a0332dbc3863bb354c92a328 (diff)
downloadydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/mail/_pop3client.py')
-rw-r--r--contrib/python/Twisted/py3/twisted/mail/_pop3client.py1235
1 files changed, 1235 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/mail/_pop3client.py b/contrib/python/Twisted/py3/twisted/mail/_pop3client.py
new file mode 100644
index 0000000000..08efe1ec54
--- /dev/null
+++ b/contrib/python/Twisted/py3/twisted/mail/_pop3client.py
@@ -0,0 +1,1235 @@
+# -*- test-case-name: twisted.mail.test.test_pop3client -*-
+# Copyright (c) 2001-2004 Divmod Inc.
+# Copyright (c) Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+A POP3 client protocol implementation.
+
+Don't use this module directly. Use twisted.mail.pop3 instead.
+
+@author: Jp Calderone
+"""
+
+import re
+from hashlib import md5
+from typing import List
+
+from twisted.internet import defer, error, interfaces
+from twisted.mail._except import (
+ InsecureAuthenticationDisallowed,
+ LineTooLong,
+ ServerErrorResponse,
+ TLSError,
+ TLSNotSupportedError,
+)
+from twisted.protocols import basic, policies
+from twisted.python import log
+
+OK = b"+OK"
+ERR = b"-ERR"
+
+
+class _ListSetter:
+ """
+ A utility class to construct a list from a multi-line response accounting
+ for deleted messages.
+
+ POP3 responses sometimes occur in the form of a list of lines containing
+ two pieces of data, a message index and a value of some sort. When a
+ message is deleted, it is omitted from these responses. The L{setitem}
+ method of this class is meant to be called with these two values. In the
+ cases where indices are skipped, it takes care of padding out the missing
+ values with L{None}.
+
+ @ivar L: See L{__init__}
+ """
+
+ def __init__(self, L):
+ """
+ @type L: L{list} of L{object}
+ @param L: The list being constructed. An empty list should be
+ passed in.
+ """
+ self.L = L
+
+ def setitem(self, itemAndValue):
+ """
+ Add the value at the specified position, padding out missing entries.
+
+ @type itemAndValue: C{tuple}
+ @param itemAndValue: A tuple of (item, value). The I{item} is the 0-based
+ index in the list at which the value should be placed. The value is
+ is an L{object} to put in the list.
+ """
+ (item, value) = itemAndValue
+ diff = item - len(self.L) + 1
+ if diff > 0:
+ self.L.extend([None] * diff)
+ self.L[item] = value
+
+
+def _statXform(line):
+ """
+ Parse the response to a STAT command.
+
+ @type line: L{bytes}
+ @param line: The response from the server to a STAT command minus the
+ status indicator.
+
+ @rtype: 2-L{tuple} of (0) L{int}, (1) L{int}
+ @return: The number of messages in the mailbox and the size of the mailbox.
+ """
+ numMsgs, totalSize = line.split(None, 1)
+ return int(numMsgs), int(totalSize)
+
+
+def _listXform(line):
+ """
+ Parse a line of the response to a LIST command.
+
+ The line from the LIST response consists of a 1-based message number
+ followed by a size.
+
+ @type line: L{bytes}
+ @param line: A non-initial line from the multi-line response to a LIST
+ command.
+
+ @rtype: 2-L{tuple} of (0) L{int}, (1) L{int}
+ @return: The 0-based index of the message and the size of the message.
+ """
+ index, size = line.split(None, 1)
+ return int(index) - 1, int(size)
+
+
+def _uidXform(line):
+ """
+ Parse a line of the response to a UIDL command.
+
+ The line from the UIDL response consists of a 1-based message number
+ followed by a unique id.
+
+ @type line: L{bytes}
+ @param line: A non-initial line from the multi-line response to a UIDL
+ command.
+
+ @rtype: 2-L{tuple} of (0) L{int}, (1) L{bytes}
+ @return: The 0-based index of the message and the unique identifier
+ for the message.
+ """
+ index, uid = line.split(None, 1)
+ return int(index) - 1, uid
+
+
+def _codeStatusSplit(line):
+ """
+ Parse the first line of a multi-line server response.
+
+ @type line: L{bytes}
+ @param line: The first line of a multi-line server response.
+
+ @rtype: 2-tuple of (0) L{bytes}, (1) L{bytes}
+ @return: The status indicator and the rest of the server response.
+ """
+ parts = line.split(b" ", 1)
+ if len(parts) == 1:
+ return parts[0], b""
+ return parts
+
+
+def _dotUnquoter(line):
+ """
+ Remove a byte-stuffed termination character at the beginning of a line if
+ present.
+
+ When the termination character (C{'.'}) appears at the beginning of a line,
+ the server byte-stuffs it by adding another termination character to
+ avoid confusion with the terminating sequence (C{'.\\r\\n'}).
+
+ @type line: L{bytes}
+ @param line: A received line.
+
+ @rtype: L{bytes}
+ @return: The line without the byte-stuffed termination character at the
+ beginning if it was present. Otherwise, the line unchanged.
+ """
+ if line.startswith(b".."):
+ return line[1:]
+ return line
+
+
+class POP3Client(basic.LineOnlyReceiver, policies.TimeoutMixin):
+ """
+ A POP3 client protocol.
+
+ Instances of this class provide a convenient, efficient API for
+ retrieving and deleting messages from a POP3 server.
+
+ This API provides a pipelining interface but POP3 pipelining
+ on the network is not yet supported.
+
+ @type startedTLS: L{bool}
+ @ivar startedTLS: An indication of whether TLS has been negotiated
+ successfully.
+
+ @type allowInsecureLogin: L{bool}
+ @ivar allowInsecureLogin: An indication of whether plaintext login should
+ be allowed when the server offers no authentication challenge and the
+ transport does not offer any protection via encryption.
+
+ @type serverChallenge: L{bytes} or L{None}
+ @ivar serverChallenge: The challenge received in the server greeting.
+
+ @type timeout: L{int}
+ @ivar timeout: The number of seconds to wait on a response from the server
+ before timing out a connection. If the number is <= 0, no timeout
+ checking will be performed.
+
+ @type _capCache: L{None} or L{dict} mapping L{bytes}
+ to L{list} of L{bytes} and/or L{bytes} to L{None}
+ @ivar _capCache: The cached server capabilities. Capabilities are not
+ allowed to change during the session (except when TLS is negotiated),
+ so the first response to a capabilities command can be used for
+ later lookups.
+
+ @type _challengeMagicRe: L{Pattern <re.Pattern.search>}
+ @ivar _challengeMagicRe: A regular expression which matches the
+ challenge in the server greeting.
+
+ @type _blockedQueue: L{None} or L{list} of 3-L{tuple}
+ of (0) L{Deferred <defer.Deferred>}, (1) callable which results
+ in a L{Deferred <defer.Deferred>}, (2) L{tuple}
+ @ivar _blockedQueue: A list of blocked commands. While a command is
+ awaiting a response from the server, other commands are blocked. When
+ no command is outstanding, C{_blockedQueue} is set to L{None}.
+ Otherwise, it contains a list of information about blocked commands.
+ Each list entry provides the following information about a blocked
+ command: the deferred that should be called when the response to the
+ command is received, the function that sends the command, and the
+ arguments to the function.
+
+ @type _waiting: L{Deferred <defer.Deferred>} or
+ L{None}
+ @ivar _waiting: A deferred which fires when the response to the
+ outstanding command is received from the server.
+
+ @type _timedOut: L{bool}
+ @ivar _timedOut: An indication of whether the connection was dropped
+ because of a timeout.
+
+ @type _greetingError: L{bytes} or L{None}
+ @ivar _greetingError: The server greeting minus the status indicator, when
+ the connection was dropped because of an error in the server greeting.
+ Otherwise, L{None}.
+
+ @type state: L{bytes}
+ @ivar state: The state which indicates what type of response is expected
+ from the server. Valid states are: 'WELCOME', 'WAITING', 'SHORT',
+ 'LONG_INITIAL', 'LONG'.
+
+ @type _xform: L{None} or callable that takes L{bytes}
+ and returns L{object}
+ @ivar _xform: The transform function which is used to convert each
+ line of a multi-line response into usable values for use by the
+ consumer function. If L{None}, each line of the multi-line response
+ is sent directly to the consumer function.
+
+ @type _consumer: callable that takes L{object}
+ @ivar _consumer: The consumer function which is used to store the
+ values derived by the transform function from each line of a
+ multi-line response into a list.
+ """
+
+ startedTLS = False
+ allowInsecureLogin = False
+ timeout = 0
+ serverChallenge = None
+
+ _capCache = None
+ _challengeMagicRe = re.compile(b"(<[^>]+>)")
+ _blockedQueue = None
+ _waiting = None
+ _timedOut = False
+ _greetingError = None
+
+ def _blocked(self, f, *a):
+ """
+ Block a command, if necessary.
+
+ If commands are being blocked, append information about the function
+ which sends the command to a list and return a deferred that will be
+ chained with the return value of the function when it eventually runs.
+ Otherwise, set up for subsequent commands to be blocked and return
+ L{None}.
+
+ @type f: callable
+ @param f: A function which sends a command.
+
+ @type a: L{tuple}
+ @param a: Arguments to the function.
+
+ @rtype: L{None} or L{Deferred <defer.Deferred>}
+ @return: L{None} if the command can run immediately. Otherwise,
+ a deferred that will eventually trigger with the return value of
+ the function.
+ """
+ if self._blockedQueue is not None:
+ d = defer.Deferred()
+ self._blockedQueue.append((d, f, a))
+ return d
+ self._blockedQueue = []
+ return None
+
+ def _unblock(self):
+ """
+ Send the next blocked command.
+
+ If there are no more commands in the blocked queue, set up for the next
+ command to be sent immediately.
+ """
+ if self._blockedQueue == []:
+ self._blockedQueue = None
+ elif self._blockedQueue is not None:
+ _blockedQueue = self._blockedQueue
+ self._blockedQueue = None
+
+ d, f, a = _blockedQueue.pop(0)
+ d2 = f(*a)
+ d2.chainDeferred(d)
+ # f is a function which uses _blocked (otherwise it wouldn't
+ # have gotten into the blocked queue), which means it will have
+ # re-set _blockedQueue to an empty list, so we can put the rest
+ # of the blocked queue back into it now.
+ self._blockedQueue.extend(_blockedQueue)
+
+ def sendShort(self, cmd, args):
+ """
+ Send a POP3 command to which a short response is expected.
+
+ Block all further commands from being sent until the response is
+ received. Transition the state to SHORT.
+
+ @type cmd: L{bytes}
+ @param cmd: A POP3 command.
+
+ @type args: L{bytes}
+ @param args: The command arguments.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the entire response is received.
+ On an OK response, it returns the response from the server minus
+ the status indicator. On an ERR response, it issues a server
+ error response failure with the response from the server minus the
+ status indicator.
+ """
+ d = self._blocked(self.sendShort, cmd, args)
+ if d is not None:
+ return d
+
+ if args:
+ self.sendLine(cmd + b" " + args)
+ else:
+ self.sendLine(cmd)
+ self.state = "SHORT"
+ self._waiting = defer.Deferred()
+ return self._waiting
+
+ def sendLong(self, cmd, args, consumer, xform):
+ """
+ Send a POP3 command to which a multi-line response is expected.
+
+ Block all further commands from being sent until the entire response is
+ received. Transition the state to LONG_INITIAL.
+
+ @type cmd: L{bytes}
+ @param cmd: A POP3 command.
+
+ @type args: L{bytes}
+ @param args: The command arguments.
+
+ @type consumer: callable that takes L{object}
+ @param consumer: A consumer function which should be used to put
+ the values derived by a transform function from each line of the
+ multi-line response into a list.
+
+ @type xform: L{None} or callable that takes
+ L{bytes} and returns L{object}
+ @param xform: A transform function which should be used to transform
+ each line of the multi-line response into usable values for use by
+ a consumer function. If L{None}, each line of the multi-line
+ response should be sent directly to the consumer function.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ callable that takes L{object} and fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the entire response is received.
+ On an OK response, it returns the consumer function. On an ERR
+ response, it issues a server error response failure with the
+ response from the server minus the status indicator and the
+ consumer function.
+ """
+ d = self._blocked(self.sendLong, cmd, args, consumer, xform)
+ if d is not None:
+ return d
+
+ if args:
+ self.sendLine(cmd + b" " + args)
+ else:
+ self.sendLine(cmd)
+ self.state = "LONG_INITIAL"
+ self._xform = xform
+ self._consumer = consumer
+ self._waiting = defer.Deferred()
+ return self._waiting
+
+ # Twisted protocol callback
+ def connectionMade(self):
+ """
+ Wait for a greeting from the server after the connection has been made.
+
+ Start the connection in the WELCOME state.
+ """
+ if self.timeout > 0:
+ self.setTimeout(self.timeout)
+
+ self.state = "WELCOME"
+ self._blockedQueue = []
+
+ def timeoutConnection(self):
+ """
+ Drop the connection when the server does not respond in time.
+ """
+ self._timedOut = True
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason):
+ """
+ Clean up when the connection has been lost.
+
+ When the loss of connection was initiated by the client due to a
+ timeout, the L{_timedOut} flag will be set. When it was initiated by
+ the client due to an error in the server greeting, L{_greetingError}
+ will be set to the server response minus the status indicator.
+
+ @type reason: L{Failure <twisted.python.failure.Failure>}
+ @param reason: The reason the connection was terminated.
+ """
+ if self.timeout > 0:
+ self.setTimeout(None)
+
+ if self._timedOut:
+ reason = error.TimeoutError()
+ elif self._greetingError:
+ reason = ServerErrorResponse(self._greetingError)
+
+ d = []
+ if self._waiting is not None:
+ d.append(self._waiting)
+ self._waiting = None
+ if self._blockedQueue is not None:
+ d.extend([deferred for (deferred, f, a) in self._blockedQueue])
+ self._blockedQueue = None
+ for w in d:
+ w.errback(reason)
+
+ def lineReceived(self, line):
+ """
+ Pass a received line to a state machine function and
+ transition to the next state.
+
+ @type line: L{bytes}
+ @param line: A received line.
+ """
+ if self.timeout > 0:
+ self.resetTimeout()
+
+ state = self.state
+ self.state = None
+ state = getattr(self, "state_" + state)(line) or state
+ if self.state is None:
+ self.state = state
+
+ def lineLengthExceeded(self, buffer):
+ """
+ Drop the connection when a server response exceeds the maximum line
+ length (L{LineOnlyReceiver.MAX_LENGTH}).
+
+ @type buffer: L{bytes}
+ @param buffer: A received line which exceeds the maximum line length.
+ """
+ # XXX - We need to be smarter about this
+ if self._waiting is not None:
+ waiting, self._waiting = self._waiting, None
+ waiting.errback(LineTooLong())
+ self.transport.loseConnection()
+
+ # POP3 Client state logic - don't touch this.
+ def state_WELCOME(self, line):
+ """
+ Handle server responses for the WELCOME state in which the server
+ greeting is expected.
+
+ WELCOME is the first state. The server should send one line of text
+ with a greeting and possibly an APOP challenge. Transition the state
+ to WAITING.
+
+ @type line: L{bytes}
+ @param line: A line received from the server.
+
+ @rtype: L{bytes}
+ @return: The next state.
+ """
+ code, status = _codeStatusSplit(line)
+ if code != OK:
+ self._greetingError = status
+ self.transport.loseConnection()
+ else:
+ m = self._challengeMagicRe.search(status)
+
+ if m is not None:
+ self.serverChallenge = m.group(1)
+
+ self.serverGreeting(status)
+
+ self._unblock()
+ return "WAITING"
+
+ def state_WAITING(self, line):
+ """
+ Log an error for server responses received in the WAITING state during
+ which the server is not expected to send anything.
+
+ @type line: L{bytes}
+ @param line: A line received from the server.
+ """
+ log.msg("Illegal line from server: " + repr(line))
+
+ def state_SHORT(self, line):
+ """
+ Handle server responses for the SHORT state in which the server is
+ expected to send a single line response.
+
+ Parse the response and fire the deferred which is waiting on receipt of
+ a complete response. Transition the state back to WAITING.
+
+ @type line: L{bytes}
+ @param line: A line received from the server.
+
+ @rtype: L{bytes}
+ @return: The next state.
+ """
+ deferred, self._waiting = self._waiting, None
+ self._unblock()
+ code, status = _codeStatusSplit(line)
+ if code == OK:
+ deferred.callback(status)
+ else:
+ deferred.errback(ServerErrorResponse(status))
+ return "WAITING"
+
+ def state_LONG_INITIAL(self, line):
+ """
+ Handle server responses for the LONG_INITIAL state in which the server
+ is expected to send the first line of a multi-line response.
+
+ Parse the response. On an OK response, transition the state to
+ LONG. On an ERR response, cleanup and transition the state to
+ WAITING.
+
+ @type line: L{bytes}
+ @param line: A line received from the server.
+
+ @rtype: L{bytes}
+ @return: The next state.
+ """
+ code, status = _codeStatusSplit(line)
+ if code == OK:
+ return "LONG"
+ consumer = self._consumer
+ deferred = self._waiting
+ self._consumer = self._waiting = self._xform = None
+ self._unblock()
+ deferred.errback(ServerErrorResponse(status, consumer))
+ return "WAITING"
+
+ def state_LONG(self, line):
+ """
+ Handle server responses for the LONG state in which the server is
+ expected to send a non-initial line of a multi-line response.
+
+ On receipt of the last line of the response, clean up, fire the
+ deferred which is waiting on receipt of a complete response, and
+ transition the state to WAITING. Otherwise, pass the line to the
+ transform function, if provided, and then the consumer function.
+
+ @type line: L{bytes}
+ @param line: A line received from the server.
+
+ @rtype: L{bytes}
+ @return: The next state.
+ """
+ # This is the state for each line of a long response.
+ if line == b".":
+ consumer = self._consumer
+ deferred = self._waiting
+ self._consumer = self._waiting = self._xform = None
+ self._unblock()
+ deferred.callback(consumer)
+ return "WAITING"
+ else:
+ if self._xform is not None:
+ self._consumer(self._xform(line))
+ else:
+ self._consumer(line)
+ return "LONG"
+
+ # Callbacks - override these
+ def serverGreeting(self, greeting):
+ """
+ Handle the server greeting.
+
+ @type greeting: L{bytes}
+ @param greeting: The server greeting minus the status indicator.
+ For servers implementing APOP authentication, this will contain a
+ challenge string.
+ """
+
+ # External API - call these (most of 'em anyway)
+ def startTLS(self, contextFactory=None):
+ """
+ Switch to encrypted communication using TLS.
+
+ The first step of switching to encrypted communication is obtaining
+ the server's capabilities. When that is complete, the L{_startTLS}
+ callback function continues the switching process.
+
+ @type contextFactory: L{None} or
+ L{ClientContextFactory <twisted.internet.ssl.ClientContextFactory>}
+ @param contextFactory: The context factory with which to negotiate TLS.
+ If not provided, try to create a new one.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully results in
+ L{dict} mapping L{bytes} to L{list} of L{bytes} and/or L{bytes} to
+ L{None} or fails with L{TLSError}
+ @return: A deferred which fires when the transport has been
+ secured according to the given context factory with the server
+ capabilities, or which fails with a TLS error if the transport
+ cannot be secured.
+ """
+ tls = interfaces.ITLSTransport(self.transport, None)
+ if tls is None:
+ return defer.fail(
+ TLSError(
+ "POP3Client transport does not implement "
+ "interfaces.ITLSTransport"
+ )
+ )
+
+ if contextFactory is None:
+ contextFactory = self._getContextFactory()
+
+ if contextFactory is None:
+ return defer.fail(
+ TLSError(
+ "POP3Client requires a TLS context to "
+ "initiate the STLS handshake"
+ )
+ )
+
+ d = self.capabilities()
+ d.addCallback(self._startTLS, contextFactory, tls)
+ return d
+
+ def _startTLS(self, caps, contextFactory, tls):
+ """
+ Continue the process of switching to encrypted communication.
+
+ This callback function runs after the server capabilities are received.
+
+ The next step is sending the server an STLS command to request a
+ switch to encrypted communication. When an OK response is received,
+ the L{_startedTLS} callback function completes the switch to encrypted
+ communication. Then, the new server capabilities are requested.
+
+ @type caps: L{dict} mapping L{bytes} to L{list} of L{bytes} and/or
+ L{bytes} to L{None}
+ @param caps: The server capabilities.
+
+ @type contextFactory: L{ClientContextFactory
+ <twisted.internet.ssl.ClientContextFactory>}
+ @param contextFactory: A context factory with which to negotiate TLS.
+
+ @type tls: L{ITLSTransport <interfaces.ITLSTransport>}
+ @param tls: A TCP transport that supports switching to TLS midstream.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully triggers with
+ L{dict} mapping L{bytes} to L{list} of L{bytes} and/or L{bytes} to
+ L{None} or fails with L{TLSNotSupportedError}
+ @return: A deferred which successfully fires when the response from
+ the server to the request to start TLS has been received and the
+ new server capabilities have been received or fails when the server
+ does not support TLS.
+ """
+ assert (
+ not self.startedTLS
+ ), "Client and Server are currently communicating via TLS"
+
+ if b"STLS" not in caps:
+ return defer.fail(
+ TLSNotSupportedError(
+ "Server does not support secure communication " "via TLS / SSL"
+ )
+ )
+
+ d = self.sendShort(b"STLS", None)
+ d.addCallback(self._startedTLS, contextFactory, tls)
+ d.addCallback(lambda _: self.capabilities())
+ return d
+
+ def _startedTLS(self, result, context, tls):
+ """
+ Complete the process of switching to encrypted communication.
+
+ This callback function runs after the response to the STLS command has
+ been received.
+
+ The final steps are discarding the cached capabilities and initiating
+ TLS negotiation on the transport.
+
+ @type result: L{dict} mapping L{bytes} to L{list} of L{bytes} and/or
+ L{bytes} to L{None}
+ @param result: The server capabilities.
+
+ @type context: L{ClientContextFactory
+ <twisted.internet.ssl.ClientContextFactory>}
+ @param context: A context factory with which to negotiate TLS.
+
+ @type tls: L{ITLSTransport <interfaces.ITLSTransport>}
+ @param tls: A TCP transport that supports switching to TLS midstream.
+
+ @rtype: L{dict} mapping L{bytes} to L{list} of L{bytes} and/or L{bytes}
+ to L{None}
+ @return: The server capabilities.
+ """
+ self.transport = tls
+ self.transport.startTLS(context)
+ self._capCache = None
+ self.startedTLS = True
+ return result
+
+ def _getContextFactory(self):
+ """
+ Get a context factory with which to negotiate TLS.
+
+ @rtype: L{None} or
+ L{ClientContextFactory <twisted.internet.ssl.ClientContextFactory>}
+ @return: A context factory or L{None} if TLS is not supported on the
+ client.
+ """
+ try:
+ from twisted.internet import ssl
+ except ImportError:
+ return None
+ else:
+ context = ssl.ClientContextFactory()
+ context.method = ssl.SSL.TLSv1_2_METHOD
+ return context
+
+ def login(self, username, password):
+ """
+ Log in to the server.
+
+ If APOP is available it will be used. Otherwise, if TLS is
+ available, an encrypted session will be started and plaintext
+ login will proceed. Otherwise, if L{allowInsecureLogin} is set,
+ insecure plaintext login will proceed. Otherwise,
+ L{InsecureAuthenticationDisallowed} will be raised.
+
+ The first step of logging into the server is obtaining the server's
+ capabilities. When that is complete, the L{_login} callback function
+ continues the login process.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @type password: L{bytes}
+ @param password: The password with which to log in.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes}
+ @return: A deferred which fires when the login process is complete.
+ On a successful login, it returns the server's response minus the
+ status indicator.
+ """
+ d = self.capabilities()
+ d.addCallback(self._login, username, password)
+ return d
+
+ def _login(self, caps, username, password):
+ """
+ Continue the process of logging in to the server.
+
+ This callback function runs after the server capabilities are received.
+
+ If the server provided a challenge in the greeting, proceed with an
+ APOP login. Otherwise, if the server and the transport support
+ encrypted communication, try to switch to TLS and then complete
+ the login process with the L{_loginTLS} callback function. Otherwise,
+ if insecure authentication is allowed, do a plaintext login.
+ Otherwise, fail with an L{InsecureAuthenticationDisallowed} error.
+
+ @type caps: L{dict} mapping L{bytes} to L{list} of L{bytes} and/or
+ L{bytes} to L{None}
+ @param caps: The server capabilities.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @type password: L{bytes}
+ @param password: The password with which to log in.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes}
+ @return: A deferred which fires when the login process is complete.
+ On a successful login, it returns the server's response minus the
+ status indicator.
+ """
+ if self.serverChallenge is not None:
+ return self._apop(username, password, self.serverChallenge)
+
+ tryTLS = b"STLS" in caps
+
+ # If our transport supports switching to TLS, we might want to
+ # try to switch to TLS.
+ tlsableTransport = interfaces.ITLSTransport(self.transport, None) is not None
+
+ # If our transport is not already using TLS, we might want to
+ # try to switch to TLS.
+ nontlsTransport = interfaces.ISSLTransport(self.transport, None) is None
+
+ if not self.startedTLS and tryTLS and tlsableTransport and nontlsTransport:
+ d = self.startTLS()
+
+ d.addCallback(self._loginTLS, username, password)
+ return d
+
+ elif self.startedTLS or not nontlsTransport or self.allowInsecureLogin:
+ return self._plaintext(username, password)
+ else:
+ return defer.fail(InsecureAuthenticationDisallowed())
+
+ def _loginTLS(self, res, username, password):
+ """
+ Do a plaintext login over an encrypted transport.
+
+ This callback function runs after the transport switches to encrypted
+ communication.
+
+ @type res: L{dict} mapping L{bytes} to L{list} of L{bytes} and/or
+ L{bytes} to L{None}
+ @param res: The server capabilities.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @type password: L{bytes}
+ @param password: The password with which to log in.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server accepts the username
+ and password or fails when the server rejects either. On a
+ successful login, it returns the server's response minus the
+ status indicator.
+ """
+ return self._plaintext(username, password)
+
+ def _plaintext(self, username, password):
+ """
+ Perform a plaintext login.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @type password: L{bytes}
+ @param password: The password with which to log in.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server accepts the username
+ and password or fails when the server rejects either. On a
+ successful login, it returns the server's response minus the
+ status indicator.
+ """
+ return self.user(username).addCallback(lambda r: self.password(password))
+
+ def _apop(self, username, password, challenge):
+ """
+ Perform an APOP login.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @type password: L{bytes}
+ @param password: The password with which to log in.
+
+ @type challenge: L{bytes}
+ @param challenge: A challenge string.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On a successful login, it returns the server response minus
+ the status indicator.
+ """
+ digest = md5(challenge + password).hexdigest().encode("ascii")
+ return self.apop(username, digest)
+
+ def apop(self, username, digest):
+ """
+ Send an APOP command to perform authenticated login.
+
+ This should be used in special circumstances only, when it is
+ known that the server supports APOP authentication, and APOP
+ authentication is absolutely required. For the common case,
+ use L{login} instead.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @type digest: L{bytes}
+ @param digest: The challenge response to authenticate with.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"APOP", username + b" " + digest)
+
+ def user(self, username):
+ """
+ Send a USER command to perform the first half of plaintext login.
+
+ Unless this is absolutely required, use the L{login} method instead.
+
+ @type username: L{bytes}
+ @param username: The username with which to log in.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"USER", username)
+
+ def password(self, password):
+ """
+ Send a PASS command to perform the second half of plaintext login.
+
+ Unless this is absolutely required, use the L{login} method instead.
+
+ @type password: L{bytes}
+ @param password: The plaintext password with which to authenticate.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"PASS", password)
+
+ def delete(self, index):
+ """
+ Send a DELE command to delete a message from the server.
+
+ @type index: L{int}
+ @param index: The 0-based index of the message to delete.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"DELE", b"%d" % (index + 1,))
+
+ def _consumeOrSetItem(self, cmd, args, consumer, xform):
+ """
+ Send a command to which a long response is expected and process the
+ multi-line response into a list accounting for deleted messages.
+
+ @type cmd: L{bytes}
+ @param cmd: A POP3 command to which a long response is expected.
+
+ @type args: L{bytes}
+ @param args: The command arguments.
+
+ @type consumer: L{None} or callable that takes
+ L{object}
+ @param consumer: L{None} or a function that consumes the output from
+ the transform function.
+
+ @type xform: L{None}, callable that takes
+ L{bytes} and returns 2-L{tuple} of (0) L{int}, (1) L{object},
+ or callable that takes L{bytes} and returns L{object}
+ @param xform: A function that parses a line from a multi-line response
+ and transforms the values into usable form for input to the
+ consumer function. If no consumer function is specified, the
+ output must be a message index and corresponding value. If no
+ transform function is specified, the line is used as is.
+
+ @rtype: L{Deferred <defer.Deferred>} which fires with L{list} of
+ L{object} or callable that takes L{list} of L{object}
+ @return: A deferred which fires when the entire response has been
+ received. When a consumer is not provided, the return value is a
+ list of the value for each message or L{None} for deleted messages.
+ Otherwise, it returns the consumer itself.
+ """
+ if consumer is None:
+ L = []
+ consumer = _ListSetter(L).setitem
+ return self.sendLong(cmd, args, consumer, xform).addCallback(lambda r: L)
+ return self.sendLong(cmd, args, consumer, xform)
+
+ def _consumeOrAppend(self, cmd, args, consumer, xform):
+ """
+ Send a command to which a long response is expected and process the
+ multi-line response into a list.
+
+ @type cmd: L{bytes}
+ @param cmd: A POP3 command which expects a long response.
+
+ @type args: L{bytes}
+ @param args: The command arguments.
+
+ @type consumer: L{None} or callable that takes
+ L{object}
+ @param consumer: L{None} or a function that consumes the output from the
+ transform function.
+
+ @type xform: L{None} or callable that takes
+ L{bytes} and returns L{object}
+ @param xform: A function that transforms a line from a multi-line
+ response into usable form for input to the consumer function. If
+ no transform function is specified, the line is used as is.
+
+ @rtype: L{Deferred <defer.Deferred>} which fires with L{list} of
+ 2-L{tuple} of (0) L{int}, (1) L{object} or callable that
+ takes 2-L{tuple} of (0) L{int}, (1) L{object}
+ @return: A deferred which fires when the entire response has been
+ received. When a consumer is not provided, the return value is a
+ list of the transformed lines. Otherwise, it returns the consumer
+ itself.
+ """
+ if consumer is None:
+ L = []
+ consumer = L.append
+ return self.sendLong(cmd, args, consumer, xform).addCallback(lambda r: L)
+ return self.sendLong(cmd, args, consumer, xform)
+
+ def capabilities(self, useCache=True):
+ """
+ Send a CAPA command to retrieve the capabilities supported by
+ the server.
+
+ Not all servers support this command. If the server does not
+ support this, it is treated as though it returned a successful
+ response listing no capabilities. At some future time, this may be
+ changed to instead seek out information about a server's
+ capabilities in some other fashion (only if it proves useful to do
+ so, and only if there are servers still in use which do not support
+ CAPA but which do support POP3 extensions that are useful).
+
+ @type useCache: L{bool}
+ @param useCache: A flag that determines whether previously retrieved
+ results should be used if available.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully results in
+ L{dict} mapping L{bytes} to L{list} of L{bytes} and/or L{bytes} to
+ L{None}
+ @return: A deferred which fires with a mapping of capability name to
+ parameters. For example::
+
+ C: CAPA
+ S: +OK Capability list follows
+ S: TOP
+ S: USER
+ S: SASL CRAM-MD5 KERBEROS_V4
+ S: RESP-CODES
+ S: LOGIN-DELAY 900
+ S: PIPELINING
+ S: EXPIRE 60
+ S: UIDL
+ S: IMPLEMENTATION Shlemazle-Plotz-v302
+ S: .
+
+ will be lead to a result of::
+
+ | {'TOP': None,
+ | 'USER': None,
+ | 'SASL': ['CRAM-MD5', 'KERBEROS_V4'],
+ | 'RESP-CODES': None,
+ | 'LOGIN-DELAY': ['900'],
+ | 'PIPELINING': None,
+ | 'EXPIRE': ['60'],
+ | 'UIDL': None,
+ | 'IMPLEMENTATION': ['Shlemazle-Plotz-v302']}
+ """
+ if useCache and self._capCache is not None:
+ return defer.succeed(self._capCache)
+
+ cache = {}
+
+ def consume(line):
+ tmp = line.split()
+ if len(tmp) == 1:
+ cache[tmp[0]] = None
+ elif len(tmp) > 1:
+ cache[tmp[0]] = tmp[1:]
+
+ def capaNotSupported(err):
+ err.trap(ServerErrorResponse)
+ return None
+
+ def gotCapabilities(result):
+ self._capCache = cache
+ return cache
+
+ d = self._consumeOrAppend(b"CAPA", None, consume, None)
+ d.addErrback(capaNotSupported).addCallback(gotCapabilities)
+ return d
+
+ def noop(self):
+ """
+ Send a NOOP command asking the server to do nothing but respond.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"NOOP", None)
+
+ def reset(self):
+ """
+ Send a RSET command to unmark any messages that have been flagged
+ for deletion on the server.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"RSET", None)
+
+ def retrieve(self, index, consumer=None, lines=None):
+ """
+ Send a RETR or TOP command to retrieve all or part of a message from
+ the server.
+
+ @type index: L{int}
+ @param index: A 0-based message index.
+
+ @type consumer: L{None} or callable that takes
+ L{bytes}
+ @param consumer: A function which consumes each transformed line from a
+ multi-line response as it is received.
+
+ @type lines: L{None} or L{int}
+ @param lines: If specified, the number of lines of the message to be
+ retrieved. Otherwise, the entire message is retrieved.
+
+ @rtype: L{Deferred <defer.Deferred>} which fires with L{list} of
+ L{bytes}, or callable that takes 2-L{tuple} of (0) L{int},
+ (1) L{object}
+ @return: A deferred which fires when the entire response has been
+ received. When a consumer is not provided, the return value is a
+ list of the transformed lines. Otherwise, it returns the consumer
+ itself.
+ """
+ idx = b"%d" % (index + 1,)
+ if lines is None:
+ return self._consumeOrAppend(b"RETR", idx, consumer, _dotUnquoter)
+
+ return self._consumeOrAppend(
+ b"TOP", b"%b %d" % (idx, lines), consumer, _dotUnquoter
+ )
+
+ def stat(self):
+ """
+ Send a STAT command to get information about the size of the mailbox.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ a 2-tuple of (0) L{int}, (1) L{int} or fails with
+ L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the number of
+ messages in the mailbox and the size of the mailbox in octets.
+ On an ERR response, the deferred fails with a server error
+ response failure.
+ """
+ return self.sendShort(b"STAT", None).addCallback(_statXform)
+
+ def listSize(self, consumer=None):
+ """
+ Send a LIST command to retrieve the sizes of all messages on the
+ server.
+
+ @type consumer: L{None} or callable that takes
+ 2-L{tuple} of (0) L{int}, (1) L{int}
+ @param consumer: A function which consumes the 0-based message index
+ and message size derived from the server response.
+
+ @rtype: L{Deferred <defer.Deferred>} which fires L{list} of L{int} or
+ callable that takes 2-L{tuple} of (0) L{int}, (1) L{int}
+ @return: A deferred which fires when the entire response has been
+ received. When a consumer is not provided, the return value is a
+ list of message sizes. Otherwise, it returns the consumer itself.
+ """
+ return self._consumeOrSetItem(b"LIST", None, consumer, _listXform)
+
+ def listUID(self, consumer=None):
+ """
+ Send a UIDL command to retrieve the UIDs of all messages on the server.
+
+ @type consumer: L{None} or callable that takes
+ 2-L{tuple} of (0) L{int}, (1) L{bytes}
+ @param consumer: A function which consumes the 0-based message index
+ and UID derived from the server response.
+
+ @rtype: L{Deferred <defer.Deferred>} which fires with L{list} of
+ L{object} or callable that takes 2-L{tuple} of (0) L{int},
+ (1) L{bytes}
+ @return: A deferred which fires when the entire response has been
+ received. When a consumer is not provided, the return value is a
+ list of message sizes. Otherwise, it returns the consumer itself.
+ """
+ return self._consumeOrSetItem(b"UIDL", None, consumer, _uidXform)
+
+ def quit(self):
+ """
+ Send a QUIT command to disconnect from the server.
+
+ @rtype: L{Deferred <defer.Deferred>} which successfully fires with
+ L{bytes} or fails with L{ServerErrorResponse}
+ @return: A deferred which fires when the server response is received.
+ On an OK response, the deferred succeeds with the server
+ response minus the status indicator. On an ERR response, the
+ deferred fails with a server error response failure.
+ """
+ return self.sendShort(b"QUIT", None)
+
+
+__all__: List[str] = []