From 2a90953283c145605e8b5fb2d83e77516c152290 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Mon, 11 Mar 2013 21:00:53 -0700 Subject: [PATCH] Adding new settings allowing one to specify a list of mimetypes that are permitted to be stored in the message body of the e-mail message model. --- .gitignore | 1 + django_mailbox.egg-info/PKG-INFO | 20 ++++++++ django_mailbox.egg-info/SOURCES.txt | 38 ++++++++++++++ django_mailbox.egg-info/dependency_links.txt | 1 + django_mailbox.egg-info/requires.txt | 5 ++ django_mailbox.egg-info/top_level.txt | 1 + django_mailbox/models.py | 52 +++++++++++++++++--- django_mailbox/tests/__init__.py | 52 +++++++++++++++++++- docs/topics/settings.rst | 23 ++++++--- setup.py | 5 +- 10 files changed, 180 insertions(+), 18 deletions(-) create mode 100644 .gitignore create mode 100644 django_mailbox.egg-info/PKG-INFO create mode 100644 django_mailbox.egg-info/SOURCES.txt create mode 100644 django_mailbox.egg-info/dependency_links.txt create mode 100644 django_mailbox.egg-info/requires.txt create mode 100644 django_mailbox.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/django_mailbox.egg-info/PKG-INFO b/django_mailbox.egg-info/PKG-INFO new file mode 100644 index 0000000..298901f --- /dev/null +++ b/django_mailbox.egg-info/PKG-INFO @@ -0,0 +1,20 @@ +Metadata-Version: 1.0 +Name: django-mailbox +Version: 1.8.3 +Summary: Import mail from POP3, IMAP, local mailboxes or directly from Postfix or Exim4 into your Django application automatically. +Home-page: http://bitbucket.org/latestrevision/django-mailbox/ +Author: Adam Coddington +Author-email: me@adamcoddington.net +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Classifier: Framework :: Django +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Communications :: Email +Classifier: Topic :: Communications :: Email :: Post-Office +Classifier: Topic :: Communications :: Email :: Post-Office :: IMAP +Classifier: Topic :: Communications :: Email :: Post-Office :: POP3 +Classifier: Topic :: Communications :: Email :: Email Clients (MUA) diff --git a/django_mailbox.egg-info/SOURCES.txt b/django_mailbox.egg-info/SOURCES.txt new file mode 100644 index 0000000..9953a21 --- /dev/null +++ b/django_mailbox.egg-info/SOURCES.txt @@ -0,0 +1,38 @@ +setup.py +django_mailbox/__init__.py +django_mailbox/admin.py +django_mailbox/models.py +django_mailbox/runtests.py +django_mailbox/signals.py +django_mailbox.egg-info/PKG-INFO +django_mailbox.egg-info/SOURCES.txt +django_mailbox.egg-info/dependency_links.txt +django_mailbox.egg-info/requires.txt +django_mailbox.egg-info/top_level.txt +django_mailbox/management/__init__.py +django_mailbox/management/commands/__init__.py +django_mailbox/management/commands/getmail.py +django_mailbox/management/commands/processincomingmessage.py +django_mailbox/migrations/0001_initial.py +django_mailbox/migrations/0002_auto__chg_field_mailbox_uri.py +django_mailbox/migrations/0003_auto__add_field_mailbox_active.py +django_mailbox/migrations/0004_auto__add_field_message_outgoing.py +django_mailbox/migrations/0005_rename_fields.py +django_mailbox/migrations/0006_auto__add_field_message_in_reply_to.py +django_mailbox/migrations/0007_auto__del_field_message_address__add_field_message_from_header__add_fi.py +django_mailbox/migrations/0008_populate_from_to_fields.py +django_mailbox/migrations/0009_remove_references_table.py +django_mailbox/migrations/0010_auto__add_field_mailbox_from_email.py +django_mailbox/migrations/0011_auto__add_field_message_read.py +django_mailbox/migrations/0012_auto__add_messageattachment.py +django_mailbox/migrations/__init__.py +django_mailbox/tests/__init__.py +django_mailbox/transports/__init__.py +django_mailbox/transports/babyl.py +django_mailbox/transports/generic.py +django_mailbox/transports/imap.py +django_mailbox/transports/maildir.py +django_mailbox/transports/mbox.py +django_mailbox/transports/mh.py +django_mailbox/transports/mmdf.py +django_mailbox/transports/pop3.py \ No newline at end of file diff --git a/django_mailbox.egg-info/dependency_links.txt b/django_mailbox.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/django_mailbox.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/django_mailbox.egg-info/requires.txt b/django_mailbox.egg-info/requires.txt new file mode 100644 index 0000000..1cc17f8 --- /dev/null +++ b/django_mailbox.egg-info/requires.txt @@ -0,0 +1,5 @@ + + +[test] +django +mimic \ No newline at end of file diff --git a/django_mailbox.egg-info/top_level.txt b/django_mailbox.egg-info/top_level.txt new file mode 100644 index 0000000..2602f12 --- /dev/null +++ b/django_mailbox.egg-info/top_level.txt @@ -0,0 +1 @@ +django_mailbox diff --git a/django_mailbox/models.py b/django_mailbox/models.py index b6b3bd0..8c83b04 100755 --- a/django_mailbox/models.py +++ b/django_mailbox/models.py @@ -1,4 +1,5 @@ import email +from email.message import Message as EmailMessage from email.utils import formatdate import rfc822 import urllib @@ -20,6 +21,24 @@ SKIPPED_EXTENSIONS = getattr( 'DJANGO_MAILBOX_SKIPPED_EXTENSIONS', ['.p7s'] ) +STRIP_UNALLOWED_MIMETYPES = getattr( + settings, + 'DJANGO_MAILBOX_STRIP_UNALLOWED_MIMETYPES', + False +) +ALLOWED_MIMETYPES = getattr( + settings, + 'DJANGO_MAILBOX_ALLOWED_MIMETYPES', + [ + 'text/plain', + 'text/html' + ] +) +ALTERED_MESSAGE_HEADER = getattr( + settings, + 'DJANGO_MAILBOX_ALTERED_MESSAGE_HEADER', + 'X-Django-Mailbox-Altered-Message' +) class ActiveMailboxManager(models.Manager): def get_query_set(self): @@ -155,6 +174,29 @@ class Mailbox(models.Model): msg.save() return msg + def _filter_message_body(self, message): + if not message.is_multipart() or not STRIP_UNALLOWED_MIMETYPES: + return message + stripped_content = {} + new = EmailMessage() + for header, value in message.items(): + new[header] = value + for part in message.walk(): + content_type = part.get_content_type() + print content_type + if not content_type in ALLOWED_MIMETYPES: + if content_type not in stripped_content: + stripped_content[content_type] = 0 + stripped_content[content_type] = ( + stripped_content[content_type] + 1 + ) + continue + new.attach(part) + new[ALTERED_MESSAGE_HEADER] = 'Stripped ' + ', '.join( + ['%s*%s' % (key, value) for key, value in stripped_content.items()] + ) + return new + def _process_message(self, message): msg = Message() msg.mailbox = self @@ -162,14 +204,8 @@ class Mailbox(models.Model): msg.message_id = message['message-id'][0:255] msg.from_header = message['from'] msg.to_header = message['to'] - # if message is multipart want to only store the actual message body in database (not attachments) - if message.is_multipart(): - for part in message.walk(): - if part.get_content_type() == 'text/plain' or part.get_content_type() == 'text/html': - msg.body = part.get_payload() - # else store the whole message in database - else: - msg.body = message.as_string() + message = self._filter_message_body(message) + msg.body = message.as_string() if message['in-reply-to']: try: msg.in_reply_to = Message.objects.filter(message_id=message['in-reply-to'])[0] diff --git a/django_mailbox/tests/__init__.py b/django_mailbox/tests/__init__.py index 7972100..44d64ef 100644 --- a/django_mailbox/tests/__init__.py +++ b/django_mailbox/tests/__init__.py @@ -5,9 +5,10 @@ import shutil from django.db import models from django.test import TestCase +import django_mailbox from django_mailbox.models import Mailbox, Message -class TestProcessMessage(TestCase): +class EmailMessageTestCase(TestCase): def _get_email_object(self, name): with open(os.path.join(os.path.dirname(__file__), name), 'r') as f: return email.message_from_string( @@ -17,7 +18,8 @@ class TestProcessMessage(TestCase): def tearDown(self): for message in Message.objects.all(): message.delete() - + +class TestProcessEmail(EmailMessageTestCase): def test_message_without_attachments(self): message = self._get_email_object('generic_message.eml') @@ -75,3 +77,49 @@ class TestProcessMessage(TestCase): expected_results, actual_results, ) + +class TestFilterMessageBody(EmailMessageTestCase): + def setUp(self): + django_mailbox.models.STRIP_UNALLOWED_MIMETYPES = True + super(TestFilterMessageBody, self).setUp() + + def tearDown(self): + django_mailbox.models.STRIP_UNALLOWED_MIMETYPES = False + super(TestFilterMessageBody, self).tearDown() + + def test_filter_message_does_not_filter_message_if_disabled(self): + django_mailbox.models.STRIP_UNALLOWED_MIMETYPES = False + message = self._get_email_object('message_with_attachment.eml') + mailbox = Mailbox.objects.create() + + self.assertEquals( + message.as_string(), + mailbox._filter_message_body(message).as_string() + ) + + def test_filter_message_removes_unknown_content_if_disabled(self): + # The below is the _same_ as message_with_attachment.eml, but missing + # its attached png image, and adding the expected message altered header. + message_without_non_plaintext = ( + "MIME-Version: 1.0\n" + "Received: by 10.221.0.211 with HTTP; Sun, 20 Jan 2013 12:07:07 -0800 (PST)\n" + "X-Originating-IP: [24.22.122.177]\n" + "Date: Sun, 20 Jan 2013 12:07:07 -0800\n" + "Delivered-To: test@adamcoddington.net\n" + "Message-ID: \n" + "Subject: Message With Attachment\n" + "From: Adam Coddington \n" + "To: Adam Coddington \n" + "Content-Type: multipart/mixed; boundary=047d7b33dd729737fe04d3bde348\n" + "X-Django-Mailbox-Altered-Message: Stripped image/png*1, multipart/mixed*1\n" + "\n--047d7b33dd729737fe04d3bde348\n" + "Content-Type: text/plain; charset=UTF-8\n\n" + "This message has an attachment.\n\n--047d7b33dd729737fe04d3bde348--" + ) + message = self._get_email_object('message_with_attachment.eml') + mailbox = Mailbox.objects.create() + + self.assertEquals( + message_without_non_plaintext, + mailbox._filter_message_body(message).as_string() + ) diff --git a/docs/topics/settings.rst b/docs/topics/settings.rst index 9d01f55..18585b4 100644 --- a/docs/topics/settings.rst +++ b/docs/topics/settings.rst @@ -2,11 +2,20 @@ Settings ======== -+---------------------------------------+--------------+-------------------------------------------------------------------------+ -| Setting | Default | Notes | -+=======================================+==============+=========================================================================+ -| ``DJANGO_MAILBOX_ADMIN_ENABLED`` | ``True`` | Controls whether mailboxes appear in the Django Admin. | -+---------------------------------------+--------------+-------------------------------------------------------------------------+ -| ``DJANGO_MAILBOX_SKIPPED_EXTENSIONS`` | ``['.p7s']`` | A list of extensions to skip when processing email message attachments. | -+---------------------------------------+--------------+-------------------------------------------------------------------------+ +* ``DJANGO_MAILBOX_ADMIN_ENABLED`` + * Default: ``True`` + * Type: ``boolean`` + * Controls whether mailboxes appear in the Django Admin. +* ``DJANGO_MAILBOX_SKIPPED_EXTENSIONS`` + * Default: ``['.p7s']`` + * Type: ``list`` + * A list of extensions to skip when processing e-mail message attachments. +* ``DJANGO_MAILBOX_STRIP_UNALLOWED_MIMETYPES`` + * Default: ``False`` + * Type: ``boolean`` + * Controls whether or not we remove mimetypes not specified in ``DJANGO_MAILBOX_PRESERVED_MIMETYPES``. +* ``DJANGO_MAILBOX_ALLOWED_MIMETYPES`` + * Default ``['text/html', 'text/plain']`` + * Type: ``list`` + * A list of mimetypes that will remain and be stored in the message payload of the message object. Has no effect unless ``DJANGO_MAILBOX_STRIP_UNALLOWED_MIMETYPES`` is set to ``True``. diff --git a/setup.py b/setup.py index a786adc..f261fa0 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,9 @@ from setuptools import setup -tests_require=['django'] +tests_require=[ + 'django', + 'mimic', +] setup( name='django-mailbox',