From c1062c9aef1303327890cb9b92117ea505754efe Mon Sep 17 00:00:00 2001 From: Dave Brondsema Date: Tue, 12 Mar 2024 10:32:37 -0400 Subject: [PATCH] encode emails when the body has long lines. [#8533] erroneously removed handling of it. This is a better solution than before too --- Allura/allura/lib/mail_util.py | 5 +++++ Allura/allura/tests/test_tasks.py | 31 +++++++++++++++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py index 24f1eb8c6..285c12d73 100644 --- a/Allura/allura/lib/mail_util.py +++ b/Allura/allura/lib/mail_util.py @@ -194,6 +194,11 @@ def encode_email_part(content, content_type): # simplest email - plain ascii encoded_content = content.encode('ascii') encoding = 'ascii' + for line in encoded_content.splitlines(): + if len(line) > MAX_MAIL_LINE_OCTETS: + # force base64 content-encoding to make lines shorter + encoding = 'utf-8' + break except Exception: # utf8 will get base64 encoded so we only do it if ascii fails encoded_content = content.encode('utf-8') diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py index 56ab38f55..54e936ec6 100644 --- a/Allura/allura/tests/test_tasks.py +++ b/Allura/allura/tests/test_tasks.py @@ -14,7 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. - +import email.parser +import email.iterators import operator import shutil from textwrap import dedent @@ -28,6 +29,7 @@ import tg import mock from tg import tmpl_context as c, app_globals as g +import pytest from ming.odm import FieldProperty, Mapper from ming.odm import ThreadLocalODMSession @@ -464,29 +466,34 @@ def test_fromaddr_objectid_not_str(self): return_path, rcpts, body = _client.sendmail.call_args[0] assert 'From: "Test Admin" ' in body - def test_send_email_long_lines_use_quoted_printable(self): + @pytest.mark.parametrize('bodychars', [ + '0123456789', # plain ascii is handled different since it doesn't necessarily need to be encoded + 'Громады стро ', + ]) + def test_send_email_long_lines(self, bodychars): with mock.patch.object(mail_tasks.smtp_client, '_client') as _client: mail_tasks.sendsimplemail( fromaddr='"По" ', toaddr='blah@blah.com', - text=('0123456789' * 100) + '\n\n' + ('Громады стро ' * 100), + text=bodychars * 100, reply_to=g.noreply, subject='123451234512345' * 100, references=['foo@example.com'] * 100, # needs to handle really long headers as well message_id=h.gen_message_id()) return_path, rcpts, body = _client.sendmail.call_args[0] - body = body.split(email_policy.linesep) + body_lines = body.split(email_policy.linesep) - for line in body: + for line in body_lines: assert len(line) <= MAX_MAIL_LINE_OCTETS - bodystr = ''.join(body) - # plain text - assert b64encode(b'012345678901234567890123').decode('utf8') in bodystr - assert b64encode('Громады стро '.encode('utf8')).decode('utf8') in bodystr - # html - assert b64encode(b'

012345678901234567890123').decode('utf8') in bodystr - assert b64encode('

Громады стро '.encode('utf8')).decode('utf8') in bodystr + msg = email.parser.Parser().parsestr(body) + plain_subpart = next(email.iterators.typed_subpart_iterator(msg, 'text', 'plain')) + plain = plain_subpart.get_payload(decode=True).decode('utf-8') + html_subpart = next(email.iterators.typed_subpart_iterator(msg, 'text', 'html')).get_payload(decode=True) + html = html_subpart.decode('utf-8') + + assert (bodychars + bodychars) in plain + assert f'

{bodychars}{bodychars}' in html @td.with_wiki def test_receive_email_ok(self):