Skip to content
Merged
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
18 changes: 16 additions & 2 deletions Lib/imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@
_quoted = re.compile(br'"(?:[^"\\]|\\.)*+"')


def _paren_depth(data, depth=0):
# Net parenthesis nesting of data, ignoring parentheses in quoted strings.
data = _quoted.sub(b'', data)
return depth + data.count(b'(') - data.count(b')')


class IMAP4:

r"""IMAP4 client class.
Expand Down Expand Up @@ -1277,6 +1283,11 @@ def _get_response(self, start_timeout=False):
else:
resp = self._get_line()

# Skip spurious blank lines between responses (some servers send one
# after a literal that ends a response).
while resp == b'':
resp = self._get_line()

# Command completion response?

if self._match(self.tagre, resp):
Expand Down Expand Up @@ -1314,6 +1325,7 @@ def _get_response(self, start_timeout=False):

# Is there a literal to come?

depth = 0 # open parenthesis nesting so far
while self._match(self.Literal, dat):

# Read literal direct from connection.
Expand All @@ -1327,13 +1339,15 @@ def _get_response(self, start_timeout=False):
# Store response with literal as tuple

self._append_untagged(typ, (dat, data))
depth = _paren_depth(dat, depth)

# Read trailer - possibly containing another literal

dat = self._get_line()

# Skip a blank line that some servers send after a literal.
if dat == b'':
# Skip spurious blank lines after a literal, but only inside an
# unclosed parenthesis (at top level they end the response).
while dat == b'' and depth > 0:
dat = self._get_line()

self._append_untagged(typ, dat)
Expand Down
45 changes: 45 additions & 0 deletions Lib/test/test_imaplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,51 @@ def cmd_FETCH(self, tag, args):
self.assertEqual(data, [(b'1 (BODY[HEADER] {13}', b'Subject: test'),
b')'])

def test_literal_terminating_response(self):
# A literal ending a response (a LIST mailbox name sent as a literal)
# has an empty trailer that must not be swallowed. Conforming case:
# no spurious blank lines.
names = [b'My (box)"', b'Another', b'Third']
class Handler(SimpleIMAPHandler):
def cmd_LIST(self, tag, args):
for name in names:
self._send(b'* LIST (\\HasNoChildren) "/" {%d}\r\n'
% len(name))
self._send(name)
self._send(b'\r\n') # ends the response, no blank
self._send_tagged(tag, 'OK', 'LIST completed')
client, _ = self._setup(Handler)
client.login('user', 'pass')
typ, data = client.list()
self.assertEqual(typ, 'OK')
self.assertEqual(data, [
(b'(\\HasNoChildren) "/" {9}', b'My (box)"'), b'',
(b'(\\HasNoChildren) "/" {7}', b'Another'), b'',
(b'(\\HasNoChildren) "/" {5}', b'Third'), b'',
])

def test_spurious_blank_lines_between_responses(self):
# A spurious blank line after each terminating literal falls between the
# untagged responses and must be skipped, even several in a row.
names = [b'My (box)"', b'Another', b'Third']
class Handler(SimpleIMAPHandler):
def cmd_LIST(self, tag, args):
for name in names:
self._send(b'* LIST (\\HasNoChildren) "/" {%d}\r\n'
% len(name))
self._send(name)
self._send(b'\r\n\r\n') # ends the response, then a blank
self._send_tagged(tag, 'OK', 'LIST completed')
client, _ = self._setup(Handler)
client.login('user', 'pass')
typ, data = client.list()
self.assertEqual(typ, 'OK')
self.assertEqual(data, [
(b'(\\HasNoChildren) "/" {9}', b'My (box)"'), b'',
(b'(\\HasNoChildren) "/" {7}', b'Another'), b'',
(b'(\\HasNoChildren) "/" {5}', b'Third'), b'',
])

def test_unselect(self):
client, server = self._setup(SimpleIMAPHandler)
client.login('user', 'pass')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
:mod:`imaplib` no longer fails when a server sends a spurious blank line
after the counted data of a literal. Such a blank line is now skipped.
after the counted data of a literal, including after a literal that
terminates a response (such as a mailbox name returned by ``LIST``).
Such blank lines are now skipped without swallowing the following line.
Loading