Better handle invalid character encodings in process_email and feedback_email commands. Add tests of this using stdin. Commit ready for merge.

- Legacy-Id: 19694
This commit is contained in:
Jennifer Richards 2021-11-19 00:41:05 +00:00
parent 5c2765da94
commit dcf4251363
5 changed files with 66 additions and 13 deletions

View file

@ -23,12 +23,9 @@ class Command(EmailOnFailureCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
email = options.get('email', None) email = options.get('email', None)
if not email: binary_input = io.open(email, 'rb') if email else sys.stdin.buffer
msg = sys.stdin.read() self.msg_bytes = binary_input.read()
self.msg_bytes = msg.encode() msg = self.msg_bytes.decode()
else:
self.msg_bytes = io.open(email, "rb").read()
msg = self.msg_bytes.decode()
try: try:
process_response_email(msg) process_response_email(msg)

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Tests of ipr management commands""" """Tests of ipr management commands"""
import mock import mock
import sys
from django.core.management import call_command from django.core.management import call_command
from django.test.utils import override_settings from django.test.utils import override_settings
@ -26,7 +27,7 @@ class ProcessEmailTests(TestCase):
@mock.patch('ietf.utils.management.base.send_smtp') @mock.patch('ietf.utils.management.base.send_smtp')
@mock.patch('ietf.ipr.management.commands.process_email.process_response_email') @mock.patch('ietf.ipr.management.commands.process_email.process_response_email')
def test_send_error_to_admin(self, process_mock, send_smtp_mock): def test_send_error_to_admin(self, process_mock, send_smtp_mock):
"""The process_email command should email the admins on error""" """The process_email command should email the admins on error in process_response_email"""
# arrange an mock error during processing # arrange an mock error during processing
process_mock.side_effect = RuntimeError('mock error') process_mock.side_effect = RuntimeError('mock error')
@ -47,3 +48,32 @@ class ProcessEmailTests(TestCase):
self.assertIn('mock.py', content, 'File where error occurred should be included in error email') self.assertIn('mock.py', content, 'File where error occurred should be included in error email')
self.assertIn('traceback', traceback.lower(), 'Traceback should be attached to error email') self.assertIn('traceback', traceback.lower(), 'Traceback should be attached to error email')
self.assertEqual(original, 'contents', 'Original message should be attached to error email') self.assertEqual(original, 'contents', 'Original message should be attached to error email')
@mock.patch('ietf.utils.management.base.send_smtp')
@mock.patch('ietf.ipr.management.commands.process_email.process_response_email')
def test_invalid_character_encodings(self, process_mock, send_smtp_mock):
"""The process_email command should attach messages with invalid encoding when using a file input"""
invalid_characters = b'\xfe\xff'
with name_of_file_containing(invalid_characters, mode='wb') as filename:
call_command('process_email', email_file=filename)
self.assertFalse(process_mock.called) # should not even try to process illegally encoded messages
self.assertTrue(send_smtp_mock.called)
(msg,) = send_smtp_mock.call_args.args
parts = msg.get_payload()
self.assertEqual(len(parts), 3, 'Error email should contain message, traceback, and original message')
@mock.patch.object(sys.stdin.buffer, 'read')
@mock.patch('ietf.utils.management.base.send_smtp')
@mock.patch('ietf.ipr.management.commands.process_email.process_response_email')
def test_invalid_character_encodings_via_stdin(self, process_mock, send_smtp_mock, stdin_read_mock):
"""The process_email command should attach messages with invalid encoding when using stdin"""
invalid_characters = b'\xfe\xff'
stdin_read_mock.return_value = invalid_characters
call_command('process_email')
self.assertFalse(process_mock.called) # should not even try to process illegally encoded messages
self.assertTrue(send_smtp_mock.called)
(msg,) = send_smtp_mock.call_args.args
parts = msg.get_payload()
self.assertEqual(len(parts), 3, 'Error email should contain message, traceback, and original message')

View file

@ -42,10 +42,8 @@ class Command(EmailOnFailureCommand):
except NomCom.DoesNotExist: except NomCom.DoesNotExist:
raise CommandError("NomCom %s does not exist or it isn't active" % year) raise CommandError("NomCom %s does not exist or it isn't active" % year)
if not email: binary_input = io.open(email, 'rb') if email else sys.stdin.buffer
self.msg = io.open(sys.stdin.fileno(), 'rb').read() self.msg = binary_input.read()
else:
self.msg = io.open(email, "rb").read()
try: try:
feedback = create_feedback_email(self.nomcom, self.msg) feedback = create_feedback_email(self.nomcom, self.msg)

View file

@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Tests of nomcom management commands""" """Tests of nomcom management commands"""
import mock import mock
import sys
from collections import namedtuple from collections import namedtuple
@ -83,3 +84,30 @@ class FeedbackEmailTests(TestCase):
(self.nomcom, b'feedback message'), (self.nomcom, b'feedback message'),
'feedback_email should process the correct email for the correct nomcom' 'feedback_email should process the correct email for the correct nomcom'
) )
@mock.patch('ietf.utils.management.base.send_smtp')
def test_invalid_character_encodings(self, send_smtp_mock):
"""The feedback_email command should send a message when file input has invalid encoding"""
# mock an exception in create_feedback_email()
invalid_characters = b'\xfe\xff'
with name_of_file_containing(invalid_characters, mode='wb') as filename:
call_command('feedback_email', nomcom_year=self.year, email_file=filename)
self.assertTrue(send_smtp_mock.called)
(msg,) = send_smtp_mock.call_args.args # get the message to be sent
parts = msg.get_payload()
self.assertEqual(len(parts), 2, 'Error email should contain message and original message')
@mock.patch.object(sys.stdin.buffer, 'read')
@mock.patch('ietf.utils.management.base.send_smtp')
def test_invalid_character_encodings_via_stdin(self, send_smtp_mock, stdin_read_mock):
"""The feedback_email command should send a message when stdin input has invalid encoding"""
# mock an exception in create_feedback_email()
invalid_characters = b'\xfe\xff'
stdin_read_mock.return_value = invalid_characters
call_command('feedback_email', nomcom_year=self.year)
self.assertTrue(send_smtp_mock.called)
(msg,) = send_smtp_mock.call_args.args # get the message to be sent
parts = msg.get_payload()
self.assertEqual(len(parts), 2, 'Error email should contain message and original message')

View file

@ -104,9 +104,9 @@ def reload_db_objects(*objects):
return t return t
@contextmanager @contextmanager
def name_of_file_containing(contents): def name_of_file_containing(contents, mode='w'):
"""Get a context with the name of an email file""" """Get a context with the name of an email file"""
f = NamedTemporaryFile('w', delete=False) f = NamedTemporaryFile(mode, delete=False)
f.write(contents) f.write(contents)
f.close() f.close()
yield f.name # hand the filename to the context yield f.name # hand the filename to the context