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)
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
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
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())
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])
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
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
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))
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)
def test_empty_message(self): message = email.message.Message() actual = helper.email_as_string(message) expected = '\r\n' self.assertEqual(actual, expected)