diff -r 97ceab0bd6f8 Doc/library/imaplib.rst --- a/Doc/library/imaplib.rst Fri Nov 21 21:55:39 2014 +0200 +++ b/Doc/library/imaplib.rst Tue Dec 22 23:06:16 2015 -0800 @@ -470,6 +470,13 @@ M.store(num, '+FLAGS', '\\Deleted') M.expunge() + .. note:: + + Creating flags containing ']' violates the :rfc:`3501` and the IMAP + protocol. However, popular IMAP servers, such as Gmail, accepts flags + with brackets like "[test]" despite the violation. Thus, :mod:`imaplib` + allows you create and access flags with brackets for backwards + compatibility reasons. .. method:: IMAP4.subscribe(mailbox) diff -r 97ceab0bd6f8 Lib/imaplib.py --- a/Lib/imaplib.py Fri Nov 21 21:55:39 2014 +0200 +++ b/Lib/imaplib.py Tue Dec 22 23:06:16 2015 -0800 @@ -108,7 +108,10 @@ br'"') Literal = re.compile(br'.*{(?P\d+)}$', re.ASCII) MapCRLF = re.compile(br'\r\n|\r|\n') -Response_code = re.compile(br'\[(?P[A-Z-]+)( (?P[^\]]*))?\]') +# We don't exclude the ']' character, even though it violates the RFC. This +# is because popular IMAP servers, such as Gmail, allow flags with ']'. Thus, +# we are purposefully violating the RFC for backward compatibility reasons. +Response_code = re.compile(br'\[(?P[A-Z-]+)( (?P.*))?\]') Untagged_response = re.compile(br'\* (?P[A-Z-]+)( (?P.*))?') Untagged_status = re.compile( br'\* (?P\d+) (?P[A-Z-]+)( (?P.*))?', re.ASCII) diff -r 97ceab0bd6f8 Lib/test/test_imaplib.py --- a/Lib/test/test_imaplib.py Fri Nov 21 21:55:39 2014 +0200 +++ b/Lib/test/test_imaplib.py Tue Dec 22 23:06:16 2015 -0800 @@ -228,6 +228,49 @@ client.shutdown() @reap_threads + def test_bracket_flags(self): + + class GmailHandler(SimpleIMAPHandler): + + def handle(self): + self.flags = ['Answered', 'Flagged', 'Deleted', 'Seen', 'Draft'] + super(GmailHandler, self).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(GmailHandler) 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 def test_issue5949(self): class EOFHandler(socketserver.StreamRequestHandler):