From e11c082c1229fba3aaf486d30b67323be929c5a1 Mon Sep 17 00:00:00 2001 From: Alexander Harkness Date: Thu, 5 Apr 2018 21:06:48 +0100 Subject: [PATCH] imaplib: All string arguments are now quoted when necessary. Additionally, raise an error when given CRLF in arguments. Add tests for the above behaviour. --- Lib/imaplib.py | 25 ++++++++++++---------- Lib/test/test_imaplib.py | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/Lib/imaplib.py b/Lib/imaplib.py index e1cece0b283f2be..a623168579bb8bb 100644 --- a/Lib/imaplib.py +++ b/Lib/imaplib.py @@ -110,7 +110,7 @@ br'"') # Literal is no longer used; kept for backward compatibility. Literal = re.compile(br'.*{(?P\d+)}$', re.ASCII) -MapCRLF = re.compile(br'\r\n|\r|\n') +MapCRLF = re.compile(br'[\r\n]') # We no longer exclude the ']' character from the data portion of the response # code, even though it violates the RFC. Popular IMAP servers such as Gmail # allow flags with ']', and there are programs (including imaplib!) that can @@ -128,6 +128,8 @@ _Literal = br'.*{(?P\d+)}$' _Untagged_status = br'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?' +_Atom_Specials = re.compile(r'[\x00-\x1F\(\)\{ %\*"\\\]]') +_Quoted_Invalid = re.compile(r'[\r\n]') class IMAP4: @@ -144,13 +146,10 @@ class IMAP4: All arguments to commands are converted to strings, except for AUTHENTICATE, and the last argument to APPEND which is passed as - an IMAP4 literal. If necessary (the string contains any - non-printing characters or white-space and isn't enclosed with - either parentheses or double quotes) each string is quoted. - However, the 'password' argument to the LOGIN command is always - quoted. If you want to avoid having an argument string quoted - (eg: the 'flags' argument to STORE) then enclose the string in - parentheses (eg: "(\Deleted)"). + an IMAP4 literal. If necessary, each string is quoted. If you + want to avoid having an argument string quoted (eg: the 'flags' + argument to STORE) then enclose the string in parentheses + (eg: "(\Deleted)"). Each command returns a tuple: (type, [data, ...]) where 'type' is usually 'OK' or 'NO', and 'data' is either the text from the @@ -585,10 +584,8 @@ def login(self, user, password): """Identify client using plaintext password. (typ, [data]) = .login(user, password) - - NB: 'password' will be quoted. """ - typ, dat = self._simple_command('LOGIN', user, self._quote(password)) + typ, dat = self._simple_command('LOGIN', user, password) if typ != 'OK': raise self.error(dat[-1]) self.state = 'AUTH' @@ -952,6 +949,12 @@ def _command(self, name, *args): for arg in args: if arg is None: continue if isinstance(arg, str): + if _Quoted_Invalid.search(arg): + raise self.error('illegal cr or lf in argument') + if len(arg) > 2 and [arg[0], arg[-1]] == ['(', ')']: + arg = arg[1:-1] + elif _Atom_Specials.search(arg): + arg = self._quote(arg) arg = bytes(arg, self._encoding) data = data + b' ' + arg diff --git a/Lib/test/test_imaplib.py b/Lib/test/test_imaplib.py index f16bacd0006c876..149c937a757393f 100644 --- a/Lib/test/test_imaplib.py +++ b/Lib/test/test_imaplib.py @@ -242,6 +242,52 @@ def cmd_CAPABILITY(self, tag, args): self.assertRaises(imaplib.IMAP4.abort, self.imap_class, *server.server_address) + def test_create_quoted(self): + # https://bugs.python.org/issue13940 + class CreateHandler(SimpleIMAPHandler): + def cmd_CREATE(self, tag, args): + if ' '.join(args) == '"quoted name with spaces and escaped \\" \\\\ specials"': + self._send_tagged(tag, 'OK', 'CREATE completed') + return self._send_tagged(tag, 'BAD', args[0]) + client, server = self._setup(CreateHandler) + client.state = 'AUTH' + typ, data = client.create('quoted name with spaces and escaped " \\ specials') + self.assertEqual(typ, 'OK') + self.assertEqual(data[0], b'CREATE completed') + + def test_create_unquoted(self): + # https://bugs.python.org/issue13940 + class CreateHandler(SimpleIMAPHandler): + def cmd_CREATE(self, tag, args): + if args[0] == 'unquoted-name-with-no-sp3cials': + self._send_tagged(tag, 'OK', 'CREATE completed') + return self._send_tagged(tag, 'BAD', args[0]) + client, server = self._setup(CreateHandler) + client.state = 'AUTH' + typ, data = client.create('unquoted-name-with-no-sp3cials') + self.assertEqual(typ, 'OK') + self.assertEqual(data[0], b'CREATE completed') + + def test_create_force_unquoted(self): + # https://bugs.python.org/issue13940 + class CreateHandler(SimpleIMAPHandler): + def cmd_CREATE(self, tag, args): + if ' '.join(args) == 'unquoted name with spaces and " \\ specials': + self._send_tagged(tag, 'OK', 'CREATE completed') + return self._send_tagged(tag, 'BAD', args[0]) + client, server = self._setup(CreateHandler) + client.state = 'AUTH' + typ, data = client.create('(unquoted name with spaces and " \\ specials)') + self.assertEqual(typ, 'OK') + self.assertEqual(data[0], b'CREATE completed') + + def test_create_crlf(self): + client, server = self._setup(SimpleIMAPHandler) + client.state = 'AUTH' + with self.assertRaisesRegex(imaplib.IMAP4.error, + 'illegal cr or lf in argument'): + typ, data = client.create('name with CR and LF \n\r') + def test_enable_raises_error_if_not_AUTH(self): class EnableHandler(SimpleIMAPHandler): capabilities = 'AUTH ENABLE UTF8=ACCEPT'