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 Sat Nov 22 02:32:46 2014 -0800 @@ -470,6 +470,16 @@ M.store(num, '+FLAGS', '\\Deleted') M.expunge() + .. note:: + + .. index:: single: Brackets + + 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 Sat Nov 22 02:32:46 2014 -0800 @@ -108,7 +108,13 @@ 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 Sat Nov 22 02:32:46 2014 -0800 @@ -134,7 +134,6 @@ return if line.endswith(b'\r\n'): break - if verbose: print('GOT: %r' % line.strip()) if self.continuation: try: @@ -228,6 +227,49 @@ client.shutdown() @reap_threads + def test_bracket_flags(self): + + class MyServer(SimpleIMAPHandler): + + def handle(self): + self.flags = ['Answered', 'Flagged', 'Deleted', 'Seen', 'Draft'] + super(MyServer, 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(MyServer) 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.assertTrue(b'[test]' in data) + client.select('test') + typ, [data] = client.response('PERMANENTFLAGS') + self.assertTrue(b'[test]' in data) + + @reap_threads def test_issue5949(self): class EOFHandler(socketserver.StreamRequestHandler):