Skip to content

Commit e11c082

Browse files
committed
imaplib: All string arguments are now quoted when necessary.
Additionally, raise an error when given CRLF in arguments. Add tests for the above behaviour.
1 parent 7494091 commit e11c082

2 files changed

Lines changed: 60 additions & 11 deletions

File tree

Lib/imaplib.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
br'"')
111111
# Literal is no longer used; kept for backward compatibility.
112112
Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
113-
MapCRLF = re.compile(br'\r\n|\r|\n')
113+
MapCRLF = re.compile(br'[\r\n]')
114114
# We no longer exclude the ']' character from the data portion of the response
115115
# code, even though it violates the RFC. Popular IMAP servers such as Gmail
116116
# allow flags with ']', and there are programs (including imaplib!) that can
@@ -128,6 +128,8 @@
128128
_Literal = br'.*{(?P<size>\d+)}$'
129129
_Untagged_status = br'\* (?P<data>\d+) (?P<type>[A-Z-]+)( (?P<data2>.*))?'
130130

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

132134

133135
class IMAP4:
@@ -144,13 +146,10 @@ class IMAP4:
144146
145147
All arguments to commands are converted to strings, except for
146148
AUTHENTICATE, and the last argument to APPEND which is passed as
147-
an IMAP4 literal. If necessary (the string contains any
148-
non-printing characters or white-space and isn't enclosed with
149-
either parentheses or double quotes) each string is quoted.
150-
However, the 'password' argument to the LOGIN command is always
151-
quoted. If you want to avoid having an argument string quoted
152-
(eg: the 'flags' argument to STORE) then enclose the string in
153-
parentheses (eg: "(\Deleted)").
149+
an IMAP4 literal. If necessary, each string is quoted. If you
150+
want to avoid having an argument string quoted (eg: the 'flags'
151+
argument to STORE) then enclose the string in parentheses
152+
(eg: "(\Deleted)").
154153
155154
Each command returns a tuple: (type, [data, ...]) where 'type'
156155
is usually 'OK' or 'NO', and 'data' is either the text from the
@@ -585,10 +584,8 @@ def login(self, user, password):
585584
"""Identify client using plaintext password.
586585
587586
(typ, [data]) = <instance>.login(user, password)
588-
589-
NB: 'password' will be quoted.
590587
"""
591-
typ, dat = self._simple_command('LOGIN', user, self._quote(password))
588+
typ, dat = self._simple_command('LOGIN', user, password)
592589
if typ != 'OK':
593590
raise self.error(dat[-1])
594591
self.state = 'AUTH'
@@ -952,6 +949,12 @@ def _command(self, name, *args):
952949
for arg in args:
953950
if arg is None: continue
954951
if isinstance(arg, str):
952+
if _Quoted_Invalid.search(arg):
953+
raise self.error('illegal cr or lf in argument')
954+
if len(arg) > 2 and [arg[0], arg[-1]] == ['(', ')']:
955+
arg = arg[1:-1]
956+
elif _Atom_Specials.search(arg):
957+
arg = self._quote(arg)
955958
arg = bytes(arg, self._encoding)
956959
data = data + b' ' + arg
957960

Lib/test/test_imaplib.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,52 @@ def cmd_CAPABILITY(self, tag, args):
242242
self.assertRaises(imaplib.IMAP4.abort, self.imap_class,
243243
*server.server_address)
244244

245+
def test_create_quoted(self):
246+
# https://bugs.python.org/issue13940
247+
class CreateHandler(SimpleIMAPHandler):
248+
def cmd_CREATE(self, tag, args):
249+
if ' '.join(args) == '"quoted name with spaces and escaped \\" \\\\ specials"':
250+
self._send_tagged(tag, 'OK', 'CREATE completed')
251+
return self._send_tagged(tag, 'BAD', args[0])
252+
client, server = self._setup(CreateHandler)
253+
client.state = 'AUTH'
254+
typ, data = client.create('quoted name with spaces and escaped " \\ specials')
255+
self.assertEqual(typ, 'OK')
256+
self.assertEqual(data[0], b'CREATE completed')
257+
258+
def test_create_unquoted(self):
259+
# https://bugs.python.org/issue13940
260+
class CreateHandler(SimpleIMAPHandler):
261+
def cmd_CREATE(self, tag, args):
262+
if args[0] == 'unquoted-name-with-no-sp3cials':
263+
self._send_tagged(tag, 'OK', 'CREATE completed')
264+
return self._send_tagged(tag, 'BAD', args[0])
265+
client, server = self._setup(CreateHandler)
266+
client.state = 'AUTH'
267+
typ, data = client.create('unquoted-name-with-no-sp3cials')
268+
self.assertEqual(typ, 'OK')
269+
self.assertEqual(data[0], b'CREATE completed')
270+
271+
def test_create_force_unquoted(self):
272+
# https://bugs.python.org/issue13940
273+
class CreateHandler(SimpleIMAPHandler):
274+
def cmd_CREATE(self, tag, args):
275+
if ' '.join(args) == 'unquoted name with spaces and " \\ specials':
276+
self._send_tagged(tag, 'OK', 'CREATE completed')
277+
return self._send_tagged(tag, 'BAD', args[0])
278+
client, server = self._setup(CreateHandler)
279+
client.state = 'AUTH'
280+
typ, data = client.create('(unquoted name with spaces and " \\ specials)')
281+
self.assertEqual(typ, 'OK')
282+
self.assertEqual(data[0], b'CREATE completed')
283+
284+
def test_create_crlf(self):
285+
client, server = self._setup(SimpleIMAPHandler)
286+
client.state = 'AUTH'
287+
with self.assertRaisesRegex(imaplib.IMAP4.error,
288+
'illegal cr or lf in argument'):
289+
typ, data = client.create('name with CR and LF \n\r')
290+
245291
def test_enable_raises_error_if_not_AUTH(self):
246292
class EnableHandler(SimpleIMAPHandler):
247293
capabilities = 'AUTH ENABLE UTF8=ACCEPT'

0 commit comments

Comments
 (0)