def test_invalid_signature(self): """Invalid signature should not be handled as an OOPs. It should produce a message explaining to the user what went wrong. """ person = self.factory.makePerson() transaction.commit() email_address = person.preferredemail.email invalid_body = ('-----BEGIN PGP SIGNED MESSAGE-----\n' 'Hash: SHA1\n\n' 'Body\n' '-----BEGIN PGP SIGNATURE-----\n' 'Not a signature.\n' '-----END PGP SIGNATURE-----\n') ctrl = MailController(email_address, '*****@*****.**', 'subject', invalid_body, bulk=False) ctrl.send() handleMail() self.assertEqual([], self.oopses) [notification] = pop_notifications() body = notification.get_payload()[0].get_payload(decode=True) self.assertIn( "An error occurred while processing a mail you sent to " "Launchpad's email\ninterface.\n\n\n" "Error message:\n\nSignature couldn't be verified: " "(7, 58, u'No data')", body)
def test_invalid_signature(self): """Invalid signature should not be handled as an OOPs. It should produce a message explaining to the user what went wrong. """ person = self.factory.makePerson() transaction.commit() email_address = person.preferredemail.email invalid_body = ( "-----BEGIN PGP SIGNED MESSAGE-----\n" "Hash: SHA1\n\n" "Body\n" "-----BEGIN PGP SIGNATURE-----\n" "Not a signature.\n" "-----END PGP SIGNATURE-----\n" ) ctrl = MailController(email_address, "*****@*****.**", "subject", invalid_body, bulk=False) ctrl.send() handleMail() self.assertEqual([], self.oopses) [notification] = pop_notifications() body = notification.get_payload()[0].get_payload(decode=True) self.assertIn( "An error occurred while processing a mail you sent to " "Launchpad's email\ninterface.\n\n\n" "Error message:\n\nSignature couldn't be verified: " "(7, 58, u'No data')", body, )
def test_encodeOptimally_with_text(self): """Mostly-ascii attachments should be encoded as quoted-printable.""" text = u'I went to the caf\u00e9 today.'.encode('utf-8') part = Message() part.set_payload(text) MailController.encodeOptimally(part) self.assertEqual(text, part.get_payload(decode=True)) self.assertEqual('quoted-printable', part['Content-Transfer-Encoding'])
def test_encodeOptimally_with_binary(self): """Significantly non-ascii attachments should be base64-encoded.""" bytes = '\x00\xff\x44\x55\xaa\x99' part = Message() part.set_payload(bytes) MailController.encodeOptimally(part) self.assertEqual(bytes, part.get_payload(decode=True)) self.assertEqual('base64', part['Content-Transfer-Encoding'])
def test_MakeMessage_with_binary_attachment(self): """Message should still encode as ascii with non-ascii attachments.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', u'Body') ctrl.addAttachment('\x00\xffattach') message = ctrl.makeMessage() self.assertTrue( is_ascii_only(message.as_string()), "Non-ascii message string.")
def test_MakeMessage_with_binary_attachment(self): """Message should still encode as ascii with non-ascii attachments.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', u'Body') ctrl.addAttachment('\x00\xffattach') message = ctrl.makeMessage() self.assertTrue(is_ascii_only(message.as_string()), "Non-ascii message string.")
def test_encodeOptimally_with_ascii_text(self): """Mostly-ascii attachments should be encoded as quoted-printable.""" text = 'I went to the cafe today.\n\r' part = Message() part.set_payload(text) MailController.encodeOptimally(part, exact=False) self.assertEqual(part.get_payload(), part.get_payload(decode=True)) self.assertIs(None, part['Content-Transfer-Encoding'])
def test_MakeMessage_with_non_binary_attachment(self): """Simple ascii attachments should not be encoded.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', u'Body') ctrl.addAttachment('Hello, I am ascii') message = ctrl.makeMessage() body, attachment = message.get_payload() self.assertEqual(attachment.get_payload(), attachment.get_payload(decode=True))
def test_MakeMessage_with_non_binary_attachment(self): """Simple ascii attachments should not be encoded.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', u'Body') ctrl.addAttachment('Hello, I am ascii') message = ctrl.makeMessage() body, attachment = message.get_payload() self.assertEqual( attachment.get_payload(), attachment.get_payload(decode=True))
def test_encodeOptimally_with_7_bit_binary(self): """Mostly-ascii attachments should be encoded as quoted-printable.""" text = 'I went to the cafe today.\n\r' part = Message() part.set_payload(text) MailController.encodeOptimally(part) self.assertEqual(text, part.get_payload(decode=True)) self.assertEqual('I went to the cafe today.=0A=0D', part.get_payload()) self.assertEqual('quoted-printable', part['Content-Transfer-Encoding'])
def test_MakeMessage_no_attachment(self): """A message without an attachment should have a single body.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', 'body') message = ctrl.makeMessage() self.assertEqual('*****@*****.**', message['From']) self.assertEqual('*****@*****.**', message['To']) self.assertEqual('subject', message['Subject']) self.assertEqual('body', message.get_payload(decode=True))
def test_MakeMessage_no_attachment(self): """A message without an attachment should have a single body.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', 'body') message = ctrl.makeMessage() self.assertEqual('*****@*****.**', message['From']) self.assertEqual('*****@*****.**', message['To']) self.assertEqual('subject', message['Subject']) self.assertEqual('body', message.get_payload(decode=True))
def test_MakeMessage_unicode_body(self): # A message without an attachment with a unicode body gets sent as # UTF-8 encoded MIME text, and the message as a whole can be flattened # to a string with Unicode errors. ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', u'Bj\xf6rn') message = ctrl.makeMessage() # Make sure that the message can be flattened to a string as sendmail # does without raising a UnicodeEncodeError. message.as_string() self.assertEqual('Bj\xc3\xb6rn', message.get_payload(decode=True))
def test_MakeMessage_unicode_body(self): # A message without an attachment with a unicode body gets sent as # UTF-8 encoded MIME text, and the message as a whole can be flattened # to a string with Unicode errors. ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', u'Bj\xf6rn') message = ctrl.makeMessage() # Make sure that the message can be flattened to a string as sendmail # does without raising a UnicodeEncodeError. message.as_string() self.assertEqual('Bj\xc3\xb6rn', message.get_payload(decode=True))
def test_MakeMessageSpecialChars(self): """A message should have its to and from addrs converted to ascii.""" to_addr = u'\[email protected]' from_addr = u'\[email protected]' ctrl = MailController(from_addr, to_addr, 'subject', 'body') message = ctrl.makeMessage() self.assertEqual('=?utf-8?b?4YSAZnJvbUBleGFtcGxlLmNvbQ==?=', message['From']) self.assertEqual('=?utf-8?b?4YSAdG9AZXhhbXBsZS5jb20=?=', message['To']) self.assertEqual('subject', message['Subject']) self.assertEqual('body', message.get_payload(decode=True))
def test_MakeMessage_long_address(self): # Long email addresses are not wrapped if very long. These are due to # the paranoid checks that are in place to make sure that there are no # carriage returns in the to or from email addresses. to_addr = ('Launchpad Community Help Rotation team ' '<*****@*****.**>') from_addr = ('Some Random User With Many Public Names ' '<*****@*****.**') ctrl = MailController(from_addr, to_addr, 'subject', 'body') message = ctrl.makeMessage() self.assertEqual(from_addr, message['From']) self.assertEqual(to_addr, message['To'])
def test_MailController_into_timeline(self): """sendmail records stuff in the timeline.""" fake_mailer = RecordingMailer() self.useFixture(ZopeUtilityFixture(fake_mailer, IMailDelivery, 'Mail')) to_addresses = ['*****@*****.**', '*****@*****.**'] subject = self.getUniqueString('subject') with CaptureTimeline() as ctl: ctrl = MailController('*****@*****.**', to_addresses, subject, 'body', {'key': 'value'}) ctrl.send() self.assertEqual(fake_mailer.from_addr, '*****@*****.**') self.assertEqual(fake_mailer.to_addr, to_addresses) self.checkTimelineHasOneMailAction(ctl.timeline, subject=subject)
def test_MakeMessage_with_specific_attachment(self): """Explicit attachment params should be obeyed.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', 'body') ctrl.addAttachment( 'attach', 'text/plain', inline=True, filename='README') message = ctrl.makeMessage() attachment = message.get_payload()[1] self.assertEqual('attach', attachment.get_payload(decode=True)) self.assertEqual( 'text/plain', attachment['Content-Type']) self.assertEqual( 'inline; filename="README"', attachment['Content-Disposition'])
def test_MakeMessage_with_specific_attachment(self): """Explicit attachment params should be obeyed.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', 'body') ctrl.addAttachment('attach', 'text/plain', inline=True, filename='README') message = ctrl.makeMessage() attachment = message.get_payload()[1] self.assertEqual('attach', attachment.get_payload(decode=True)) self.assertEqual('text/plain', attachment['Content-Type']) self.assertEqual('inline; filename="README"', attachment['Content-Disposition'])
def test_MakeMessage_long_address(self): # Long email addresses are not wrapped if very long. These are due to # the paranoid checks that are in place to make sure that there are no # carriage returns in the to or from email addresses. to_addr = ( 'Launchpad Community Help Rotation team ' '<*****@*****.**>') from_addr = ( 'Some Random User With Many Public Names ' '<*****@*****.**') ctrl = MailController(from_addr, to_addr, 'subject', 'body') message = ctrl.makeMessage() self.assertEqual(from_addr, message['From']) self.assertEqual(to_addr, message['To'])
def test_MakeMessage_with_attachment(self): """A message with an attachment should be multipart.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', 'body') ctrl.addAttachment('attach') message = ctrl.makeMessage() self.assertEqual('*****@*****.**', message['From']) self.assertEqual('*****@*****.**', message['To']) self.assertEqual('subject', message['Subject']) body, attachment = message.get_payload() self.assertEqual('body', body.get_payload(decode=True)) self.assertEqual('attach', attachment.get_payload(decode=True)) self.assertEqual('application/octet-stream', attachment['Content-Type']) self.assertEqual('attachment', attachment['Content-Disposition'])
def test_MakeMessage_with_attachment(self): """A message with an attachment should be multipart.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', 'body') ctrl.addAttachment('attach') message = ctrl.makeMessage() self.assertEqual('*****@*****.**', message['From']) self.assertEqual('*****@*****.**', message['To']) self.assertEqual('subject', message['Subject']) body, attachment = message.get_payload() self.assertEqual('body', body.get_payload(decode=True)) self.assertEqual('attach', attachment.get_payload(decode=True)) self.assertEqual( 'application/octet-stream', attachment['Content-Type']) self.assertEqual('attachment', attachment['Content-Disposition'])
def test_MailController_into_timeline(self): """sendmail records stuff in the timeline.""" fake_mailer = RecordingMailer() self.useFixture(ZopeUtilityFixture( fake_mailer, IMailDelivery, 'Mail')) to_addresses = ['*****@*****.**', '*****@*****.**'] subject = self.getUniqueString('subject') with CaptureTimeline() as ctl: ctrl = MailController( '*****@*****.**', to_addresses, subject, 'body', {'key': 'value'}) ctrl.send() self.assertEquals(fake_mailer.from_addr, '*****@*****.**') self.assertEquals(fake_mailer.to_addr, to_addresses) self.checkTimelineHasOneMailAction(ctl.timeline, subject=subject)
def test_sendUsesRealTo(self): """MailController.envelope_to is provided as to_addrs.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', 'body', envelope_to=['*****@*****.**']) sendmail_kwargs = {} def fake_sendmail(message, to_addrs=None, bulk=True): sendmail_kwargs.update(locals()) real_sendmail = sendmail.sendmail sendmail.sendmail = fake_sendmail try: ctrl.send() finally: sendmail.sendmail = real_sendmail self.assertEqual('*****@*****.**', sendmail_kwargs['message']['To']) self.assertEqual(['*****@*****.**'], sendmail_kwargs['to_addrs'])
def create_mail_for_directoryMailBox(from_addr, to_addrs, subject, body, headers=None): """Create a email in the DirectoryMailBox.""" mc = MailController(from_addr, to_addrs, subject, body, headers) message = mc.makeMessage() if 'message-id' not in message: message['Message-Id'] = get_msgid() if 'date' not in message: message['Date'] = formatdate() # Since this is faking incoming email, set the X-Original-To. message['X-Original-To'] = to_addrs mailbox = getUtility(IMailBox) msg_file = open( os.path.join(mailbox.mail_dir, message['Message-Id']), 'w') msg_file.write(message.as_string()) msg_file.close()
def test_mail_too_big(self): """Much-too-big mail should generate a bounce, not an OOPS. See <https://bugs.launchpad.net/launchpad/+bug/893612>. """ person = self.factory.makePerson() transaction.commit() email_address = person.preferredemail.email fat_body = "\n".join(["some big mail with this line repeated many many times\n"] * 1000000) ctrl = MailController(email_address, "*****@*****.**", "subject", fat_body, bulk=False) ctrl.send() handleMail() self.assertEqual([], self.oopses) [notification] = pop_notifications() body = notification.get_payload()[0].get_payload(decode=True) self.assertIn("The mail you sent to Launchpad is too long.", body) self.assertIn("was 55 MB and the limit is 10 MB.", body)
def create_mail_for_directoryMailBox(from_addr, to_addrs, subject, body, headers=None): """Create a email in the DirectoryMailBox.""" mc = MailController(from_addr, to_addrs, subject, body, headers) message = mc.makeMessage() if 'message-id' not in message: message['Message-Id'] = get_msgid() if 'date' not in message: message['Date'] = formatdate() # Since this is faking incoming email, set the X-Original-To. message['X-Original-To'] = to_addrs mailbox = getUtility(IMailBox) msg_file = open(os.path.join(mailbox.mail_dir, message['Message-Id']), 'w') msg_file.write(message.as_string()) msg_file.close()
def test_mail_too_big(self): """Much-too-big mail should generate a bounce, not an OOPS. See <https://bugs.launchpad.net/launchpad/+bug/893612>. """ person = self.factory.makePerson() transaction.commit() email_address = person.preferredemail.email fat_body = '\n'.join( ['some big mail with this line repeated many many times\n'] * 1000000) ctrl = MailController( email_address, '*****@*****.**', 'subject', fat_body, bulk=False) ctrl.send() handleMail() self.assertEqual([], self.oopses) [notification] = pop_notifications() body = notification.get_payload()[0].get_payload(decode=True) self.assertIn("The mail you sent to Launchpad is too long.", body) self.assertIn("was 55 MB and the limit is 10 MB.", body)
def test_constructor2(self): """Test the explicit construction behaviour. Since to is a list, it is not converted into a list. """ ctrl = MailController('*****@*****.**', ['*****@*****.**', '*****@*****.**'], 'subject', 'body', {'key': 'value'}) self.assertEqual(['*****@*****.**', '*****@*****.**'], ctrl.to_addrs) self.assertEqual({'key': 'value'}, ctrl.headers) self.assertEqual('body', ctrl.body) self.assertEqual([], ctrl.attachments)
def test_constructor(self): """Test the default construction behaviour. Defaults should be empty. The 'to' should be converted to a list. """ ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', 'body') self.assertEqual('*****@*****.**', ctrl.from_addr) self.assertEqual(['*****@*****.**'], ctrl.to_addrs) self.assertEqual('subject', ctrl.subject) self.assertEqual({}, ctrl.headers) self.assertEqual('body', ctrl.body) self.assertEqual([], ctrl.attachments)
def getOopsMailController(self, oops_id): """Return a MailController for notifying people about oopses. Return None if there is no-one to notify. """ recipients = self.getOopsRecipients() if len(recipients) == 0: return None subject = 'Launchpad internal error' body = ('Launchpad encountered an internal error during the following' ' operation: %s. It was logged with id %s. Sorry for the' ' inconvenience.' % (self.getOperationDescription(), oops_id)) from_addr = config.canonical.noreply_from_address return MailController(from_addr, recipients, subject, body)
def getUserErrorMailController(self, e): """Return a MailController for notifying about user errors. Return None if there is no-one to notify. """ recipients = self.getErrorRecipients() if len(recipients) == 0: return None subject = 'Launchpad error while %s' % self.getOperationDescription() body = ('Launchpad encountered an error during the following' ' operation: %s. %s' % (self.getOperationDescription(), str(e))) from_addr = config.canonical.noreply_from_address return MailController(from_addr, recipients, subject, body)
def test_addAttachment(self): """addAttachment should add a part to the list of attachments.""" ctrl = MailController('*****@*****.**', '*****@*****.**', 'subject', 'body') ctrl.addAttachment('content1') attachment = ctrl.attachments[0] self.assertEqual('application/octet-stream', attachment['Content-Type']) self.assertEqual('attachment', attachment['Content-Disposition']) self.assertEqual('content1', attachment.get_payload(decode=True)) ctrl.addAttachment('content2', 'text/plain', inline=True, filename='name1') attachment = ctrl.attachments[1] self.assertEqual('text/plain', attachment['Content-Type']) self.assertEqual('inline; filename="name1"', attachment['Content-Disposition']) self.assertEqual('content2', attachment.get_payload(decode=True)) ctrl.addAttachment('content2', 'text/plain', inline=True, filename='name1')
def test_addAttachment(self): """addAttachment should add a part to the list of attachments.""" ctrl = MailController( '*****@*****.**', '*****@*****.**', 'subject', 'body') ctrl.addAttachment('content1') attachment = ctrl.attachments[0] self.assertEqual( 'application/octet-stream', attachment['Content-Type']) self.assertEqual( 'attachment', attachment['Content-Disposition']) self.assertEqual( 'content1', attachment.get_payload(decode=True)) ctrl.addAttachment( 'content2', 'text/plain', inline=True, filename='name1') attachment = ctrl.attachments[1] self.assertEqual( 'text/plain', attachment['Content-Type']) self.assertEqual( 'inline; filename="name1"', attachment['Content-Disposition']) self.assertEqual( 'content2', attachment.get_payload(decode=True)) ctrl.addAttachment( 'content2', 'text/plain', inline=True, filename='name1')