From 069bf7ef40332f6ca347f56d9989f3fbea863421 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 14 Aug 2026 15:17:48 +0300 Subject: [PATCH] gh-155775: Do not consume the CAPABILITY response in imaplib It can again be read with response('CAPABILITY') after LOGIN and AUTHENTICATE. The capabilities in the greeting are still consumed. Also document the capabilities attribute. Co-Authored-By: Claude Opus 5 (1M context) --- Doc/library/imaplib.rst | 14 ++++++++++++++ Lib/imaplib.py | 11 ++++++++--- Lib/test/test_imaplib.py | 6 ++++++ .../2026-08-14-14-10-00.gh-issue-155775.Kq3vTx.rst | 4 ++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-14-14-10-00.gh-issue-155775.Kq3vTx.rst diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst index 910b0f00c0e7de5..745683673ccf507 100644 --- a/Doc/library/imaplib.rst +++ b/Doc/library/imaplib.rst @@ -907,6 +907,20 @@ An :class:`IMAP4` instance has the following methods: The following attributes are defined on instances of :class:`IMAP4`: +.. attribute:: IMAP4.capabilities + + A tuple of the capabilities advertised by the server, in upper case. + + It is set when the connection is established, + and refreshed after a successful :meth:`~IMAP4.login`, + :meth:`~IMAP4.authenticate` or :meth:`~IMAP4.starttls`, + because the server can advertise different capabilities + in different connection states. + + .. versionchanged:: 3.14.7 + Refreshed after :meth:`~IMAP4.login` and :meth:`~IMAP4.authenticate`. + + .. attribute:: IMAP4.PROTOCOL_VERSION The most recent supported protocol in the ``CAPABILITY`` response from the diff --git a/Lib/imaplib.py b/Lib/imaplib.py index 24d3a27f21d2d19..977d6bdb1c3d86b 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -366,7 +366,8 @@ def _connect(self): self._encoding, 'replace') raise self.error('invalid greeting: ' + greeting) - self._refresh_capabilities() + # The greeting is not a response to a command. + self._refresh_capabilities(consume=True) if __debug__: if self.debug >= 3: self._mesg('CAPABILITIES: %r' % (self.capabilities,)) @@ -1509,10 +1510,14 @@ def _get_capabilities(self): self.capabilities = tuple(dat.split()) - def _refresh_capabilities(self): + def _refresh_capabilities(self, consume=False): # Use a CAPABILITY response sent by the server, or ask for it. + # Unless it is consumed, the response can still be read with + # response('CAPABILITY'). if 'CAPABILITY' in self.untagged_responses: - dat = self.untagged_responses.pop('CAPABILITY')[-1] + dat = self.untagged_responses['CAPABILITY'][-1] + if consume: + del self.untagged_responses['CAPABILITY'] self.capabilities = tuple(str(dat, self._encoding).upper().split()) else: self._get_capabilities() diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index d97da398803681d..da63da54e8ef733 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1069,6 +1069,8 @@ def cmd_ENABLE(self, tag, args): client.login('user', 'pass') self.assertIn('ENABLE', client.capabilities) self.assertIn('UTF8=ACCEPT', client.capabilities) + self.assertEqual(client.response('CAPABILITY'), + ('CAPABILITY', [b'IMAP4rev1 ENABLE UTF8=ACCEPT'])) typ, _ = client.enable('UTF8=ACCEPT') self.assertEqual(typ, 'OK') @@ -1087,6 +1089,8 @@ def cmd_AUTHENTICATE(self, tag, args): self.assertNotIn('ENABLE', client.capabilities) client.authenticate('MYAUTH', lambda x: b'fake') self.assertIn('ENABLE', client.capabilities) + self.assertEqual(client.response('CAPABILITY'), + ('CAPABILITY', [b'IMAP4rev1 ENABLE'])) def test_greeting_capabilities(self): # Capabilities advertised in the greeting are used directly, @@ -1100,6 +1104,8 @@ def cmd_CAPABILITY(self, tag, args): client, server = self._setup(GreetingHandler) self.assertEqual(client.capabilities, ('IMAP4REV1', 'ENABLE')) self.assertFalse(getattr(server, 'capability_queried', False)) + # The greeting is not a response to a command, so it is consumed. + self.assertEqual(client.response('CAPABILITY'), ('CAPABILITY', [None])) def test_login_requery_capabilities(self): # If the server does not advertise capabilities after login, diff --git a/Misc/NEWS.d/next/Library/2026-08-14-14-10-00.gh-issue-155775.Kq3vTx.rst b/Misc/NEWS.d/next/Library/2026-08-14-14-10-00.gh-issue-155775.Kq3vTx.rst new file mode 100644 index 000000000000000..309421ae08240f8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-14-14-10-00.gh-issue-155775.Kq3vTx.rst @@ -0,0 +1,4 @@ +Fix a regression in :mod:`imaplib` introduced in the fix for :gh:`63121`: +refreshing the capabilities after a successful :meth:`~imaplib.IMAP4.login` +or :meth:`~imaplib.IMAP4.authenticate` consumed the ``CAPABILITY`` response, +so it could no longer be read with :meth:`~imaplib.IMAP4.response`.