https://github.com/python/cpython/commit/807e46ca6f62a92c5d06da63bf2b8628191997c9
commit: 807e46ca6f62a92c5d06da63bf2b8628191997c9
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-29T18:14:35Z
summary:

[3.14] gh-155775: Do not consume the CAPABILITY response in imaplib (GH-155789) 
(GH-156586)

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.
(cherry picked from commit 9111820ebb6243c961ea4cedf1cdf7e248701209)

Co-authored-by: Serhiy Storchaka <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-08-14-14-10-00.gh-issue-155775.Kq3vTx.rst
M Doc/library/imaplib.rst
M Lib/imaplib.py
M Lib/test/test_imaplib.py

diff --git a/Doc/library/imaplib.rst b/Doc/library/imaplib.rst
index 131bc0101fb5a50..2c9aa8d714d9f1b 100644
--- a/Doc/library/imaplib.rst
+++ b/Doc/library/imaplib.rst
@@ -706,6 +706,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 4c0aefd082f8dce..896006741da8ab0 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -273,7 +273,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,))
@@ -1250,10 +1251,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 a91de7230b6fab1..0c11e23d7607599 100644
--- a/Lib/test/test_imaplib.py
+++ b/Lib/test/test_imaplib.py
@@ -900,6 +900,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')
 
@@ -918,6 +920,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,
@@ -931,6 +935,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`.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to