[#8241] Send mails using quoted-printable if the lines are longer than SMTP allows
diff --git a/Allura/allura/lib/mail_util.py b/Allura/allura/lib/mail_util.py
index bb768a1..ae72f93 100644
--- a/Allura/allura/lib/mail_util.py
+++ b/Allura/allura/lib/mail_util.py
@@ -175,11 +175,35 @@
return M.User.anonymous()
+# http://www.jebriggs.com/blog/2010/07/smtp-maximum-line-lengths/
+MAX_MAIL_LINE_OCTETS = 990
+
+
def encode_email_part(content, content_type):
try:
- return MIMEText(content.encode('ascii'), content_type, 'ascii')
- except:
- return MIMEText(content.encode('utf-8'), content_type, 'utf-8')
+ # simplest email - plain ascii
+ encoded_content = content.encode('ascii')
+ encoding = 'ascii'
+ except Exception:
+ # utf8 will get base64 encoded so we only do it if ascii fails
+ encoded_content = content.encode('utf-8')
+ encoding = 'utf-8'
+
+ for line in encoded_content.splitlines():
+ if len(line) > MAX_MAIL_LINE_OCTETS:
+ # switch to Quoted-Printable encoding to avoid too-long lines
+ # we could always Quoted-Printabl, but it makes the output a little messier and less human-readable
+ # the particular order of all these operations seems to be very important for everything to end up right
+ msg = MIMEText(None, content_type)
+ msg.replace_header('content-transfer-encoding', 'quoted-printable')
+ cs = email.charset.Charset('utf-8')
+ cs.header_encoding = email.charset.QP
+ cs.body_encoding = email.charset.QP
+ payload = cs.body_encode(content.encode('utf-8'))
+ msg.set_payload(payload, 'utf-8')
+ return msg
+ else:
+ return MIMEText(encoded_content, content_type, encoding)
def make_multipart_message(*parts):
diff --git a/Allura/allura/tests/test_tasks.py b/Allura/allura/tests/test_tasks.py
index 7aefa9c..2d27256 100644
--- a/Allura/allura/tests/test_tasks.py
+++ b/Allura/allura/tests/test_tasks.py
@@ -28,7 +28,7 @@
import mock
from pylons import tmpl_context as c, app_globals as g
from datadiff.tools import assert_equal
-from nose.tools import assert_in
+from nose.tools import assert_in, assert_less
from ming.orm import FieldProperty, Mapper
from ming.orm import ThreadLocalORMSession
from testfixtures import LogCapture
@@ -423,6 +423,28 @@
return_path, rcpts, body = _client.sendmail.call_args[0]
assert_in('From: "Test Admin" <test-admin@users.localhost>', body)
+ def test_send_email_long_lines_use_quoted_printable(self):
+ with mock.patch.object(mail_tasks.smtp_client, '_client') as _client:
+ mail_tasks.sendsimplemail(
+ fromaddr=u'"По" <foo@bar.com>',
+ toaddr='blah@blah.com',
+ text=(u'0123456789' * 100) + u'\n\n' + (u'Громады стро ' * 100),
+ reply_to=g.noreply,
+ subject=u'По оживлённым берегам',
+ message_id=h.gen_message_id())
+ return_path, rcpts, body = _client.sendmail.call_args[0]
+ body = body.split('\n')
+
+ for line in body:
+ assert_less(len(line), 991)
+
+ # plain text
+ assert_in('012345678901234567890123456789012345678901234567890123456789012345678901234=', body)
+ assert_in('=D0=93=D1=80=D0=BE=D0=BC=D0=B0=D0=B4=D1=8B =D1=81=D1=82=D1=80=D0=BE =D0=93=', body)
+ # html
+ assert_in('<div class=3D"markdown_content"><p>0123456789012345678901234567890123456789=', body)
+ assert_in('<p>=D0=93=D1=80=D0=BE=D0=BC=D0=B0=D0=B4=D1=8B =D1=81=D1=82=D1=80=D0=BE =D0=', body)
+
@td.with_wiki
def test_receive_email_ok(self):
c.user = M.User.by_username('test-admin')