# HG changeset patch # Parent 73b56f900045dd9f45d28854a9691852cb41256c diff --git a/Doc/library/poplib.rst b/Doc/library/poplib.rst --- a/Doc/library/poplib.rst +++ b/Doc/library/poplib.rst @@ -97,6 +97,12 @@ An :class:`POP3` instance has the follow Returns the greeting string sent by the POP3 server. +.. method:: POP3.capa() + + Query the server's capabilities as specified in RFC 2449. + Returns a dictionary in the form {'name': ['param'...]} + + .. method:: POP3.user(username) Send user command, response should indicate that a password is required. diff --git a/Lib/poplib.py b/Lib/poplib.py --- a/Lib/poplib.py +++ b/Lib/poplib.py @@ -55,6 +55,7 @@ class POP3: APOP name digest apop(name, digest) TOP msg n top(msg, n) UIDL [msg] uidl(msg = None) + CAPA capa() Raises one exception: 'error_proto'. @@ -322,6 +323,35 @@ class POP3: return self._shortcmd('UIDL %s' % which) return self._longcmd('UIDL') + + def capa(self): + """Return server capabilities (RFC 2449) as a dictionary + >>> c=poplib.POP3('localhost') + >>> c.capa() + {'IMPLEMENTATION': ['Cyrus', 'POP3', 'server', 'v2.2.12'], + 'TOP': [], 'LOGIN-DELAY': ['0'], 'AUTH-RESP-CODE': [], + 'EXPIRE': ['NEVER'], 'USER': [], 'STLS': [], 'PIPELINING': [], + 'UIDL': [], 'RESP-CODES': []} + >>> + + Really, according to RFC 2449, the cyrus folks should avoid + having the implementation splitted into multiple arguments... + """ + def _parsecap(line): + lst = line.split() + return lst[0], lst[1:] + + caps={} + try: + resp = self._longcmd('CAPA') + rawcaps = resp[1] + for capline in rawcaps: + capnm, capargs = _parsecap(capline) + caps[capnm] = capargs + except error_proto as _err: + raise error_proto('-ERR CAPA not supported by server') + return caps + try: import ssl except ImportError: diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -33,6 +33,8 @@ line3\r\n\ class DummyPOP3Handler(asynchat.async_chat): + CAPAS = {'UIDL': [], 'IMPLEMENTATION': ['python-testlib-pop-server']} + def __init__(self, conn): asynchat.async_chat.__init__(self, conn) self.set_terminator(b"\r\n") @@ -112,6 +114,17 @@ class DummyPOP3Handler(asynchat.async_ch self.push('+OK closing.') self.close_when_done() + def cmd_capa(self, arg): + self.push('+OK Capability list follows') + if self.CAPAS: + for cap, params in self.CAPAS.items(): + _ln = [cap] + if params: + _ln.extend(params) + self.push(' '.join(_ln)) + self.push('.') + + class DummyPOP3Server(asyncore.dispatcher, threading.Thread): @@ -232,6 +245,10 @@ class TestPOP3Class(TestCase): self.client.uidl() self.client.uidl('foo') + def test_capa(self): + capa = self.client.capa() + self.assertTrue(b'IMPLEMENTATION' in capa.keys()) + def test_quit(self): resp = self.client.quit() self.assertTrue(resp)