diff --git a/.travis.yml b/.travis.yml index 50ea65a..f8cefa9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,11 @@ branches: python: - "2.7" - "pypy" - - "3.3" + - "pypy3" - "3.4" - "3.5" + - "3.6" + - "3.7" install: - travis_retry pip install --upgrade setuptools pip codecov 'setuptools_scm>=1.9' diff --git a/LICENSE.txt b/LICENSE.txt index b3c4dc3..a2a1027 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright © 2006-2016 Alice Bevan-McGregor and contributors. +Copyright © 2006-2019 Alice Bevan-McGregor and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.textile b/README.textile index 389dba3..757be1f 100644 --- a/README.textile +++ b/README.textile @@ -2,7 +2,7 @@ h1(#title). Marrow Mailer bq(subtitle). A highly efficient and modular mail delivery framework for Python 2.6+ and 3.2+, formerly called TurboMail. -bq(byline). (C) 2006-2016, Alice Bevan-McGregor and contributors. +bq(byline). (C) 2006-2019, Alice Bevan-McGregor and contributors. bq(byline). "https://github.com/marrow/mailer":github-project @@ -43,6 +43,8 @@ Installing @marrow.mailer@ is easy, just execute the following in a terminal: [2 If you add @marrow.mailer@ to the @install_requires@ argument of the call to @setup()@ in your application's @setup.py@ file, @marrow.mailer@ will be automatically installed and made available when your own application is installed. We recommend using "less than" version numbers to ensure there are no unintentional side-effects when updating. Use @"marrow.mailer<4.1"@ to get all bugfixes for the current release, and @"marrow.mailer<5.0"@ to get bugfixes and feature updates, but ensure that large breaking changes are not installed. +*Warning:* The 4.0 series is the last to support Python 2. + h3(#install-dev). %2.1.% Development Version @@ -454,7 +456,7 @@ Marrow Mailer has been released under the MIT Open Source license. h3(#license-mit). %8.1.% The MIT License -Copyright (C) 2006-2016 Alice Bevan-McGregor and contributors. +Copyright (C) 2006-2019 Alice Bevan-McGregor and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/marrow/mailer/message.py b/marrow/mailer/message.py index 2f83f9d..7e0cb77 100644 --- a/marrow/mailer/message.py +++ b/marrow/mailer/message.py @@ -191,6 +191,9 @@ def _build_header_list(self, author, sender): else: headers.extend(self.headers) + if 'message-id' not in (header[0].lower() for header in headers): + headers.append(('Message-Id', self.id)) + return headers def _add_headers_to_message(self, message, headers): @@ -322,11 +325,18 @@ def attach(self, name, data=None, maintype=None, subtype=None, filename=(filename_charset, filename_language, filename) if inline: - part.add_header('Content-Disposition', 'inline', filename=filename) - part.add_header('Content-ID', '<%s>' % filename) + if sys.version_info < (3, 0): + part.add_header('Content-Disposition'.encode('utf-8'), 'inline'.encode('utf-8'), filename=filename) + part.add_header('Content-ID'.encode('utf-8'), '<%s>'.encode('utf-8') % filename) + else: + part.add_header('Content-Disposition', 'inline', filename=filename) + part.add_header('Content-ID', '<%s>' % filename) self.embedded.append(part) else: - part.add_header('Content-Disposition', 'attachment', filename=filename) + if sys.version_info < (3, 0): + part.add_header('Content-Disposition'.encode('utf-8'), 'attachment'.encode('utf-8'), filename=filename) + else: + part.add_header('Content-Disposition', 'attachment', filename=filename) self.attachments.append(part) def embed(self, name, data=None): diff --git a/marrow/mailer/release.py b/marrow/mailer/release.py index 4c7ece0..99f5398 100644 --- a/marrow/mailer/release.py +++ b/marrow/mailer/release.py @@ -5,7 +5,7 @@ from collections import namedtuple -version_info = namedtuple('version_info', ('major', 'minor', 'micro', 'releaselevel', 'serial'))(4, 0, 2, 'final', 0) +version_info = namedtuple('version_info', ('major', 'minor', 'micro', 'releaselevel', 'serial'))(4, 0, 3, 'final', 0) version = ".".join([str(i) for i in version_info[:3]]) + ((version_info.releaselevel[0] + str(version_info.serial)) if version_info.releaselevel != 'final' else '') author = namedtuple('Author', ['name', 'email'])("Alice Bevan-McGregor", 'alice@gothcandy.com') diff --git a/test/test_message.py b/test/test_message.py index f014155..3ce4dc6 100644 --- a/test/test_message.py +++ b/test/test_message.py @@ -18,7 +18,7 @@ from marrow.mailer import Message from marrow.mailer.address import AddressList -from marrow.util.compat import unicode +from marrow.util.compat import basestring, unicode class TestBasicMessage(TestCase): @@ -281,6 +281,29 @@ def test_mime_embed_failures(self): with pytest.raises(TypeError): message.embed('test.gif', object()) + + def test_that_add_header_and_collapse_header_are_inverses_ascii_filename(self): + message = self.build_message() + message.plain = "Hello world." + message.rich = "Farewell cruel world." + message.attach("wat.txt", b"not a unicode snowman") # calls add_header() under the covers + attachment = message.attachments[0] + filename = attachment.get_filename() # calls email.utils.collapse_rfc2231_value() under the covers + assert filename == "wat.txt" + + def test_that_add_header_and_collapse_header_are_inverses_non_ascii_filename(self): + message = self.build_message() + message.plain = "Hello world." + message.rich = "Farewell cruel world." + message.attach("☃.txt", b"unicode snowman", filename_language='en-us') + attachment = message.attachments[0] + filename = attachment.get_param('filename', object(), 'content-disposition') # get_filename() calls this under the covers + assert isinstance(filename, tuple) # Since attachment encoded according to RFC2231, should be represented as a tuple + filename = attachment.get_filename() # Calls email.utils.collapse_rfc2231_value() under the covers, currently fails + if sys.version_info < (3, 0): + assert isinstance(filename, basestring) # Successfully converts tuple to a string + else: + assert isinstance(filename, str) def test_recipients_collection(self): message = self.build_message()