Opened 8 years ago

Closed 8 years ago

#24623 closed Bug (fixed)

AttributeError when trying to send an utf-8 encoded email with text attachments (mime type: text/*)

Reported by: tkrapp Owned by: Konrad Świat
Component: Core (Mail) Version: 1.7
Severity: Normal Keywords: email python3 SafeMIMEText MIMEText attach_file
Cc: Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

I get "AttributeError: 'bytes' object has no attribute 'encode'" when trying to send a utf-8 encoded email with text attachments (see stack trace below).

As far as I can see this happens when using python 3 (like we do) and utf-8 as encoding.

  • If you attach a text/csv file for example, django tries to instantiate a SafeMIMEText object with the following parameters: (text: bytes(...), subtype: 'csv', charset: 'utf-8') (django/core/mail/message.py:338)
  • SafeMIMEText calls init of MIMEText and sets the charset to None explicitly when charset was 'utf-8' before but does not convert text from bytes to str which is expected by MIMEText (django/core/mail/message.py:175)
  • MIMEText then tries to encode the provided _text parameter as 'us-ascii' and fails since a bytes object has no encode-method in python3 (email/mime/text.py:33)

Example

from django.core.mail import EmailMessage

email = EmailMessage('subject', 'body', 'from@example.com', ['to@example.com'])
email.attach_file('/path/to/csv.file')

email.send()

Solutions I could think of:

  • Convert text parameter of SafeMIMEText from bytes to str before calling init of MIMEText. This would imply that one knows the encoding of the text file or the file encoding is detected automatically.
  • Convert the file content and call attach of EmailMessage.

Stack trace:

Traceback (most recent call last):
  File "./manage.py", line 10, in <module>
    execute_from_command_line(sys.argv)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/usr/local/lib/python3.4/dist-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/mnt/storage/django/code/trouble_ticket/management/commands/send2ntt.py", line 41, in handle
    raise(e)
  File "/mnt/storage/django/code/trouble_ticket/management/commands/send2ntt.py", line 39, in handle
    email.send()
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 286, in send
    return self.get_connection(fail_silently).send_messages([self])
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/backends/smtp.py", line 99, in send_messages
    sent = self._send(message)
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/backends/smtp.py", line 113, in _send
    message = email_message.message()
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 253, in message
    msg = self._create_message(msg)
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 312, in _create_message
    return self._create_attachments(msg)
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 325, in _create_attachments
    msg.attach(self._create_attachment(*attachment))
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 367, in _create_attachment
    attachment = self._create_mime_attachment(content, mimetype)
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 338, in _create_mime_attachment
    attachment = SafeMIMEText(content, subtype, encoding)
  File "/usr/local/lib/python3.4/dist-packages/django/core/mail/message.py", line 175, in __init__
    MIMEText.__init__(self, text, subtype, None)
  File "/usr/lib/python3.4/email/mime/text.py", line 33, in __init__
    _text.encode('us-ascii')
AttributeError: 'bytes' object has no attribute 'encode'

Attachments (2)

24623-test.diff (1.2 KB) - added by Tim Graham 8 years ago.
24623_workaround_1.diff (712 bytes) - added by Konrad Świat 8 years ago.

Download all attachments as: .zip

Change History (15)

comment:1 Changed 8 years ago by Tim Graham

Triage Stage: UnreviewedAccepted

Reproduced with the attached test (EmailMessage.attach_file() is untested at the moment).

Changed 8 years ago by Tim Graham

Attachment: 24623-test.diff added

comment:2 Changed 8 years ago by ChillarAnand

Owner: changed from nobody to ChillarAnand
Status: newassigned

comment:3 Changed 8 years ago by ChillarAnand

Owner: ChillarAnand deleted
Status: assignednew

Changed 8 years ago by Konrad Świat

Attachment: 24623_workaround_1.diff added

comment:4 Changed 8 years ago by Konrad Świat

Owner: set to Konrad Świat
Status: newassigned

https://docs.python.org/3/library/functions.html#open

Files opened in binary mode (including 'b' in the mode argument) return contents as bytes objects without any decoding.

EmailMessage.attach_file() opens an attachment file with 'rb' mode. which results in a bytes object content. In case of a text base mimetype, content is beeing sent to python's MimeText, and then encode() fails on bytes object.

I attached a simple workaround patch.

Last edited 8 years ago by Konrad Świat (previous) (diff)

comment:5 Changed 8 years ago by Claude Paroz

Would you mind creating a pull request with your patch, including the test from Tim?

comment:6 Changed 8 years ago by Konrad Świat

Of course, I will create PR within a few hours.

comment:7 Changed 8 years ago by Konrad Świat

Has patch: set

comment:8 Changed 8 years ago by Claude Paroz

Triage Stage: AcceptedReady for checkin

comment:9 Changed 8 years ago by Tim Graham

Patch needs improvement: set
Triage Stage: Ready for checkinAccepted

comment:10 Changed 8 years ago by Konrad Świat

PR updated.
edit:
There are some test cases included, that maybe need a discussion - what should we allow for (e.g invalid binary mimetype for a text file - should it be handled silently if possible)?

Last edited 8 years ago by Konrad Świat (previous) (diff)

comment:11 Changed 8 years ago by Konrad Świat

Patch needs improvement: unset

comment:12 Changed 8 years ago by Paul Hallett

@kswiat - you've got some tests in there already, are there any more edgecases that you can think of? It doesn't hurt to have more.

comment:13 Changed 8 years ago by Tim Graham <timograham@…>

Resolution: fixed
Status: assignedclosed

In c6da621:

Fixed #24623 -- Fixed EmailMessage.attach_file() with text files on Python 3.

Thanks tkrapp for the report and Tim Graham for the review.

Note: See TracTickets for help on using tickets.
Back to Top