gh-155775: Do not consume the CAPABILITY response in imaplib (GH-155789)
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) <noreply@anthropic.com>diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst
index 910b0f0..7456836 100644
--- a/Doc/library/imaplib.rst
+++ b/Doc/library/imaplib.rst
@@ -907,6 +907,20 @@
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 24d3a27..977d6bd 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 d97da39..da63da5 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 0000000..309421a
--- /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`.