Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UTF-8 encoded mails are not sent #30

Closed
viktordick opened this issue Jul 14, 2020 · 19 comments · Fixed by #32
Closed

UTF-8 encoded mails are not sent #30

viktordick opened this issue Jul 14, 2020 · 19 comments · Fixed by #32

Comments

@viktordick
Copy link

According to the documentation in smptlib.sendmail,

    msg may be a string containing characters in the ASCII range, or a byte
    string.  A string is encoded to bytes using the ascii codec, and lone
    \\r and \\n characters are converted to \\r\\n characters.

Since switching to Zope 4, we are unable to send mails that have a UTF-8 encoded body - Even if we transform it to bytes ourselves, Products.MailHost converts it back to strings before sending it to zope.sendmail and smptlib fails to encode the passed message using ASCII during the second phase of the two phase commit. Our quick fix was to change zope.sendmail to encode the message using utf-8 before passing it to smtplib.sendmail.

A really clean way would probably be to inspect the headers (which should be ASCII) and check for a Content-Type and Content-Transfer-Encoding. Maybe it is even possible to have a multipart message with different encodings (not sure). But since UTF-8 is such a popular choice, we used that for now.

I am not sure if the fix should happen in zope.sendmail or somewhere further up like Products.MailHost. If it should be zope.sendmail, I could try to supply a PR. Otherwise I would need to be pointed to the right direction.

@d-maurer
Copy link
Contributor

d-maurer commented Jul 14, 2020 via email

@viktordick
Copy link
Author

Thanks for the reply.

I actually read this as: Either ensure that you handled the encoding yourself (passing bytes) or only use ASCII. stmplib will not try to guess the encoding.

I am pretty sure that current mail clients assume that every MTA on the way supports https://tools.ietf.org/html/rfc6152. At least if I send a message with Thunderbird with default settings, it uses an 8bit-encoding (In fact, utf-8).

More to the point, passing UTF-8 encoded messages to smtplib.send yields the expected result (assuming the correct headers are set and only the body uses UTF-8, not the headers).

@d-maurer
Copy link
Contributor

d-maurer commented Jul 14, 2020 via email

@viktordick
Copy link
Author

I agree. I posted the issue here because I could not exactly pinpoint the location in Products.MailHost where the issue would have to be fixed, while it was a two-liner if I place the fix in zope.sendmail. But the correct place is Products.MailHost.

Is it possible to move the issue there? Otherwise I will close this and re-open another issue there (and take another look at what needs to be fixed there).

@dataflake dataflake transferred this issue from zopefoundation/zope.sendmail Jul 14, 2020
@viktordick
Copy link
Author

@jugmac00 can you give me a hint how exactly you are using Products.MailHost in Zope4, in particular with non-ASCII characters? My minimal working example for creating a failure is something like a PythonScript containing

mail = '''\
To: test@test.de
From: <no-reply@test.de>
Subject: Test
Mime-Version: 1.0
Content-Type: text/html; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit

<!DOCTYPE HTML>
<html>
<head>

<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
Testü
</body>
</html>'''

context.mailhost.send(messageText=mail)

What exactly am I missing?

@d-maurer
Copy link
Contributor

d-maurer commented Jul 15, 2020 via email

@d-maurer
Copy link
Contributor

d-maurer commented Jul 15, 2020 via email

@d-maurer
Copy link
Contributor

d-maurer commented Jul 15, 2020 via email

d-maurer added a commit that referenced this issue Jul 16, 2020
@d-maurer
Copy link
Contributor

@viktordick Could you check whether #32 solves your issue?

@jugmac00
Copy link
Member

@viktordick When I inherited my Zope app 2015 I just continued to work with the idioms my predecessor left in the app.

So, it is something similar like this...

        outer = MIMEMultipart()
        outer['Subject'] = SUBJECT
        outer['To'] = TO
        outer['From'] = FROM
        outer.preamble = 'This is a MIME encoded multipart message.\n'
        outer.epilogue = ''
        msg = MIMEText(text, _subtype='html', _charset='utf-8')
        outer.attach(msg)
        ...
        self.MailHost.send(outer.as_string(), mto=TO, mfrom=FROM)

d-maurer added a commit that referenced this issue Jul 17, 2020
…esponding messages could not be sent anyway due to #30
@viktordick
Copy link
Author

Thanks very much for your efforts so far!
Sorry for not answering sooner, I have not found time yet to understand and test the suggestions. I hope I find the time today or tomorrow.

@viktordick
Copy link
Author

    msg = MIMEText(text, _subtype='html', _charset='utf-8')
    outer.attach(msg)
    ...
    self.MailHost.send(outer.as_string(), mto=TO, mfrom=FROM)

@jugmac00 I tested this and it results in a base64 encoded message. Which of course also gets the job done, it just makes inspection more difficult and increases the message size. I guess my problem with this solution is purely aesthetic (we have 2020 and I do not think that there is a MTA out there that is unable to use a Content-Transfer-Encoding of 8bit).

@d-maurer
Copy link
Contributor

d-maurer commented Jul 18, 2020 via email

viktordick referenced this issue in zopefoundation/zope.sendmail Jul 20, 2020
This is based on the discussion in
`https://github.com/zopefoundation/Products.MailHost/issues/30`
and
`https://github.com/zopefoundation/Products.MailHost/pull/32`.

In order to allow messages with 8bit-encoding to be sent using smtplib,
they have to be prepared as bytes. Since any preparation and header
parsing and manipulation is done in `Products.MailHost`, `zope.sendmail`
only needs to store messages internally as bytes and allow them to be
passed as bytes.
@jugmac00
Copy link
Member

@d-maurer and @viktordick - could you add a test somewhere, which makes sure the way I create emails ( see #30 (comment) ) will still work with your pull requests?

Don't get me wrong - I trust you both you know what you do... it is just... if something gets fixed which worked for 15 years I am always a bit .. anxious :-)

@d-maurer
Copy link
Contributor

d-maurer commented Jul 20, 2020 via email

@jugmac00
Copy link
Member

As I wrote to Viktor, I do not mind if others contribute to "my" PR. I invite you to add the test.

@d-maurer

Is this test ok? Then I push it to your pr / branch (except I will put the import statements at the top).

    def testSendMultipartMessageObject(self):
        from email.mime.multipart import MIMEMultipart
        from email.mime.text import MIMEText
        TO = 'Name, Nick <recipient@example.com>'
        FROM = 'sender@example.com'

        outer = MIMEMultipart()
        outer['Subject'] = 'This is the subject'
        outer['To'] = TO
        outer['From'] = FROM
        outer.preamble = 'This is a MIME encoded multipart message.\n'
        outer.epilogue = ''
        body = 'This is the message body.'
        msg = MIMEText(body, _subtype='text', _charset='utf-8')
        outer.attach(msg)
        outer.set_boundary('===============8011890429167670482==')

        mailhost = self._makeOne('MailHost')
        mailhost.send(
            outer.as_string(),
            mto=TO,
            mfrom=FROM,
        )
        # date_pattern matches e.g. `Tue, 21 Jul 2020 08:50:04 +0200`
        date_pattern = rb'[a-zA-Z]{2,3}, [0-9]{1,2} [a-zA-Z]{3} [0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-]{1}[0-9]{4}'  # noqa: E501
        pattern = rb"""Content-Type: multipart/mixed; """\
                  rb"""boundary="===============8011890429167670482=="\n"""\
                  rb"""MIME-Version: 1.0\n"""\
                  rb"""Subject: This is the subject\n"""\
                  rb"""To: Name, Nick <recipient@example.com>\n"""\
                  rb"""From: sender@example.com\n"""\
                  rb"""Date: %s\n\n"""\
                  rb"""This is a MIME encoded multipart message.\n\n"""\
                  rb"""--===============8011890429167670482==\n"""\
                  rb"""Content-Type: text/text; charset="utf-8"\n"""\
                  rb"""MIME-Version: 1.0\n"""\
                  rb"""Content-Transfer-Encoding: quoted-printable\n\n"""\
                  rb"""This is the message body.\n"""\
                  rb"""--===============8011890429167670482==--\n""" % date_pattern  # noqa: E501
        self.assertRegex(mailhost.sent, pattern)

@d-maurer
Copy link
Contributor

d-maurer commented Jul 21, 2020 via email

@jugmac00
Copy link
Member

@d-maurer Thanks your reply.

I pushed the code to your branch, after I updated my code slightly to also pass the Python 2 tests.

The two noqa: E501 were necessary as those lines were slightly longer than allowed.

While possible, breaking those lines into multiple lines seemed very unnatural.

viktordick referenced this issue in zopefoundation/zope.sendmail Jul 24, 2020
This is based on the discussion in
`https://github.com/zopefoundation/Products.MailHost/issues/30`
and
`https://github.com/zopefoundation/Products.MailHost/pull/32`.

In order to allow messages with 8bit-encoding to be sent using smtplib,
they have to be prepared as bytes. Since any preparation and header
parsing and manipulation is done in `Products.MailHost`, `zope.sendmail`
only needs to store messages internally as bytes and allow them to be
passed as bytes.

Co-authored-by: dieter <dieter@handshake.de>
mauritsvanrees added a commit to plone/buildout.coredev that referenced this issue Sep 15, 2020
I saw test failures earlier when I tried it in combination with other new packages.
Now let's try it on its own.
I expect failures due to the changes here: zopefoundation/Products.MailHost#30
@mauritsvanrees
Copy link
Member

Possible regression on Python 3, at least in Plone tests: #33

mauritsvanrees added a commit to plone/buildout.coredev that referenced this issue Sep 23, 2020
I saw test failures earlier when I tried it in combination with other new packages.
Now let's try it on its own.
I expect failures due to the changes here: zopefoundation/Products.MailHost#30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants