Example #1
0
class MailpileMailbox(UnorderedPicklable(mailbox.Maildir, editable=True)):
    """A Maildir class that supports pickling and a few mailpile specifics."""
    supported_platform = None

    @classmethod
    def parse_path(cls, config, fn, create=False):
        if (((cls.supported_platform is None) or
             (cls.supported_platform == sys.platform[:3].lower())) and
            ((os.path.isdir(fn) and os.path.exists(os.path.join(fn, 'cur'))) or
             (create and not os.path.exists(fn)))):
            return (fn, )
        raise ValueError('Not a Maildir: %s' % fn)

    def _refresh(self):
        with self._lock:
            mailbox.Maildir._refresh(self)
            # Dotfiles are not mail. Ignore them.
            for t in [k for k in self._toc.keys() if k.startswith('.')]:
                del self._toc[t]

    def __unicode__(self):
        return _("Maildir at %s") % self._path

    def _describe_msg_by_ptr(self, msg_ptr):
        return _("e-mail in file %s") % self._lookup(msg_ptr[MBX_ID_LEN:])

    def get_metadata_keywords(self, toc_id):
        subdir, name = os.path.split(self._lookup(toc_id))
        if self.colon in name:
            flags = name.split(self.colon)[-1]
            if flags[:2] == '2,':
                return ['%s:maildir' % c for c in flags[2:]]
        return []
Example #2
0
class MailpileMailbox(UnorderedPicklable(IMAPMailbox)):
    @classmethod
    def parse_path(cls, path, create=False):
        if path.startswith("imap://"):
            url = path[7:]
            try:
                serverpart, mailbox = url.split("/")
            except ValueError:
                serverpart = url
                mailbox = None
            userpart, server = serverpart.split("@")
            user, password = userpart.split(":")
            # WARNING: Order must match IMAPMailbox.__init__(...)
            return (server, 993, user, password)
        raise ValueError('Not an IMAP url: %s' % path)

    def __getstate__(self):
        odict = self.__dict__.copy()
        # Pickle can't handle file and function objects.
        del odict['_mailbox']
        del odict['_save_to']
        return odict

    def get_msg_size(self, toc_id):
        # FIXME: We should make this less horrible.
        fd = self.get_file(toc_id)
        fd.seek(0, 2)
        return fd.tell()
Example #3
0
class MailpileMailbox(UnorderedPicklable(MacMaildir)):
    """A Mac Mail.app maildir class that supports pickling etc."""
    @classmethod
    def parse_path(cls, config, fn, create=False):
        if (os.path.isdir(fn)
                and os.path.exists(os.path.join(fn, 'Info.plist'))):
            return (fn, )
        raise ValueError('Not a Mac Mail.app Maildir: %s' % fn)
Example #4
0
class MailpileMailbox(UnorderedPicklable(mailbox.Maildir, editable=True)):
    """A Maildir class that supports pickling and a few mailpile specifics."""
    supported_platform = None

    @classmethod
    def parse_path(cls, fn, create=False):
        if (((cls.supported_platform is None) or
             (cls.supported_platform in system().lower())) and
            ((os.path.isdir(fn) and os.path.exists(os.path.join(fn, 'cur'))) or
             (create and not os.path.exists(fn)))):
            return (fn, )
        raise ValueError('Not a Maildir: %s' % fn)
Example #5
0
class MailpileMailbox(UnorderedPicklable(MacMaildir)):
    """A Mac Mail.app maildir class that supports pickling etc."""
    @classmethod
    def parse_path(cls, config, fn, create=False, allow_empty=False):
        if (os.path.isdir(fn)
                and os.path.exists(os.path.join(fn, 'Info.plist'))):
            return (fn, )
        raise ValueError('Not a Mac Mail.app Maildir: %s' % fn)

    def __unicode__(self):
        return _("Mac Maildir %s") % self._mailroot

    def _describe_msg_by_ptr(self, msg_ptr):
        return _("e-mail in file %s") % self._lookup(msg_ptr[MBX_ID_LEN:])
Example #6
0
class MailpileMailbox(UnorderedPicklable(mailbox.Maildir, editable=True)):
    """A Maildir class that supports pickling and a few mailpile specifics."""
    supported_platform = None

    @classmethod
    def parse_path(cls, config, fn, create=False):
        if (((cls.supported_platform is None) or
             (cls.supported_platform in system().lower())) and
            ((os.path.isdir(fn) and os.path.exists(os.path.join(fn, 'cur'))) or
             (create and not os.path.exists(fn)))):
            return (fn, )
        raise ValueError('Not a Maildir: %s' % fn)

    def _refresh(self):
        mailbox.Maildir._refresh(self)
        # Dotfiles are not mail. Ignore them.
        for t in [k for k in self._toc.keys() if k.startswith('.')]:
            del self._toc[t]
Example #7
0
class MailpileMailbox(UnorderedPicklable(IMAPMailbox)):
    UNPICKLABLE = ['_mailbox']

    @classmethod
    def parse_path(cls, config, path, create=False):
        if path.startswith("imap://"):
            url = path[7:]
            try:
                serverpart, mailbox = url.split("/")
            except ValueError:
                serverpart = url
                mailbox = None
            userpart, server = serverpart.split("@")
            user, password = userpart.split(":")
            # WARNING: Order must match IMAPMailbox.__init__(...)
            return (server, 993, user, password)
        raise ValueError('Not an IMAP url: %s' % path)

    def get_msg_size(self, toc_id):
        # FIXME: We should make this less horrible.
        fd = self.get_file(toc_id)
        fd.seek(0, 2)
        return fd.tell()
Example #8
0
class MailpileMailbox(UnorderedPicklable(POP3Mailbox)):
    UNPICKLABLE = ['_pop3', '_debug']

    @classmethod
    def parse_path(cls, config, path, create=False):
        path = path.split('/')
        if path and path[0].lower() in ('pop:', 'pop3:', 'pop3_ssl:',
                                        'pop3s:'):
            proto = path[0][:-1].lower()
            userpart, server = path[2].rsplit("@", 1)
            user, password = userpart.rsplit(":", 1)
            if ":" in server:
                server, port = server.split(":", 1)
            else:
                port = 995 if ('s' in proto) else 110

            # This is a hack for GMail
            if 'recent' in path[3:]:
                user = '******' + user

            if not config:
                debug = False
            elif 'pop3' in config.sys.debug:
                debug = 99
            elif 'rescan' in config.sys.debug:
                debug = 1
            else:
                debug = False

            # WARNING: Order must match POP3Mailbox.__init__(...)
            return (server, user, password, 's' in proto, int(port), debug)
        raise ValueError('Not a POP3 url: %s' % path)

    def save(self, *args, **kwargs):
        # Do not save state locally
        pass
Example #9
0
class MailpileMailbox(UnorderedPicklable(mailbox.Maildir, editable=True)):
    """A Maildir class that supports pickling and a few mailpile specifics."""
    supported_platform = None

    @classmethod
    def parse_path(cls, config, fn, create=False):
        if (((cls.supported_platform is None) or
             (cls.supported_platform == sys.platform[:3].lower())) and
            ((os.path.isdir(fn) and os.path.exists(os.path.join(fn, 'cur'))
              and os.path.exists(os.path.join(fn, 'wervd.ver'))) or
             (create and not os.path.exists(fn)))):
            return (fn, )
        raise ValueError('Not a Maildir: %s' % fn)

    def __init2__(self, *args, **kwargs):
        open(os.path.join(self._path, 'wervd.ver'), 'w+b').write('0')

    def remove(self, key):
        # FIXME: Remove all the copies of this message!
        fn = self._lookup(key)
        del self._toc[key]
        safe_remove(fn)

    def _refresh(self):
        mailbox.Maildir._refresh(self)
        # WERVD mail names don't have dots in them
        for t in [k for k in self._toc.keys() if '.' in k]:
            del self._toc[t]
        safe_remove()  # Try to remove any postponed removals

    def _get_fd(self, key):
        fd = open(os.path.join(self._path, self._lookup(key)), 'rb')
        key = self._decryption_key_func()
        if key:
            fd = DecryptingStreamer(fd, mep_key=key, name='WERVD')
        return fd

    def get_message(self, key):
        """Return a Message representation or raise a KeyError."""
        with self._get_fd(key) as fd:
            if self._factory:
                return self._factory(fd)
            else:
                return mailbox.MaildirMessage(fd)

    def get_string(self, key):
        with self._get_fd(key) as fd:
            return fd.read()

    def get_file(self, key):
        return StringIO.StringIO(self.get_string(key))

    def add(self, message, copies=1):
        """Add message and return assigned key."""
        key = self._encryption_key_func()
        es = None
        try:
            tmpdir = os.path.join(self._path, 'tmp')
            if key:
                es = EncryptingStreamer(key,
                                        dir=tmpdir,
                                        name='WERVD',
                                        delimited=False)
            else:
                es = ChecksummingStreamer(dir=tmpdir, name='WERVD')
            self._dump_message(message, es)
            es.finish()

            # We are using the MD5 to detect file system corruption, not in a
            # security context - so using as little as 40 bits should be fine.
            saved = False
            key = None
            for l in range(10, len(es.outer_md5sum)):
                key = es.outer_md5sum[:l]
                fn = os.path.join(self._path, 'new', key)
                if not os.path.exists(fn):
                    es.save(fn)
                    saved = self._toc[key] = os.path.join('new', key)
                    break
            if not saved:
                raise mailbox.ExternalClashError(
                    _('Could not find a filename '
                      'for the message.'))

            for cpn in range(1, copies):
                fn = os.path.join(self._path, 'new', '%s.%s' % (key, cpn))
                with mailbox._create_carefully(fn) as ofd:
                    es.save_copy(ofd)

            return key
        finally:
            if es is not None:
                es.close()

    def _dump_message(self, message, target):
        if isinstance(message, email.message.Message):
            gen = email.generator.Generator(target, False, 0)
            gen.flatten(message)
        elif isinstance(message, str):
            target.write(message)
        else:
            raise TypeError(_('Invalid message type: %s') % type(message))

    def __setitem__(self, key, message):
        raise IOError(_('Mailbox messages are immutable'))
Example #10
0
class MailpileMailbox(UnorderedPicklable(mailbox.Maildir, editable=True)):
    """A Maildir class that supports pickling and a few mailpile specifics."""
    supported_platform = None
    colon = '!'  # Works on both Windows and Unix

    # FIXME: Copies were part of the original WERVD spec, to compensate for
    #        the additional fragility of encrypted data. This hasn't been
    #        implemented however, and while SSDs are expensive it is not
    #        obvious that doubling (or tripling...) the storage requirements
    #        for all e-mail is a cost folks are willing to pay for never
    #        losing a message to the occaisional bitflips. So this is all
    #        commented out at the moment. Revisit?
    #   MAX_COPIES = 5

    @classmethod
    def parse_path(cls, config, fn, create=False, allow_empty=False):
        if (((cls.supported_platform is None) or
             (cls.supported_platform == sys.platform[:3].lower())) and
            ((os.path.isdir(fn) and os.path.exists(os.path.join(fn, 'cur'))
              and os.path.exists(os.path.join(fn, 'wervd.ver'))) or
             (create and not os.path.exists(fn)))):
            return (fn, )
        raise ValueError('Not a Mailpile Maildir: %s' % fn)

    def __init2__(self, *args, **kwargs):
        open(os.path.join(self._path, 'wervd.ver'), 'w+b').write('0')

    def __unicode__(self):
        return _("Mailpile mailbox at %s") % self._path

    def _describe_msg_by_ptr(self, msg_ptr):
        return _("e-mail in file %s") % self._lookup(msg_ptr[MBX_ID_LEN:])

# FIXME: Copies
#   def _copy_paths(self, where, key, copies):
#       for cpn in range(1, copies):
#           yield os.path.join(self._path, where, '%s.%s' % (key, cpn))

    def remove(self, key):
        with self._lock:
            fn = os.path.join(self._path, self._lookup(key))
            del self._toc[key]
        safe_remove(fn)

# FIXME: Copies
#       # Also remove all the copies of this message!
#       key = os.path.basename(fn)
#       for where in ('cur', 'new', 'tmp'):
#           for copy_fn in self._copy_paths(where, key, self.MAX_COPIES):
#               if os.path.exists(copy_fn):
#                   safe_remove(copy_fn)
#               else:
#                   break

    def _refresh(self):
        with self._lock:
            mailbox.Maildir._refresh(self)
            # WERVD mail names don't have dots in them
            for t in [k for k in self._toc.keys() if '.' in k]:
                del self._toc[t]
        safe_remove()  # Try to remove any postponed removals

    def _get_fd(self, key):
        with self._lock:
            fn = os.path.join(self._path, self._lookup(key))
            mep_key = self._decryption_key_func()
        fd = open(fn, 'rb')
        if mep_key:
            fd = DecryptingStreamer(fd, mep_key=mep_key, name='WERVD(%s)' % fn)
        return fd

    def get_message(self, key):
        """Return a Message representation or raise a KeyError."""
        with self._lock:
            with self._get_fd(key) as fd:
                if self._factory:
                    return self._factory(fd)
                else:
                    return mailbox.MaildirMessage(fd)

    def get_string(self, key):
        with self._lock:
            with self._get_fd(key) as fd:
                return fd.read()

    def get_file(self, key):
        with self._lock:
            return StringIO.StringIO(self.get_string(key))

    def get_metadata_keywords(self, toc_id):
        subdir, name = os.path.split(self._lookup(toc_id))
        if self.colon in name:
            flags = name.split(self.colon)[-1]
            if flags[:2] == '2,':
                return ['%s:maildir' % c for c in flags[2:]]
        return []

    def set_metadata_keywords(self, toc_id, kws):
        with self._lock:
            old_fpath = self._lookup(toc_id)
            new_fpath = old_fpath.rsplit(self.colon, 1)[0]

            flags = ''.join(sorted([k[0] for k in kws]))
            if flags:
                new_fpath += '%s2,%s' % (self.colon, flags)
                if new_fpath != old_fpath:
                    os.rename(os.path.join(self._path, old_fpath),
                              os.path.join(self._path, new_fpath))
                    self._toc[toc_id] = new_fpath

    def add(self, message):
        """Add message and return assigned key."""
        key = self._encryption_key_func()
        es = None
        try:
            tmpdir = os.path.join(self._path, 'tmp')
            if not os.path.exists(tmpdir):
                os.mkdir(tmpdir, 0o700)
            if key:
                es = EncryptingStreamer(key,
                                        dir=tmpdir,
                                        name='WERVD',
                                        delimited=False)
            else:
                es = ChecksummingStreamer(dir=tmpdir, name='WERVD')
            self._dump_message(message, es)
            es.finish()

            # We are using the MAC to detect file system corruption, not in a
            # security context - so using as little as 40 bits should be fine.
            saved = False
            key = None
            outer_mac = es.outer_mac_sha256()
            for l in range(10, len(outer_mac)):
                key = outer_mac[:l]
                fn = os.path.join(self._path, 'new', key)
                if not os.path.exists(fn):
                    es.save(fn)
                    saved = self._toc[key] = os.path.join('new', key)
                    break
            if not saved:
                raise mailbox.ExternalClashError(
                    _('Could not find a filename '
                      'for the message.'))


# FIXME: Copies
#           for fn in self._copy_paths('new', key, copies):
#               with mailbox._create_carefully(fn) as ofd:
#                   es.save_copy(ofd)

            return key
        finally:
            if es is not None:
                es.close()

    def _dump_message(self, message, target):
        if isinstance(message, email.message.Message):
            gen = email.generator.Generator(target, False, 0)
            gen.flatten(message)
        elif isinstance(message, str):
            target.write(message)
        else:
            raise TypeError(_('Invalid message type: %s') % type(message))

    def __setitem__(self, key, message):
        raise IOError(_('Mailbox messages are immutable'))