From d0921efb665aff26b378f495e5ff84f7e3fe649d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 5 Jul 2026 18:25:36 +0300 Subject: [PATCH] gh-143921: Narrow the control character check in imaplib commands (GH-153067) Only NUL, CR and LF are rejected now. Other control characters are valid in quoted strings and can occur in mailbox names returned by the server, so they are now accepted and sent quoted. Co-authored-by: Claude Fable 5 --- Lib/imaplib.py | 6 ++++-- Lib/test/test_imaplib.py | 16 +++++++++++++--- ...026-07-05-10-24-30.gh-issue-143921.wQx3Tn.rst | 4 ++++ 3 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-05-10-24-30.gh-issue-143921.wQx3Tn.rst diff --git a/Lib/imaplib.py b/Lib/imaplib.py index 085c8d9a5f22..8e271209a664 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -129,7 +129,9 @@ Untagged_status = re.compile( # We compile these in _mode_xxx. _Literal = br'.*{(?P\d+)}$' _Untagged_status = br'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?' -_control_chars = re.compile(b'[\x00-\x1F\x7F]') +# Only NUL, CR and LF are unsafe (they cannot be represented even in +# a quoted string); other control characters are sent quoted. +_control_chars = re.compile(b'[\x00\r\n]') _non_astring_char = re.compile(br'[(){ \x00-\x1f\x7f-\xff%*\\"]') _non_list_char = re.compile(br'[(){ \x00-\x1f\x7f-\xff\\"]') _quoted = re.compile(br'"(?:[^"\\]|\\.)*+"') @@ -1170,7 +1172,7 @@ class IMAP4: if isinstance(arg, str): arg = bytes(arg, self._encoding) if _control_chars.search(arg): - raise ValueError("Control characters not allowed in commands") + raise ValueError("NUL, CR and LF not allowed in commands") data = data + b' ' + arg literal = self.literal diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index b186dca27aaa..ef283267ff08 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1751,10 +1751,20 @@ class NewIMAPTestsMixin: client.NONEXISTENT def test_control_characters(self): - client, _ = self._setup(SimpleIMAPHandler) - for c0 in support.control_characters_c0(): + client, server = self._setup(SimpleIMAPHandler) + client.login('user', 'pass') + for c in '\0\r\n': with self.assertRaises(ValueError): - client.login(f'user{c0}', 'pass') + client.select(f'a{c}b') + # Other control characters are valid in a quoted string and can + # occur in mailbox names returned by the server, so the client + # must be able to send them back. + for c in support.control_characters_c0(): + if c in '\0\r\n': + continue + typ, _ = client.select(f'a{c}b') + self.assertEqual(typ, 'OK') + self.assertEqual(server.is_selected, [f'"a{c}b"']) # property tests diff --git a/Misc/NEWS.d/next/Library/2026-07-05-10-24-30.gh-issue-143921.wQx3Tn.rst b/Misc/NEWS.d/next/Library/2026-07-05-10-24-30.gh-issue-143921.wQx3Tn.rst new file mode 100644 index 000000000000..04e8bd6d6f9d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-05-10-24-30.gh-issue-143921.wQx3Tn.rst @@ -0,0 +1,4 @@ +Narrow the control character check in :mod:`imaplib` commands: only NUL, CR +and LF are now rejected. Other control characters are valid in quoted +strings and can occur in mailbox names returned by the server, so they are +now accepted and sent quoted. -- 2.47.3