#21815: violate IMAP RFC to be compatible with, e.g., gmail

and others, including imaplib's own behavior.  I'm applying this only to 3.6
because there's a potential backward compatibility concern: if there are
servers that include ] characters in the 'text' portion of their imap
responses, this code change could introduce a new bug.

Patch by Lita Cho, reviewed by Jessica McKellar, Berker Peksag, Maciej Szulik,
silentghost, and me (I fleshed out the comments with the additional
info/concerns.)
This commit is contained in:
R David Murray 2016-01-02 17:18:34 -05:00
parent 01759d5554
commit 317f64f048
4 changed files with 73 additions and 1 deletions

View File

@ -500,6 +500,17 @@ An :class:`IMAP4` instance has the following methods:
M.store(num, '+FLAGS', '\\Deleted') M.store(num, '+FLAGS', '\\Deleted')
M.expunge() M.expunge()
.. note::
Creating flags containing ']' (for example: "[test]") violates
:rfc:`3501` (the IMAP protocol). However, imaplib has historically
allowed creation of such tags, and popular IMAP servers, such as Gmail,
accept and produce such flags. There are non-Python programs which also
create such tags. Although it is an RFC violation and IMAP clients and
servers are supposed to be strict, imaplib nontheless continues to allow
such tags to be created for backward compatibility reasons, and as of
python 3.5.2/3.6.0, handles them if they are sent from the server, since
this improves real-world compatibility.
.. method:: IMAP4.subscribe(mailbox) .. method:: IMAP4.subscribe(mailbox)

View File

@ -111,7 +111,15 @@ InternalDate = re.compile(br'.*INTERNALDATE "'
# Literal is no longer used; kept for backward compatibility. # Literal is no longer used; kept for backward compatibility.
Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII) Literal = re.compile(br'.*{(?P<size>\d+)}$', re.ASCII)
MapCRLF = re.compile(br'\r\n|\r|\n') MapCRLF = re.compile(br'\r\n|\r|\n')
Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>[^\]]*))?\]') # 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
# produce them. The problem with this is if the 'text' portion of the response
# includes a ']' we'll parse the response wrong (which is the point of the RFC
# restriction). However, that seems less likely to be a problem in practice
# than being unable to correctly parse flags that include ']' chars, which
# was reported as a real-world problem in issue #21815.
Response_code = re.compile(br'\[(?P<type>[A-Z-]+)( (?P<data>.*))?\]')
Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?') Untagged_response = re.compile(br'\* (?P<type>[A-Z-]+)( (?P<data>.*))?')
# Untagged_status is no longer used; kept for backward compatibility # Untagged_status is no longer used; kept for backward compatibility
Untagged_status = re.compile( Untagged_status = re.compile(

View File

@ -242,6 +242,55 @@ class ThreadedNetworkedTests(unittest.TestCase):
client = self.imap_class(*server.server_address) client = self.imap_class(*server.server_address)
client.shutdown() client.shutdown()
@reap_threads
def test_bracket_flags(self):
# This violates RFC 3501, which disallows ']' characters in tag names,
# but imaplib has allowed producing such tags forever, other programs
# also produce them (eg: OtherInbox's Organizer app as of 20140716),
# and Gmail, for example, accepts them and produces them. So we
# support them. See issue #21815.
class BracketFlagHandler(SimpleIMAPHandler):
def handle(self):
self.flags = ['Answered', 'Flagged', 'Deleted', 'Seen', 'Draft']
super().handle()
def cmd_AUTHENTICATE(self, tag, args):
self._send_textline('+')
self.server.response = yield
self._send_tagged(tag, 'OK', 'FAKEAUTH successful')
def cmd_SELECT(self, tag, args):
flag_msg = ' \\'.join(self.flags)
self._send_line(('* FLAGS (%s)' % flag_msg).encode('ascii'))
self._send_line(b'* 2 EXISTS')
self._send_line(b'* 0 RECENT')
msg = ('* OK [PERMANENTFLAGS %s \\*)] Flags permitted.'
% flag_msg)
self._send_line(msg.encode('ascii'))
self._send_tagged(tag, 'OK', '[READ-WRITE] SELECT completed.')
def cmd_STORE(self, tag, args):
new_flags = args[2].strip('(').strip(')').split()
self.flags.extend(new_flags)
flags_msg = '(FLAGS (%s))' % ' \\'.join(self.flags)
msg = '* %s FETCH %s' % (args[0], flags_msg)
self._send_line(msg.encode('ascii'))
self._send_tagged(tag, 'OK', 'STORE completed.')
with self.reaped_pair(BracketFlagHandler) as (server, client):
code, data = client.authenticate('MYAUTH', lambda x: b'fake')
self.assertEqual(code, 'OK')
self.assertEqual(server.response, b'ZmFrZQ==\r\n')
client.select('test')
typ, [data] = client.store(b'1', "+FLAGS", "[test]")
self.assertIn(b'[test]', data)
client.select('test')
typ, [data] = client.response('PERMANENTFLAGS')
self.assertIn(b'[test]', data)
@reap_threads @reap_threads
def test_issue5949(self): def test_issue5949(self):

View File

@ -128,6 +128,10 @@ Core and Builtins
Library Library
------- -------
- Issue #21815: Accept ] characters in the data portion of imap responses,
in order to handle the flags with square brackets accepted and produced
by servers such as gmail.
- Issue #25447: fileinput now uses sys.stdin as-is if it does not have a - Issue #25447: fileinput now uses sys.stdin as-is if it does not have a
buffer attribute (restores backward compatibility). buffer attribute (restores backward compatibility).