From 25cee19beb3117f834100cf29cff625c98d0da29 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 23 Nov 2012 20:07:39 +0100 Subject: [PATCH] Issue #4473: Add a POP3.capa() method to query the capabilities advertised by the POP3 server. Patch by Lorenzo Catucci. --- Doc/library/poplib.rst | 8 ++++++++ Lib/poplib.py | 30 ++++++++++++++++++++++++++++++ Lib/test/test_poplib.py | 16 ++++++++++++++++ Misc/NEWS | 3 +++ 4 files changed, 57 insertions(+) diff --git a/Doc/library/poplib.rst b/Doc/library/poplib.rst index 01f680e0a7a..2eb3b72d2ef 100644 --- a/Doc/library/poplib.rst +++ b/Doc/library/poplib.rst @@ -97,6 +97,14 @@ An :class:`POP3` instance has the following methods: 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'...]}``. + + .. versionadded:: 3.4 + + .. 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 index 094aaa11b3f..ccb28e58d0a 100644 --- 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.decode('ascii').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 index c0929a06b3a..8fe0c4cb099 100644 --- 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,16 @@ class DummyPOP3Handler(asynchat.async_chat): 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 +244,10 @@ class TestPOP3Class(TestCase): self.client.uidl() self.client.uidl('foo') + def test_capa(self): + capa = self.client.capa() + self.assertTrue('IMPLEMENTATION' in capa.keys()) + def test_quit(self): resp = self.client.quit() self.assertTrue(resp) diff --git a/Misc/NEWS b/Misc/NEWS index 917f4d54967..7ddb33b33ce 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -138,6 +138,9 @@ Core and Builtins Library ------- +- Issue #4473: Add a POP3.capa() method to query the capabilities advertised + by the POP3 server. Patch by Lorenzo Catucci. + - Issue #4473: Ensure the socket is shutdown cleanly in POP3.close(). Patch by Lorenzo Catucci.