# We compile these in _mode_xxx.
_Literal = br'.*{(?P<size>\d+)}$'
_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
-_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'"(?:[^"\\]|\\.)*+"')
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
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
--- /dev/null
+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.