From 7f613c03b1f9e08dd2d77497449df839db77c54c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 5 Jul 2026 09:25:51 +0300 Subject: [PATCH] gh-143921: Narrow the control character check in imaplib commands 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 a871e509b0d82c6..c9b401675c514ad 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -129,7 +129,9 @@ # 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 @@ def _command(self, name, *args): 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 5d9ca170ea59fda..f0086db05ef3cd4 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -1741,10 +1741,20 @@ def test_xatom(self): self.assertEqual(server.args, ['arg1', 'arg2']) 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 000000000000000..04e8bd6d6f9d6cb --- /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.