From ed1bf54efeeaae664c92affe164b5b6f3e95f177 Mon Sep 17 00:00:00 2001 From: Shamil Abdulaev Date: Tue, 18 Nov 2025 14:08:17 +0300 Subject: [PATCH] gh-45154: Fix imaplib handling of READ-ONLY mailboxes --- Lib/imaplib.py | 22 ++++++++----------- Lib/test/test_imaplib.py | 15 +++++++++++++ ...5-11-18-10-44-46.gh-issue-45154.aBcDe1.rst | 3 +++ 3 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-11-18-10-44-46.gh-issue-45154.aBcDe1.rst diff --git a/Lib/imaplib.py b/Lib/imaplib.py index c176736548188c..c235042d64b333 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -166,14 +166,15 @@ class IMAP4: Errors raise the exception class .error(""). IMAP4 server errors raise .abort(""), - which is a sub-class of 'error'. Mailbox status changes - from READ-WRITE to READ-ONLY raise the exception class - .readonly(""), which is a sub-class of 'abort'. + which is a sub-class of 'error'. + + When the server returns a READ-ONLY response code, the is_readonly + attribute is set to True. Applications should check this attribute + to determine mailbox access level. "error" exceptions imply a program error. "abort" exceptions imply the connection should be reset, and the command re-tried. - "readonly" exceptions imply the command should be re-tried. Note: to use this module, you must read the RFCs pertaining to the IMAP4 protocol, as the semantics of the arguments to each IMAP4 @@ -860,12 +861,8 @@ def select(self, mailbox='INBOX', readonly=False): self.state = 'AUTH' # Might have been 'SELECTED' return typ, dat self.state = 'SELECTED' - if 'READ-ONLY' in self.untagged_responses \ - and not readonly: - if __debug__: - if self.debug >= 1: - self._dump_ur(self.untagged_responses) - raise self.readonly('%s is not writable' % mailbox) + if 'READ-ONLY' in self.untagged_responses: + self.is_readonly = True return typ, self.untagged_responses.get('EXISTS', [None]) @@ -1094,9 +1091,8 @@ def _command(self, name, *args): if typ in self.untagged_responses: del self.untagged_responses[typ] - if 'READ-ONLY' in self.untagged_responses \ - and not self.is_readonly: - raise self.readonly('mailbox status changed to READ-ONLY') + if 'READ-ONLY' in self.untagged_responses: + self.is_readonly = True tag = self._new_tag() name = bytes(name, self._encoding) diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index 430fa71fa29f59..c870c5ead4ae1b 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -657,6 +657,21 @@ def test_unselect(self): self.assertEqual(data[0], b'Returned to authenticated state. (Success)') self.assertEqual(client.state, 'AUTH') + def test_select_readonly_mailbox(self): + class ReadOnlyHandler(SimpleIMAPHandler): + def cmd_SELECT(self, tag, args): + self.server.is_selected = True + self._send_line(b'* 1 EXISTS') + self._send_line(b'* OK [READ-ONLY] Mailbox is read-only') + self._send_tagged(tag, 'OK', '[READ-ONLY] SELECT completed') + client, _ = self._setup(ReadOnlyHandler) + client.login('user', 'pass') + self.assertFalse(client.is_readonly) + typ, data = client.select('INBOX') + self.assertEqual(typ, 'OK') + self.assertTrue(client.is_readonly) + self.assertEqual(client.state, 'SELECTED') + # property tests def test_file_property_should_not_be_accessed(self): diff --git a/Misc/NEWS.d/next/Library/2025-11-18-10-44-46.gh-issue-45154.aBcDe1.rst b/Misc/NEWS.d/next/Library/2025-11-18-10-44-46.gh-issue-45154.aBcDe1.rst new file mode 100644 index 00000000000000..674f821b86dda3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-11-18-10-44-46.gh-issue-45154.aBcDe1.rst @@ -0,0 +1,3 @@ +Fix :mod:`imaplib` to handle mailboxes with ACL rights like ``lrs`` correctly +by setting ``is_readonly`` flag instead of raising exception. Patched by Shamil +Abdulaev.