diff options
Diffstat (limited to 'src/leap/mail/imap')
-rw-r--r-- | src/leap/mail/imap/account.py | 15 | ||||
-rw-r--r-- | src/leap/mail/imap/mailbox.py | 32 | ||||
-rw-r--r-- | src/leap/mail/imap/messages.py | 15 | ||||
-rw-r--r-- | src/leap/mail/imap/server.py | 95 | ||||
-rw-r--r-- | src/leap/mail/imap/tests/test_imap.py | 13 |
5 files changed, 128 insertions, 42 deletions
diff --git a/src/leap/mail/imap/account.py b/src/leap/mail/imap/account.py index 38df845..ccb4b75 100644 --- a/src/leap/mail/imap/account.py +++ b/src/leap/mail/imap/account.py @@ -163,28 +163,17 @@ class IMAPAccount(object): # FIXME --- return failure instead of AssertionError # See AccountTestCase... leap_assert(name, "Need a mailbox name to create a mailbox") - if creation_ts is None: - # by default, we pass an int value - # taken from the current time - # we make sure to take enough decimals to get a unique - # mailbox-uidvalidity. - creation_ts = int(time.time() * 10E2) def check_it_does_not_exist(mailboxes): if name in mailboxes: raise imap4.MailboxCollision, repr(name) return mailboxes - def set_mbox_creation_ts(collection): - d = collection.set_mbox_attr("created", creation_ts) - d.addCallback(lambda _: collection) - return d - d = self.account.list_all_mailbox_names() d.addCallback(check_it_does_not_exist) - d.addCallback(lambda _: self.account.add_mailbox(name)) + d.addCallback(lambda _: self.account.add_mailbox( + name, creation_ts=creation_ts)) d.addCallback(lambda _: self.account.get_collection_by_mailbox(name)) - d.addCallback(set_mbox_creation_ts) d.addCallback(self._return_mailbox_from_collection) return d diff --git a/src/leap/mail/imap/mailbox.py b/src/leap/mail/imap/mailbox.py index 61baca5..1412344 100644 --- a/src/leap/mail/imap/mailbox.py +++ b/src/leap/mail/imap/mailbox.py @@ -492,17 +492,12 @@ class IMAPMailbox(object): :rtype: deferred with a generator that yields... """ - # For the moment our UID is sequential, so we - # can treat them all the same. - # Change this to the flag that twisted expects when we - # switch to content-hash based index + local UID table. - - is_sequence = True if uid == 0 else False - + # TODO implement sequence + # is_sequence = True if uid == 0 else False # XXX DEBUG --- if you attempt to use the `getmail` utility under - # imap/tests, it will choke until we implement sequence numbers. This - # is an easy hack meanwhile. - # is_sequence = False + # imap/tests, or muas like mutt, it will choke until we implement + # sequence numbers. This is an easy hack meanwhile. + is_sequence = False # ----------------------------------------------------------------- getmsg = self.collection.get_message_by_uid @@ -514,13 +509,14 @@ class IMAPMailbox(object): d_imapmsg = [] for msg in messages: d_imapmsg.append(getimapmsg(msg)) - return defer.gatherResults(d_imapmsg) + return defer.gatherResults(d_imapmsg, consumeErrors=True) def _zip_msgid(imap_messages): zipped = zip( list(msg_range), imap_messages) return (item for item in zipped) + # XXX not called?? def _unset_recent(sequence): reactor.callLater(0, self.unset_recent_flags, sequence) return sequence @@ -528,12 +524,12 @@ class IMAPMailbox(object): d_msg = [] for msgid in msg_range: # XXX We want cdocs because we "probably" are asked for the - # body. We should be smarted at do_FETCH and pass a parameter + # body. We should be smarter at do_FETCH and pass a parameter # to this method in order not to prefetch cdocs if they're not # going to be used. d_msg.append(getmsg(msgid, get_cdocs=True)) - d = defer.gatherResults(d_msg) + d = defer.gatherResults(d_msg, consumeErrors=True) d.addCallback(_get_imap_msg) d.addCallback(_zip_msgid) return d @@ -581,7 +577,13 @@ class IMAPMailbox(object): MessagePart. :rtype: tuple """ - is_sequence = True if uid == 0 else False + # is_sequence = True if uid == 0 else False + # XXX FIXME ----------------------------------------------------- + # imap/tests, or muas like mutt, it will choke until we implement + # sequence numbers. This is an easy hack meanwhile. + is_sequence = False + # --------------------------------------------------------------- + if is_sequence: raise NotImplementedError @@ -664,7 +666,6 @@ class IMAPMailbox(object): :rtype: tuple """ # TODO implement sequences - # TODO how often is thunderbird doing this? is_sequence = True if uid == 0 else False if is_sequence: raise NotImplementedError @@ -722,7 +723,6 @@ class IMAPMailbox(object): read-write. """ # TODO implement sequences - # TODO how often is thunderbird doing this? is_sequence = True if uid == 0 else False if is_sequence: raise NotImplementedError diff --git a/src/leap/mail/imap/messages.py b/src/leap/mail/imap/messages.py index 02aac2e..4c6f10d 100644 --- a/src/leap/mail/imap/messages.py +++ b/src/leap/mail/imap/messages.py @@ -22,7 +22,7 @@ from twisted.mail import imap4 from twisted.internet import defer from zope.interface import implements -from leap.mail.utils import find_charset +from leap.mail.utils import find_charset, CaseInsensitiveDict logger = logging.getLogger(__name__) @@ -228,13 +228,13 @@ def _format_headers(headers, negate, *names): # default to most likely standard charset = find_charset(headers, "utf-8") - _headers = dict() - for key, value in headers.items(): - # twisted imap server expects *some* headers to be lowercase - # We could use a CaseInsensitiveDict here... - if key.lower() == "content-type": - key = key.lower() + # We will return a copy of the headers dictionary that + # will allow case-insensitive lookups. In some parts of the twisted imap + # server code the keys are expected to be in lower case, and in this way + # we avoid having to convert them. + _headers = CaseInsensitiveDict() + for key, value in headers.items(): if not isinstance(key, str): key = key.encode(charset, 'replace') if not isinstance(value, str): @@ -247,4 +247,5 @@ def _format_headers(headers, negate, *names): # filter original dict by negate-condition if cond(key): _headers[key] = value + return _headers diff --git a/src/leap/mail/imap/server.py b/src/leap/mail/imap/server.py index 3e10171..3aeca54 100644 --- a/src/leap/mail/imap/server.py +++ b/src/leap/mail/imap/server.py @@ -36,6 +36,38 @@ from twisted.mail.imap4 import IllegalClientResponse from twisted.mail.imap4 import LiteralString, LiteralFile +def _getContentType(msg): + """ + Return a two-tuple of the main and subtype of the given message. + """ + attrs = None + mm = msg.getHeaders(False, 'content-type').get('content-type', None) + if mm: + mm = ''.join(mm.splitlines()) + mimetype = mm.split(';') + if mimetype: + type = mimetype[0].split('/', 1) + if len(type) == 1: + major = type[0] + minor = None + elif len(type) == 2: + major, minor = type + else: + major = minor = None + # XXX patched --------------------------------------------- + attrs = dict(x.strip().split('=', 1) for x in mimetype[1:]) + # XXX patched --------------------------------------------- + else: + major = minor = None + else: + major = minor = None + return major, minor, attrs + +# Monkey-patch _getContentType to avoid bug that passes lower-case boundary in +# BODYSTRUCTURE response. +imap4._getContentType = _getContentType + + class LEAPIMAPServer(imap4.IMAP4Server): """ An IMAP4 Server with a LEAP Storage Backend. @@ -60,6 +92,69 @@ class LEAPIMAPServer(imap4.IMAP4Server): # populate the test account properly (and only once # per session) + ############################################################# + # + # Twisted imap4 patch to workaround bad mime rendering in TB. + # See https://leap.se/code/issues/6773 + # and https://bugzilla.mozilla.org/show_bug.cgi?id=149771 + # Still unclear if this is a thunderbird bug. + # TODO send this patch upstream + # + ############################################################# + + def spew_body(self, part, id, msg, _w=None, _f=None): + if _w is None: + _w = self.transport.write + for p in part.part: + if msg.isMultipart(): + msg = msg.getSubPart(p) + elif p > 0: + # Non-multipart messages have an implicit first part but no + # other parts - reject any request for any other part. + raise TypeError("Requested subpart of non-multipart message") + + if part.header: + hdrs = msg.getHeaders(part.header.negate, *part.header.fields) + hdrs = imap4._formatHeaders(hdrs) + # PATCHED ########################################## + _w(str(part) + ' ' + imap4._literal(hdrs + "\r\n")) + # PATCHED ########################################## + elif part.text: + _w(str(part) + ' ') + _f() + return imap4.FileProducer(msg.getBodyFile() + ).beginProducing(self.transport + ) + elif part.mime: + hdrs = imap4._formatHeaders(msg.getHeaders(True)) + + # PATCHED ########################################## + _w(str(part) + ' ' + imap4._literal(hdrs + "\r\n")) + # END PATCHED ###################################### + + elif part.empty: + _w(str(part) + ' ') + _f() + if part.part: + return imap4.FileProducer(msg.getBodyFile() + ).beginProducing(self.transport + ) + else: + mf = imap4.IMessageFile(msg, None) + if mf is not None: + return imap4.FileProducer(mf.open()).beginProducing(self.transport) + return imap4.MessageProducer(msg, None, self._scheduler).beginProducing(self.transport) + + else: + _w('BODY ' + imap4.collapseNestedLists([imap4.getBodyStructure(msg)])) + + ################################################################## + # + # END Twisted imap4 patch to workaround bad mime rendering in TB. + # #6773 + # + ################################################################## + def lineReceived(self, line): """ Attempt to parse a single line from the server. diff --git a/src/leap/mail/imap/tests/test_imap.py b/src/leap/mail/imap/tests/test_imap.py index c4f752b..af1bd69 100644 --- a/src/leap/mail/imap/tests/test_imap.py +++ b/src/leap/mail/imap/tests/test_imap.py @@ -25,8 +25,8 @@ XXX add authors from the original twisted tests. @license: GPLv3, see included LICENSE file """ # XXX review license of the original tests!!! - import os +import string import types @@ -38,6 +38,7 @@ from twisted.python import failure from twisted import cred from leap.mail.imap.mailbox import IMAPMailbox +from leap.mail.imap.messages import CaseInsensitiveDict from leap.mail.imap.tests.utils import IMAP4HelperMixin @@ -74,8 +75,8 @@ class TestRealm: # # DEBUG --- -#from twisted.internet.base import DelayedCall -#DelayedCall.debug = True +# from twisted.internet.base import DelayedCall +# DelayedCall.debug = True class LEAPIMAP4ServerTestCase(IMAP4HelperMixin): @@ -810,7 +811,7 @@ class LEAPIMAP4ServerTestCase(IMAP4HelperMixin): infile = util.sibpath(__file__, 'rfc822.message') message = open(infile) acc = self.server.theAccount - mailbox_name = "root/subthing" + mailbox_name = "appendmbox/subthing" def add_mailbox(): return acc.addMailbox(mailbox_name) @@ -843,7 +844,7 @@ class LEAPIMAP4ServerTestCase(IMAP4HelperMixin): uid, msg = fetched[0] parsed = self.parser.parse(open(infile)) expected_body = parsed.get_payload() - expected_headers = dict(parsed.items()) + expected_headers = CaseInsensitiveDict(parsed.items()) def assert_flags(flags): self.assertEqual( @@ -860,7 +861,7 @@ class LEAPIMAP4ServerTestCase(IMAP4HelperMixin): self.assertEqual(expected_body, gotbody) def assert_headers(headers): - self.assertItemsEqual(expected_headers, headers) + self.assertItemsEqual(map(string.lower, expected_headers), headers) d = defer.maybeDeferred(msg.getFlags) d.addCallback(assert_flags) |