From 34b37b0e052885a027795fc74a0de71d5cb34c41 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 24 Dec 2013 09:27:43 -0200 Subject: Fix parsing of IMAP folder names (#4830). --- .../bug_4830_handle-unicode-in-folder-names | 2 + mail/src/leap/mail/imap/server.py | 67 +++++++++++++--------- mail/src/leap/mail/imap/tests/test_imap.py | 28 ++++----- 3 files changed, 55 insertions(+), 42 deletions(-) create mode 100644 mail/changes/bug_4830_handle-unicode-in-folder-names diff --git a/mail/changes/bug_4830_handle-unicode-in-folder-names b/mail/changes/bug_4830_handle-unicode-in-folder-names new file mode 100644 index 00000000..68247459 --- /dev/null +++ b/mail/changes/bug_4830_handle-unicode-in-folder-names @@ -0,0 +1,2 @@ + o Remove conversion of IMAP folder names to string. This makes the IMAP + server use twisted's transparent 7bit conversion (#4830). diff --git a/mail/src/leap/mail/imap/server.py b/mail/src/leap/mail/imap/server.py index 2739f8c6..b9b72d01 100644 --- a/mail/src/leap/mail/imap/server.py +++ b/mail/src/leap/mail/imap/server.py @@ -22,6 +22,7 @@ import logging import StringIO import cStringIO import time +import re from collections import defaultdict from email.parser import Parser @@ -205,6 +206,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): WithMsgFields.LAST_UID_KEY: 0 } + INBOX_RE = re.compile(INBOX_NAME, re.IGNORECASE) + def __init__(self, account_name, soledad=None): """ Creates a SoledadAccountIndex that keeps track of the mailboxes @@ -222,7 +225,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): # XXX SHOULD assert too that the name matches the user/uuid with which # soledad has been initialized. - self._account_name = account_name.upper() + self._account_name = self._parse_mailbox_name(account_name) self._soledad = soledad self.initialize_db() @@ -241,19 +244,30 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): """ return copy.deepcopy(self.EMPTY_MBOX) + def _parse_mailbox_name(self, name): + """ + :param name: the name of the mailbox + :type name: unicode + + :rtype: unicode + """ + if self.INBOX_RE.match(name): + # ensure inital INBOX is uppercase + return self.INBOX_NAME + name[len(self.INBOX_NAME):] + return name + def _get_mailbox_by_name(self, name): """ - Returns an mbox document by name. + Return an mbox document by name. :param name: the name of the mailbox :type name: str :rtype: SoledadDocument """ - # XXX only upper for INBOX --- - name = name.upper() doc = self._soledad.get_from_index( - self.TYPE_MBOX_IDX, self.MBOX_KEY, name) + self.TYPE_MBOX_IDX, self.MBOX_KEY, + self._parse_mailbox_name(name)) return doc[0] if doc else None @property @@ -261,7 +275,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): """ A list of the current mailboxes for this account. """ - return [str(doc.content[self.MBOX_KEY]) + return [doc.content[self.MBOX_KEY] for doc in self._soledad.get_from_index( self.TYPE_IDX, self.MBOX_KEY)] @@ -270,7 +284,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): """ A list of the current subscriptions for this account. """ - return [str(doc.content[self.MBOX_KEY]) + return [doc.content[self.MBOX_KEY] for doc in self._soledad.get_from_index( self.TYPE_SUBS_IDX, self.MBOX_KEY, '1')] @@ -284,8 +298,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :returns: a a SoledadMailbox instance :rtype: SoledadMailbox """ - # XXX only upper for INBOX - name = name.upper() + name = self._parse_mailbox_name(name) + if name not in self.mailboxes: raise imap4.MailboxException("No such mailbox") @@ -297,12 +311,12 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): def addMailbox(self, name, creation_ts=None): """ - Adds a mailbox to the account. + Add a mailbox to the account. :param name: the name of the mailbox :type name: str - :param creation_ts: a optional creation timestamp to be used as + :param creation_ts: an optional creation timestamp to be used as mailbox id. A timestamp will be used if no one is provided. :type creation_ts: int @@ -310,9 +324,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :returns: True if successful :rtype: bool """ - # XXX only upper for INBOX - name = name.upper() - # XXX should check mailbox name for RFC-compliant form + name = self._parse_mailbox_name(name) if name in self.mailboxes: raise imap4.MailboxCollision, name @@ -321,7 +333,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): # by default, we pass an int value # taken from the current time # we make sure to take enough decimals to get a unique - # maibox-uidvalidity. + # mailbox-uidvalidity. creation_ts = int(time.time() * 10E2) mbox = self._get_empty_mailbox() @@ -346,8 +358,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :raise MailboxException: Raised if this mailbox cannot be added. """ # TODO raise MailboxException - - paths = filter(None, pathspec.split('/')) + paths = filter(None, + self._parse_mailbox_name(pathspec).split('/')) for accum in range(1, len(paths)): try: self.addMailbox('/'.join(paths[:accum])) @@ -372,13 +384,12 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :rtype: bool """ - # XXX only upper for INBOX - name = name.upper() + name = self._parse_mailbox_name(name) if name not in self.mailboxes: return None - self.selected = str(name) + self.selected = name return SoledadMailbox( name, rw=readwrite, @@ -398,8 +409,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): names. use with care. :type force: bool """ - # XXX only upper for INBOX - name = name.upper() + name = self._parse_mailbox_name(name) + if not name in self.mailboxes: raise imap4.MailboxException("No such mailbox") @@ -436,9 +447,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :param newname: new name of the mailbox :type newname: str """ - # XXX only upper for INBOX - oldname = oldname.upper() - newname = newname.upper() + oldname = self._parse_mailbox_name(oldname) + newname = self._parse_mailbox_name(newname) if oldname not in self.mailboxes: raise imap4.NoSuchMailbox, oldname @@ -516,7 +526,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :param name: name of the mailbox :type name: str """ - name = name.upper() + name = self._parse_mailbox_name(name) if name not in self.subscriptions: self._set_subscription(name, True) @@ -527,7 +537,7 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :param name: name of the mailbox :type name: str """ - name = name.upper() + name = self._parse_mailbox_name(name) if name not in self.subscriptions: raise imap4.MailboxException, "Not currently subscribed to " + name self._set_subscription(name, False) @@ -549,7 +559,8 @@ class SoledadBackedAccount(WithMsgFields, IndexedDB): :type wildcard: str """ # XXX use wildcard in index query - ref = self._inferiorNames(ref.upper()) + ref = self._inferiorNames( + self._parse_mailbox_name(ref)) wildcard = imap4.wildcardToRegexp(wildcard, '/') return [(i, self.getMailbox(i)) for i in ref if wildcard.match(i)] diff --git a/mail/src/leap/mail/imap/tests/test_imap.py b/mail/src/leap/mail/imap/tests/test_imap.py index f87b5343..ea758542 100644 --- a/mail/src/leap/mail/imap/tests/test_imap.py +++ b/mail/src/leap/mail/imap/tests/test_imap.py @@ -521,7 +521,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): """ Test whether we can create mailboxes """ - succeed = ('testbox', 'test/box', 'test/', 'test/box/box', 'FOOBOX') + succeed = ('testbox', 'test/box', 'test/', 'test/box/box', 'foobox') fail = ('testbox', 'test/box') def cb(): @@ -553,7 +553,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): answers = ['foobox', 'testbox', 'test/box', 'test', 'test/box/box'] mbox.sort() answers.sort() - self.assertEqual(mbox, [a.upper() for a in answers]) + self.assertEqual(mbox, [a for a in answers]) @deferred(timeout=None) def testDelete(self): @@ -686,7 +686,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): d.addCallback(lambda _: self.assertEqual( SimpleLEAPServer.theAccount.mailboxes, - ['NEWNAME'])) + ['newname'])) return d @deferred(timeout=None) @@ -742,7 +742,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): mboxes = SimpleLEAPServer.theAccount.mailboxes expected = ['newname', 'newname/m1', 'newname/m2'] mboxes.sort() - self.assertEqual(mboxes, [s.upper() for s in expected]) + self.assertEqual(mboxes, [s for s in expected]) @deferred(timeout=None) def testSubscribe(self): @@ -763,7 +763,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): d.addCallback(lambda _: self.assertEqual( SimpleLEAPServer.theAccount.subscriptions, - ['THIS/MBOX'])) + ['this/mbox'])) return d @deferred(timeout=None) @@ -771,8 +771,8 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): """ Test whether we can unsubscribe from a set of mailboxes """ - SimpleLEAPServer.theAccount.subscribe('THIS/MBOX') - SimpleLEAPServer.theAccount.subscribe('THAT/MBOX') + SimpleLEAPServer.theAccount.subscribe('this/mbox') + SimpleLEAPServer.theAccount.subscribe('that/mbox') def login(): return self.client.login('testuser', 'password-test') @@ -788,7 +788,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): d.addCallback(lambda _: self.assertEqual( SimpleLEAPServer.theAccount.subscriptions, - ['THAT/MBOX'])) + ['that/mbox'])) return d @deferred(timeout=None) @@ -1029,7 +1029,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): return d.addCallback(self._cbTestExamine) def _cbTestExamine(self, ignored): - mbox = self.server.theAccount.getMailbox('TEST-MAILBOX-E') + mbox = self.server.theAccount.getMailbox('test-mailbox-e') self.assertEqual(self.server.mbox.messages.mbox, mbox.messages.mbox) self.assertEqual(self.examinedArgs, { 'EXISTS': 0, 'RECENT': 0, 'UIDVALIDITY': 42, @@ -1070,8 +1070,8 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): d.addCallback(lambda listed: self.assertEqual( sortNest(listed), sortNest([ - (SoledadMailbox.INIT_FLAGS, "/", "ROOT/SUBTHINGL"), - (SoledadMailbox.INIT_FLAGS, "/", "ROOT/ANOTHER-THING") + (SoledadMailbox.INIT_FLAGS, "/", "root/subthingl"), + (SoledadMailbox.INIT_FLAGS, "/", "root/another-thing") ]) )) return d @@ -1081,13 +1081,13 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): """ Test LSub command """ - SimpleLEAPServer.theAccount.subscribe('ROOT/SUBTHINGL2') + SimpleLEAPServer.theAccount.subscribe('root/subthingl2') def lsub(): return self.client.lsub('root', '%') d = self._listSetup(lsub) d.addCallback(self.assertEqual, - [(SoledadMailbox.INIT_FLAGS, "/", "ROOT/SUBTHINGL2")]) + [(SoledadMailbox.INIT_FLAGS, "/", "root/subthingl2")]) return d @deferred(timeout=None) @@ -1190,7 +1190,7 @@ class LeapIMAP4ServerTestCase(IMAP4HelperMixin, unittest.TestCase): return d.addCallback(self._cbTestFullAppend, infile) def _cbTestFullAppend(self, ignored, infile): - mb = SimpleLEAPServer.theAccount.getMailbox('ROOT/SUBTHING') + mb = SimpleLEAPServer.theAccount.getMailbox('root/subthing') self.assertEqual(1, len(mb.messages)) self.assertEqual( -- cgit v1.2.3