diff options
author | Folker Bernitt <fbernitt@thoughtworks.com> | 2016-01-06 16:38:20 +0100 |
---|---|---|
committer | Folker Bernitt <fbernitt@thoughtworks.com> | 2016-01-06 16:38:20 +0100 |
commit | 9db569e3f5170efd852322d428b9b0282c7bb114 (patch) | |
tree | d96e1a1601875ee052313e9ed626add3e94ca3bd | |
parent | 9452ce26ea20ed73808c172cdef3890c45473e41 (diff) |
Support attachments without name in Content-Disposition header
- Issue 557
- along the way also added support for signatures and
attached keys
-rw-r--r-- | service/pixelated/adapter/mailstore/leap_mailstore.py | 59 | ||||
-rw-r--r-- | service/test/unit/adapter/mailstore/test_leap_mailstore.py | 21 |
2 files changed, 66 insertions, 14 deletions
diff --git a/service/pixelated/adapter/mailstore/leap_mailstore.py b/service/pixelated/adapter/mailstore/leap_mailstore.py index 33bb6c74..10aad0b3 100644 --- a/service/pixelated/adapter/mailstore/leap_mailstore.py +++ b/service/pixelated/adapter/mailstore/leap_mailstore.py @@ -13,19 +13,18 @@ # # You should have received a copy of the GNU Affero General Public License # along with Pixelated. If not, see <http://www.gnu.org/licenses/>. -import base64 +import re from email.header import decode_header from email.utils import parseaddr from uuid import uuid4 -import re -from leap.mail.adaptors.soledad import SoledadMailAdaptor, ContentDocWrapper +from leap.mail.adaptors.soledad import SoledadMailAdaptor +from leap.mail.mail import Message from twisted.internet import defer + from pixelated.adapter.mailstore.body_parser import BodyParser from pixelated.adapter.mailstore.mailstore import MailStore, underscore_uuid -from leap.mail.mail import Message from pixelated.adapter.model.mail import Mail, InputMail - from pixelated.support.functional import to_unicode @@ -35,6 +34,12 @@ class AttachmentInfo(object): self.name = name self.encoding = encoding + def __repr__(self): + return 'AttachmentInfo[%s, %s, %s]' % (self.ident, self.name, self.encoding) + + def __str__(self): + return 'AttachmentInfo[%s, %s, %s]' % (self.ident, self.name, self.encoding) + class LeapMail(Mail): @@ -189,8 +194,23 @@ class LeapMail(Mail): return LeapMail(None, None, headers, tags, set(), body, attachments) -def _extract_filename(content_disposition): - match = re.compile('.*name=\"?(.*[^\"\'])').search(content_disposition) +def _extract_filename(headers, default_filename='UNNAMED'): + content_disposition = headers.get('Content-Disposition', '') + filename = _extract_filename_from_name_header_part(content_disposition) + if not filename: + filename = headers.get('Content-Description', '') + if not filename: + content_type = headers.get('Content-Type', '') + filename = _extract_filename_from_name_header_part(content_type) + + if not filename: + filename = default_filename + + return filename + + +def _extract_filename_from_name_header_part(header_value): + match = re.compile('.*name=\"?(.*[^\"\'])').search(header_value) filename = '' if match: filename = match.group(1) @@ -322,7 +342,8 @@ class LeapMailStore(MailStore): # fetch mailbox name by mbox_uuid mbox_uuid = message.get_wrapper().fdoc.mbox_uuid mbox_name = yield self._mailbox_name_from_uuid(mbox_uuid) - mail = LeapMail(mail_id, mbox_name, message.get_wrapper().hdoc.headers, set(message.get_tags()), set(message.get_flags()), body=body, attachments=self._extract_attachment_info_from(message)) # TODO assert flags are passed on + attachments = self._extract_attachment_info_from(message) + mail = LeapMail(mail_id, mbox_name, message.get_wrapper().hdoc.headers, set(message.get_tags()), set(message.get_flags()), body=body, attachments=attachments) # TODO assert flags are passed on defer.returnValue(mail) @@ -364,6 +385,18 @@ class LeapMailStore(MailStore): part_maps = wrapper.hdoc.part_map return self._extract_part_map(part_maps) + def _is_attachment(self, part_map, headers): + disposition = headers.get('Content-Disposition', None) + content_type = part_map['ctype'] + + if 'multipart' in content_type: + return False + + if 'text/plain' == content_type and ((disposition == 'inline') or (disposition is None)): + return False + + return True + def _extract_part_map(self, part_maps): result = [] @@ -371,12 +404,10 @@ class LeapMailStore(MailStore): if 'headers' in part_map and 'phash' in part_map: headers = {header[0]: header[1] for header in part_map['headers']} phash = part_map['phash'] - if 'Content-Disposition' in headers: - disposition = headers['Content-Disposition'] - if 'attachment' in disposition or 'inline' in disposition: - filename = _extract_filename(disposition) - encoding = headers.get('Content-Transfer-Encoding', None) - result.append(AttachmentInfo(phash, filename, encoding)) + if self._is_attachment(part_map, headers): + filename = _extract_filename(headers) + encoding = headers.get('Content-Transfer-Encoding', None) + result.append(AttachmentInfo(phash, filename, encoding)) if 'part_map' in part_map: result += self._extract_part_map(part_map['part_map']) diff --git a/service/test/unit/adapter/mailstore/test_leap_mailstore.py b/service/test/unit/adapter/mailstore/test_leap_mailstore.py index 6cc58513..f4fa367f 100644 --- a/service/test/unit/adapter/mailstore/test_leap_mailstore.py +++ b/service/test/unit/adapter/mailstore/test_leap_mailstore.py @@ -292,6 +292,27 @@ class TestLeapMailStore(TestCase): self.assertEqual('batatinha.rtf', attachment_info[0].name) self.assertEqual('receipt.pdf', attachment_info[1].name) + def test_extract_attachment_filename_from_other_headers(self): + input_mail = MIMEMultipart() + input_mail.attach(MIMEText(u'a utf8 message', _charset='utf-8')) + + attachment_without_description = MIMEApplication('pretend to be an attachment from apple mail', _subtype='pgp-keys') + attachment_without_description.add_header('Content-Disposition', 'attachment') + attachment_without_description.add_header('Content-Description', 'Some GPG Key') + + input_mail.attach(attachment_without_description) + + attachment_with_name_in_content_type = MIMEApplication('pretend to be an attachment from thunderbird', _subtype='pgp-signature; name="signature.asc"') + attachment_with_name_in_content_type.add_header('Content-Disposition', 'inline') + input_mail.attach(attachment_with_name_in_content_type) + + message = self._add_create_mail_mocks_to_soledad(input_mail) + store = LeapMailStore(self.soledad) + attachment_info = store._extract_attachment_info_from(message) + + self.assertEqual('Some GPG Key', attachment_info[0].name) + self.assertEqual('signature.asc', attachment_info[1].name) + @defer.inlineCallbacks def test_add_mail_with_special_chars(self): input_mail = MIMEText(u'a utf8 message', _charset='utf-8') |