Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
br'"')
# Literal is no longer used; kept for backward compatibility.
Literal = re.compile(br'.*{(?P<size>\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

@andreasg123 andreasg123 Nov 3, 2018

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. MapCRLF is only used in one line, as far as I can tell, to normalize all line endings:

literal = MapCRLF.sub(CRLF, message)

With the new pattern, b'ab\r\ncd\r\n' is replaced with b'ab\r\n\r\ncd\r\n\r\n'. The old pattern leaves that string unchanged and correctly replaces b'ab\ncd\n' with b'ab\r\ncd\r\n'.

Also, that "normalization" seems to be controversial: https://bugs.python.org/issue5430

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out - I guess I should just take out the substitution, and add a test for it, so as to fix bpo5430 as well.

# code, even though it violates the RFC. Popular IMAP servers such as Gmail
# allow flags with ']', and there are programs (including imaplib!) that can
Expand All @@ -128,6 +128,8 @@
_Literal = br'.*{(?P<size>\d+)}$'
_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'

_Atom_Specials = re.compile(r'[\x00-\x1F\(\)\{ %\*"\\\]]')
_Quoted_Invalid = re.compile(r'[\r\n]')


class IMAP4:
Expand All @@ -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
Expand Down Expand Up @@ -585,10 +584,8 @@ def login(self, user, password):
"""Identify client using plaintext password.

(typ, [data]) = <instance>.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'
Expand Down Expand Up @@ -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]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it is removing the brackets, which seems undesirable. Compare with the original _checkquote implementation removed in commit f241afa.

elif _Atom_Specials.search(arg):
arg = self._quote(arg)
arg = bytes(arg, self._encoding)
data = data + b' ' + arg

Expand Down
46 changes: 46 additions & 0 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down