예제 #1
0
 def test_empty_message_with_unicode_header(self):
     """Test if unicode header keys can be used in an email that is
     converted to string with email_as_string()."""
     # This is what alot.db.envelope.Envelope.construct_mail() currently
     # does: It constructs a message object and then copies all headers from
     # the envelope to the message object.  Some header names are stored as
     # unicode in the envelope.
     message = email.message.Message()
     message[u'X-Unicode-Header'] = 'dummy value'
     actual = helper.email_as_string(message)
     expected = 'X-Unicode-Header: dummy value\r\n\r\n'
     self.assertEqual(actual, expected)
예제 #2
0
파일: utils_test.py 프로젝트: vrs/alot
 def _make_signed(self):
     """Create a signed message that is multipart/signed."""
     text = 'This is some text'
     t = email.mime.text.MIMEText(text, 'plain', 'utf-8')
     _, sig = crypto.detached_signature_for(helper.email_as_string(t),
                                            self.keys)
     s = email.mime.application.MIMEApplication(
         sig, 'pgp-signature', email.encoders.encode_7or8bit)
     m = email.mime.multipart.MIMEMultipart('signed', None, [t, s])
     m.set_param('protocol', 'application/pgp-signature')
     m.set_param('micalg', 'pgp-sha256')
     return m
예제 #3
0
 def _make_signed(self):
     """Create a signed message that is multipart/signed."""
     text = 'This is some text'
     t = email.mime.text.MIMEText(text, 'plain', 'utf-8')
     _, sig = crypto.detached_signature_for(
         helper.email_as_string(t), self.keys)
     s = email.mime.application.MIMEApplication(
         sig, 'pgp-signature', email.encoders.encode_7or8bit)
     m = email.mime.multipart.MIMEMultipart('signed', None, [t, s])
     m.set_param('protocol', 'application/pgp-signature')
     m.set_param('micalg', 'pgp-sha256')
     return m
예제 #4
0
파일: envelope.py 프로젝트: tlevine/alot
    def apply(self, ui):
        envelope = ui.current_buffer.envelope

        # determine account to use
        sname, saddr = email.Utils.parseaddr(envelope.get('From'))
        account = settings.get_account_by_address(saddr)
        if account is None:
            if not settings.get_accounts():
                ui.notify('no accounts set.', priority='error')
                return
            else:
                account = settings.get_accounts()[0]

        if account.draft_box is None:
            ui.notify('abort: account <%s> has no draft_box set.' % saddr,
                      priority='error')
            return

        mail = envelope.construct_mail()
        # store mail locally
        # add Date header
        mail['Date'] = email.Utils.formatdate(localtime=True)
        path = account.store_draft_mail(email_as_string(mail))

        msg = 'draft saved successfully'

        # add mail to index if maildir path available
        if path is not None:
            ui.notify(msg + ' to %s' % path)
            logging.debug('adding new mail to index')
            try:
                ui.dbman.add_message(path, account.draft_tags)
                ui.apply_command(globals.FlushCommand())
                ui.apply_command(commands.globals.BufferCloseCommand())
            except DatabaseError as e:
                logging.error(e.message)
                ui.notify('could not index message:\n%s' % e.message,
                          priority='error',
                          block=True)
        else:
            ui.apply_command(commands.globals.BufferCloseCommand())
예제 #5
0
    def apply(self, ui):
        envelope = ui.current_buffer.envelope

        # determine account to use
        sname, saddr = email.Utils.parseaddr(envelope.get('From'))
        account = settings.get_account_by_address(saddr)
        if account is None:
            if not settings.get_accounts():
                ui.notify('no accounts set.', priority='error')
                return
            else:
                account = settings.get_accounts()[0]

        if account.draft_box is None:
            ui.notify('abort: account <%s> has no draft_box set.' % saddr,
                      priority='error')
            return

        mail = envelope.construct_mail()
        # store mail locally
        # add Date header
        mail['Date'] = email.Utils.formatdate(localtime=True)
        path = account.store_draft_mail(email_as_string(mail))

        msg = 'draft saved successfully'

        # add mail to index if maildir path available
        if path is not None:
            ui.notify(msg + ' to %s' % path)
            logging.debug('adding new mail to index')
            try:
                ui.dbman.add_message(path, account.draft_tags)
                ui.apply_command(globals.FlushCommand())
                ui.apply_command(commands.globals.BufferCloseCommand())
            except DatabaseError as e:
                logging.error(e.message)
                ui.notify('could not index message:\n%s' % e.message,
                          priority='error',
                          block=True)
        else:
            ui.apply_command(commands.globals.BufferCloseCommand())
예제 #6
0
파일: envelope.py 프로젝트: tlevine/alot
    def apply(self, ui):
        if self.mail is None:
            if self.envelope is None:
                # needed to close later
                self.envelope_buffer = ui.current_buffer
                self.envelope = self.envelope_buffer.envelope

            # This is to warn the user before re-sending
            # an already sent message in case the envelope buffer
            # was not closed because it was the last remaining buffer.
            if self.envelope.sent_time:
                mod = self.envelope.modified_since_sent
                when = self.envelope.sent_time
                warning = 'A modified version of ' * mod
                warning += 'this message has been sent at %s.' % when
                warning += ' Do you want to resend?'
                if (yield ui.choice(warning, cancel='no',
                                    msg_position='left')) == 'no':
                    return

            # don't do anything if another SendCommand is in the middle of
            # sending the message and we were triggered accidentally
            if self.envelope.sending:
                msg = 'sending this message already!'
                logging.debug(msg)
                return

            clearme = ui.notify(u'constructing mail (GPG, attachments)\u2026',
                                timeout=-1)

            try:
                self.mail = self.envelope.construct_mail()
                self.mail['Date'] = email.Utils.formatdate(localtime=True)
                self.mail = email_as_string(self.mail)
            except GPGProblem, e:
                ui.clear_notify([clearme])
                ui.notify(e.message, priority='error')
                return

            ui.clear_notify([clearme])
예제 #7
0
파일: envelope.py 프로젝트: arekfu/alot
    def apply(self, ui):
        if self.mail is None:
            if self.envelope is None:
                # needed to close later
                self.envelope_buffer = ui.current_buffer
                self.envelope = self.envelope_buffer.envelope

            # This is to warn the user before re-sending
            # an already sent message in case the envelope buffer
            # was not closed because it was the last remaining buffer.
            if self.envelope.sent_time:
                mod = self.envelope.modified_since_sent
                when = self.envelope.sent_time
                warning = 'A modified version of ' * mod
                warning += 'this message has been sent at %s.' % when
                warning += ' Do you want to resend?'
                if (yield ui.choice(warning, cancel='no',
                                    msg_position='left')) == 'no':
                    return

            # don't do anything if another SendCommand is in the middle of
            # sending the message and we were triggered accidentally
            if self.envelope.sending:
                msg = 'sending this message already!'
                logging.debug(msg)
                return

            clearme = ui.notify(u'constructing mail (GPG, attachments)\u2026',
                                timeout=-1)

            try:
                self.mail = self.envelope.construct_mail()
                self.mail['Date'] = email.Utils.formatdate(localtime=True)
                self.mail = email_as_string(self.mail)
            except GPGProblem, e:
                ui.clear_notify([clearme])
                ui.notify(e.message, priority='error')
                return

            ui.clear_notify([clearme])
예제 #8
0
파일: envelope.py 프로젝트: arekfu/alot
    def construct_mail(self):
        """
        compiles the information contained in this envelope into a
        :class:`email.Message`.
        """
        # Build body text part. To properly sign/encrypt messages later on, we
        # convert the text to its canonical format (as per RFC 2015).
        canonical_format = self.body.encode('utf-8')
        canonical_format = canonical_format.replace('\\t', ' ' * 4)
        textpart = MIMEText(canonical_format, 'plain', 'utf-8')

        # wrap it in a multipart container if necessary
        if self.attachments:
            inner_msg = MIMEMultipart()
            inner_msg.attach(textpart)
            # add attachments
            for a in self.attachments:
                inner_msg.attach(a.get_mime_representation())
        else:
            inner_msg = textpart

        if self.sign:
            plaintext = helper.email_as_string(inner_msg)
            logging.debug('signing plaintext: ' + plaintext)

            try:
                signatures, signature_str = crypto.detached_signature_for(
                    plaintext, self.sign_key)
                if len(signatures) != 1:
                    raise GPGProblem("Could not sign message (GPGME "
                                     "did not return a signature)",
                                     code=GPGCode.KEY_CANNOT_SIGN)
            except gpgme.GpgmeError as e:
                if e.code == gpgme.ERR_BAD_PASSPHRASE:
                    # If GPG_AGENT_INFO is unset or empty, the user just does
                    # not have gpg-agent running (properly).
                    if os.environ.get('GPG_AGENT_INFO', '').strip() == '':
                        msg = "Got invalid passphrase and GPG_AGENT_INFO\
                                not set. Please set up gpg-agent."
                        raise GPGProblem(msg, code=GPGCode.BAD_PASSPHRASE)
                    else:
                        raise GPGProblem("Bad passphrase. Is gpg-agent "
                                         "running?",
                                         code=GPGCode.BAD_PASSPHRASE)
                raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_SIGN)

            micalg = crypto.RFC3156_micalg_from_algo(signatures[0].hash_algo)
            unencrypted_msg = MIMEMultipart('signed', micalg=micalg,
                                            protocol=
                                            'application/pgp-signature')

            # wrap signature in MIMEcontainter
            stype = 'pgp-signature; name="signature.asc"'
            signature_mime = MIMEApplication(_data=signature_str,
                                             _subtype=stype,
                                             _encoder=encode_7or8bit)
            signature_mime['Content-Description'] = 'signature'
            signature_mime.set_charset('us-ascii')

            # add signed message and signature to outer message
            unencrypted_msg.attach(inner_msg)
            unencrypted_msg.attach(signature_mime)
            unencrypted_msg['Content-Disposition'] = 'inline'
        else:
            unencrypted_msg = inner_msg

        if self.encrypt:
            plaintext = helper.email_as_string(unencrypted_msg)
            logging.debug('encrypting plaintext: ' + plaintext)

            try:
                encrypted_str = crypto.encrypt(plaintext,
                                               self.encrypt_keys.values())
            except gpgme.GpgmeError as e:
                raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_ENCRYPT)

            outer_msg = MIMEMultipart('encrypted',
                                      protocol='application/pgp-encrypted')

            version_str = 'Version: 1'
            encryption_mime = MIMEApplication(_data=version_str,
                                              _subtype='pgp-encrypted',
                                              _encoder=encode_7or8bit)
            encryption_mime.set_charset('us-ascii')

            encrypted_mime = MIMEApplication(_data=encrypted_str,
                                             _subtype='octet-stream',
                                             _encoder=encode_7or8bit)
            encrypted_mime.set_charset('us-ascii')
            outer_msg.attach(encryption_mime)
            outer_msg.attach(encrypted_mime)

        else:
            outer_msg = unencrypted_msg

        headers = self.headers.copy()
        # add Message-ID
        if 'Message-ID' not in headers:
            headers['Message-ID'] = [email.Utils.make_msgid()]

        if 'User-Agent' in headers:
            uastring_format = headers['User-Agent'][0]
        else:
            uastring_format = settings.get('user_agent').strip()
        uastring = uastring_format.format(version=__version__)
        if uastring:
            headers['User-Agent'] = [uastring]

        # copy headers from envelope to mail
        for k, vlist in headers.items():
            for v in vlist:
                outer_msg[k] = encode_header(k, v)

        return outer_msg
예제 #9
0
    def construct_mail(self):
        """
        compiles the information contained in this envelope into a
        :class:`email.Message`.
        """
        # Build body text part. To properly sign/encrypt messages later on, we
        # convert the text to its canonical format (as per RFC 2015).
        canonical_format = self.body.encode('utf-8')
        canonical_format = canonical_format.replace('\\t', ' ' * 4)
        textpart = MIMEText(canonical_format, 'plain', 'utf-8')

        # wrap it in a multipart container if necessary
        if self.attachments:
            inner_msg = MIMEMultipart()
            inner_msg.attach(textpart)
            # add attachments
            for a in self.attachments:
                inner_msg.attach(a.get_mime_representation())
        else:
            inner_msg = textpart

        if self.sign:
            plaintext = helper.email_as_string(inner_msg)
            logging.debug('signing plaintext: ' + plaintext)

            try:
                signatures, signature_str = crypto.detached_signature_for(
                    plaintext, self.sign_key)
                if len(signatures) != 1:
                    raise GPGProblem("Could not sign message (GPGME "
                                     "did not return a signature)",
                                     code=GPGCode.KEY_CANNOT_SIGN)
            except gpgme.GpgmeError as e:
                if e.code == gpgme.ERR_BAD_PASSPHRASE:
                    # If GPG_AGENT_INFO is unset or empty, the user just does
                    # not have gpg-agent running (properly).
                    if os.environ.get('GPG_AGENT_INFO', '').strip() == '':
                        msg = "Got invalid passphrase and GPG_AGENT_INFO\
                                not set. Please set up gpg-agent."
                        raise GPGProblem(msg, code=GPGCode.BAD_PASSPHRASE)
                    else:
                        raise GPGProblem("Bad passphrase. Is gpg-agent "
                                         "running?",
                                         code=GPGCode.BAD_PASSPHRASE)
                raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_SIGN)

            micalg = crypto.RFC3156_micalg_from_algo(signatures[0].hash_algo)
            unencrypted_msg = MIMEMultipart('signed', micalg=micalg,
                                            protocol='application/pgp-signature')

            # wrap signature in MIMEcontainter
            stype = 'pgp-signature; name="signature.asc"'
            signature_mime = MIMEApplication(_data=signature_str,
                                             _subtype=stype,
                                             _encoder=encode_7or8bit)
            signature_mime['Content-Description'] = 'signature'
            signature_mime.set_charset('us-ascii')

            # add signed message and signature to outer message
            unencrypted_msg.attach(inner_msg)
            unencrypted_msg.attach(signature_mime)
            unencrypted_msg['Content-Disposition'] = 'inline'
        else:
            unencrypted_msg = inner_msg

        if self.encrypt:
            plaintext = helper.email_as_string(unencrypted_msg)
            logging.debug('encrypting plaintext: ' + plaintext)

            try:
                encrypted_str = crypto.encrypt(plaintext,
                                               self.encrypt_keys.values())
            except gpgme.GpgmeError as e:
                raise GPGProblem(str(e), code=GPGCode.KEY_CANNOT_ENCRYPT)

            outer_msg = MIMEMultipart('encrypted',
                                      protocol='application/pgp-encrypted')

            version_str = 'Version: 1'
            encryption_mime = MIMEApplication(_data=version_str,
                                              _subtype='pgp-encrypted',
                                              _encoder=encode_7or8bit)
            encryption_mime.set_charset('us-ascii')

            encrypted_mime = MIMEApplication(_data=encrypted_str,
                                             _subtype='octet-stream',
                                             _encoder=encode_7or8bit)
            encrypted_mime.set_charset('us-ascii')
            outer_msg.attach(encryption_mime)
            outer_msg.attach(encrypted_mime)

        else:
            outer_msg = unencrypted_msg

        headers = self.headers.copy()
        # add Message-ID
        if 'Message-ID' not in headers:
            headers['Message-ID'] = [email.Utils.make_msgid()]

        if 'User-Agent' in headers:
            uastring_format = headers['User-Agent'][0]
        else:
            uastring_format = settings.get('user_agent').strip()
        uastring = uastring_format.format(version=__version__)
        if uastring:
            headers['User-Agent'] = [uastring]

        # copy headers from envelope to mail
        for k, vlist in headers.items():
            for v in vlist:
                outer_msg[k] = encode_header(k, v)

        return outer_msg
예제 #10
0
파일: thread.py 프로젝트: vrs/alot
    def apply(self, ui):
        # get message to forward if not given in constructor
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        envelope = Envelope()

        if self.inline:  # inline mode
            # set body text
            name, address = self.message.get_author()
            timestamp = self.message.get_date()
            qf = settings.get_hook('forward_prefix')
            if qf:
                quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
            else:
                quote = 'Forwarded message from %s (%s):\n' % (
                    name or address, timestamp)
            mailcontent = quote
            quotehook = settings.get_hook('text_quote')
            if quotehook:
                mailcontent += quotehook(self.message.accumulate_body())
            else:
                quote_prefix = settings.get('quote_prefix')
                for line in self.message.accumulate_body().splitlines():
                    mailcontent += quote_prefix + line + '\n'

            envelope.body = mailcontent

            for a in self.message.get_attachments():
                envelope.attach(a)

        else:  # attach original mode
            # attach original msg
            original_mail = Message()
            original_mail.set_type('message/rfc822')
            original_mail['Content-Disposition'] = 'attachment'
            original_mail.set_payload(email_as_string(mail))
            envelope.attach(Attachment(original_mail))

        # copy subject
        subject = decode_header(mail.get('Subject', ''))
        subject = 'Fwd: ' + subject
        forward_subject_hook = settings.get_hook('forward_subject')
        if forward_subject_hook:
            subject = forward_subject_hook(subject)
        else:
            fsp = settings.get('forward_subject_prefix')
            if not subject.startswith(('Fwd:', fsp)):
                subject = fsp + subject
        envelope.add('Subject', subject)

        # set From-header and sending account
        try:
            from_header, account = determine_sender(mail, 'reply')
        except AssertionError as e:
            ui.notify(e.message, priority='error')
            return
        envelope.add('From', from_header)

        # continue to compose
        ui.apply_command(ComposeCommand(envelope=envelope,
                                        spawn=self.force_spawn))
예제 #11
0
    def apply(self, ui):
        # get message to forward if not given in constructor
        if not self.message:
            self.message = ui.current_buffer.get_selected_message()
        mail = self.message.get_email()

        envelope = Envelope()

        if self.inline:  # inline mode
            # set body text
            name, address = self.message.get_author()
            timestamp = self.message.get_date()
            qf = settings.get_hook('forward_prefix')
            if qf:
                quote = qf(name, address, timestamp, ui=ui, dbm=ui.dbman)
            else:
                quote = 'Forwarded message from %s (%s):\n' % (
                    name or address, timestamp)
            mailcontent = quote
            quotehook = settings.get_hook('text_quote')
            if quotehook:
                mailcontent += quotehook(self.message.accumulate_body())
            else:
                quote_prefix = settings.get('quote_prefix')
                for line in self.message.accumulate_body().splitlines():
                    mailcontent += quote_prefix + line + '\n'

            envelope.body = mailcontent

            for a in self.message.get_attachments():
                envelope.attach(a)

        else:  # attach original mode
            # attach original msg
            original_mail = Message()
            original_mail.set_type('message/rfc822')
            original_mail['Content-Disposition'] = 'attachment'
            original_mail.set_payload(email_as_string(mail))
            envelope.attach(Attachment(original_mail))

        # copy subject
        subject = decode_header(mail.get('Subject', ''))
        subject = 'Fwd: ' + subject
        forward_subject_hook = settings.get_hook('forward_subject')
        if forward_subject_hook:
            subject = forward_subject_hook(subject)
        else:
            fsp = settings.get('forward_subject_prefix')
            if not subject.startswith(('Fwd:', fsp)):
                subject = fsp + subject
        envelope.add('Subject', subject)

        # set From-header and sending account
        try:
            from_header, account = determine_sender(mail, 'reply')
        except AssertionError as e:
            ui.notify(e.message, priority='error')
            return
        envelope.add('From', from_header)

        # continue to compose
        ui.apply_command(ComposeCommand(envelope=envelope,
                                        spawn=self.force_spawn))
예제 #12
0
    def apply(self, ui):
        if self.mail is None:
            if self.envelope is None:
                # needed to close later
                self.envelope_buffer = ui.current_buffer
                self.envelope = self.envelope_buffer.envelope

            # This is to warn the user before re-sending
            # an already sent message in case the envelope buffer
            # was not closed because it was the last remaining buffer.
            if self.envelope.sent_time:
                mod = self.envelope.modified_since_sent
                when = self.envelope.sent_time
                warning = 'A modified version of ' * mod
                warning += 'this message has been sent at %s.' % when
                warning += ' Do you want to resend?'
                if (yield ui.choice(warning, cancel='no',
                                    msg_position='left')) == 'no':
                    return

            # don't do anything if another SendCommand is in the middle of
            # sending the message and we were triggered accidentally
            if self.envelope.sending:
                msg = 'sending this message already!'
                logging.debug(msg)
                return

            clearme = ui.notify('constructing mail (GPG, attachments)\u2026',
                                timeout=-1)

            try:
                self.mail = self.envelope.construct_mail()
                self.mail['Date'] = email.Utils.formatdate(localtime=True)
                self.mail = email_as_string(self.mail)
            except GPGProblem as e:
                ui.clear_notify([clearme])
                ui.notify(e.message, priority='error')
                return

            ui.clear_notify([clearme])

        # determine account to use for sending
        msg = self.mail
        if not isinstance(msg, email.message.Message):
            msg = email.message_from_string(self.mail)
        sname, saddr = email.Utils.parseaddr(msg.get('From', ''))
        account = settings.get_account_by_address(saddr)
        if account is None:
            if not settings.get_accounts():
                ui.notify('no accounts set', priority='error')
                return
            else:
                account = settings.get_accounts()[0]

        # make sure self.mail is a string
        logging.debug(self.mail.__class__)
        if isinstance(self.mail, email.message.Message):
            self.mail = str(self.mail)

        # define callback
        def afterwards(returnvalue):
            initial_tags = []
            if self.envelope is not None:
                self.envelope.sending = False
                self.envelope.sent_time = datetime.datetime.now()
                initial_tags = self.envelope.tags
            logging.debug('mail sent successfully')
            ui.clear_notify([clearme])
            if self.envelope_buffer is not None:
                cmd = commands.globals.BufferCloseCommand(self.envelope_buffer)
                ui.apply_command(cmd)
            ui.notify('mail sent successfully')

            # store mail locally
            # This can raise StoreMailError
            path = account.store_sent_mail(self.mail)

            # add mail to index if maildir path available
            if path is not None:
                logging.debug('adding new mail to index')
                ui.dbman.add_message(path, account.sent_tags + initial_tags)
                ui.apply_command(globals.FlushCommand())

        # define errback
        def send_errb(failure):
            if self.envelope is not None:
                self.envelope.sending = False
            ui.clear_notify([clearme])
            failure.trap(SendingMailFailed)
            logging.error(failure.getTraceback())
            errmsg = 'failed to send: %s' % failure.value
            ui.notify(errmsg, priority='error', block=True)

        def store_errb(failure):
            failure.trap(StoreMailError)
            logging.error(failure.getTraceback())
            errmsg = 'could not store mail: %s' % failure.value
            ui.notify(errmsg, priority='error', block=True)

        # send out
        clearme = ui.notify('sending..', timeout=-1)
        if self.envelope is not None:
            self.envelope.sending = True
        d = account.send_mail(self.mail)
        d.addCallback(afterwards)
        d.addErrback(send_errb)
        d.addErrback(store_errb)
예제 #13
0
 def test_empty_message(self):
     message = email.message.Message()
     actual = helper.email_as_string(message)
     expected = '\r\n'
     self.assertEqual(actual, expected)