Opened 10 years ago

Closed 9 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 10 years ago.
24623_workaround_1.diff (712 bytes ) - added by Konrad Świat 9 years ago.

Download all attachments as: .zip

Change History (15)

comment:1 by Tim Graham, 10 years ago

Triage Stage: UnreviewedAccepted

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

by Tim Graham, 10 years ago

Attachment: 24623-test.diff added

comment:2 by ChillarAnand, 10 years ago

Owner: changed from nobody to ChillarAnand
Status: newassigned

comment:3 by ChillarAnand, 10 years ago

Owner: ChillarAnand removed
Status: assignednew

by Konrad Świat, 9 years ago

Attachment: 24623_workaround_1.diff added

comment:4 by Konrad Świat, 9 years ago

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 9 years ago by Konrad Świat (previous) (diff)

comment:5 by Claude Paroz, 9 years ago

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

comment:6 by Konrad Świat, 9 years ago

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

comment:7 by Konrad Świat, 9 years ago

Has patch: set

comment:8 by Claude Paroz, 9 years ago

Triage Stage: AcceptedReady for checkin

comment:9 by Tim Graham, 9 years ago

Patch needs improvement: set
Triage Stage: Ready for checkinAccepted

comment:10 by Konrad Świat, 9 years ago

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 9 years ago by Konrad Świat (previous) (diff)

comment:11 by Konrad Świat, 9 years ago

Patch needs improvement: unset

comment:12 by Paul Hallett, 9 years ago

@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 by Tim Graham <timograham@…>, 9 years ago

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