diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/core/initialize.py | 3 | ||||
| -rw-r--r-- | src/mailman/database/mailinglist.py | 196 | ||||
| -rw-r--r-- | src/mailman/database/mailman.sql | 21 | ||||
| -rw-r--r-- | src/mailman/database/mime.py | 52 | ||||
| -rw-r--r-- | src/mailman/interfaces/mailinglist.py | 83 | ||||
| -rw-r--r-- | src/mailman/interfaces/mime.py | 68 | ||||
| -rw-r--r-- | src/mailman/pipeline/docs/filtering.txt | 75 | ||||
| -rw-r--r-- | src/mailman/pipeline/mime_delete.py | 18 | ||||
| -rw-r--r-- | src/mailman/rules/docs/implicit-dest.txt | 20 | ||||
| -rw-r--r-- | src/mailman/rules/implicit_dest.py | 5 | ||||
| -rw-r--r-- | src/mailman/styles/default.py | 13 |
11 files changed, 440 insertions, 114 deletions
diff --git a/src/mailman/core/initialize.py b/src/mailman/core/initialize.py index 7edb963b7..bb3f8a66e 100644 --- a/src/mailman/core/initialize.py +++ b/src/mailman/core/initialize.py @@ -118,6 +118,9 @@ def initialize_3(): adapter_hooks.append(adapt_domain_to_registrar) from mailman.database.autorespond import adapt_mailing_list_to_response_set adapter_hooks.append(adapt_mailing_list_to_response_set) + from mailman.database.mailinglist import ( + adapt_mailing_list_to_acceptable_alias_set) + adapter_hooks.append(adapt_mailing_list_to_acceptable_alias_set) diff --git a/src/mailman/database/mailinglist.py b/src/mailman/database/mailinglist.py index fa4af5ad0..22de044e3 100644 --- a/src/mailman/database/mailinglist.py +++ b/src/mailman/database/mailinglist.py @@ -22,6 +22,7 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ 'MailingList', + 'adapt_mailing_list_to_acceptable_alias_set', ] @@ -37,10 +38,12 @@ from zope.interface import implements from mailman.config import config from mailman.database import roster from mailman.database.digests import OneLastDigest +from mailman.database.mime import ContentFilter from mailman.database.model import Model from mailman.database.types import Enum from mailman.interfaces.mailinglist import ( - IAcceptableAlias, IMailingList, Personalization) + IAcceptableAlias, IAcceptableAliasSet, IMailingList, Personalization) +from mailman.interfaces.mime import FilterType from mailman.utilities.filesystem import makedirs from mailman.utilities.string import expand @@ -92,6 +95,10 @@ class MailingList(Model): autoresponse_postings_text = Unicode() autorespond_requests = Enum() autoresponse_request_text = Unicode() + # Content filters. + filter_content = Bool() + collapse_alternatives = Bool() + convert_html_to_plaintext = Bool() # Bounces and bans. ban_list = Pickle() bounce_info_stale_after = TimeDelta() @@ -103,8 +110,6 @@ class MailingList(Model): bounce_unrecognized_goes_to_list_owner = Bool() bounce_you_are_disabled_warnings = Int() bounce_you_are_disabled_warnings_interval = TimeDelta() - collapse_alternatives = Bool() - convert_html_to_plaintext = Bool() default_member_moderation = Bool() description = Unicode() digest_footer = Unicode() @@ -117,10 +122,6 @@ class MailingList(Model): discard_these_nonmembers = Pickle() emergency = Bool() encode_ascii_prefixes = Bool() - filter_action = Int() - filter_content = Bool() - filter_filename_extensions = Pickle() - filter_mime_types = Pickle() first_strip_reply_to = Bool() forward_auto_discards = Bool() gateway_to_mail = Bool() @@ -149,8 +150,6 @@ class MailingList(Model): nondigestable = Bool() nonmember_rejection_notice = Unicode() obscure_addresses = Bool() - pass_filename_extensions = Pickle() - pass_mime_types = Pickle() personalize = Enum() pipeline = Unicode() post_id = Int() @@ -230,41 +229,51 @@ class MailingList(Model): @property def posting_address(self): + """See `IMailingList`.""" return self.fqdn_listname @property def no_reply_address(self): + """See `IMailingList`.""" return '{0}@{1}'.format(config.mailman.noreply_address, self.host_name) @property def owner_address(self): + """See `IMailingList`.""" return '{0}-owner@{1}'.format(self.list_name, self.host_name) @property def request_address(self): + """See `IMailingList`.""" return '{0}-request@{1}'.format(self.list_name, self.host_name) @property def bounces_address(self): + """See `IMailingList`.""" return '{0}-bounces@{1}'.format(self.list_name, self.host_name) @property def join_address(self): + """See `IMailingList`.""" return '{0}-join@{1}'.format(self.list_name, self.host_name) @property def leave_address(self): + """See `IMailingList`.""" return '{0}-leave@{1}'.format(self.list_name, self.host_name) @property def subscribe_address(self): + """See `IMailingList`.""" return '{0}-subscribe@{1}'.format(self.list_name, self.host_name) @property def unsubscribe_address(self): + """See `IMailingList`.""" return '{0}-unsubscribe@{1}'.format(self.list_name, self.host_name) def confirm_address(self, cookie): + """See `IMailingList`.""" local_part = expand(config.mta.verp_confirm_format, dict( address = '{0}-confirm'.format(self.list_name), cookie = cookie)) @@ -272,10 +281,12 @@ class MailingList(Model): @property def preferred_language(self): + """See `IMailingList`.""" return config.languages[self._preferred_language] @preferred_language.setter def preferred_language(self, language): + """See `IMailingList`.""" # Accept both a language code and a `Language` instance. try: self._preferred_language = language.code @@ -298,31 +309,109 @@ class MailingList(Model): results.remove() return recipients - def clear_acceptable_aliases(self): + @property + def filter_types(self): """See `IMailingList`.""" - Store.of(self).find( - AcceptableAlias, - AcceptableAlias.mailing_list == self).remove() + results = Store.of(self).find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.filter_mime)) + for content_filter in results: + yield content_filter.filter_pattern - def add_acceptable_alias(self, alias): - if not (alias.startswith('^') or '@' in alias): - raise ValueError(alias) - alias = AcceptableAlias(self, alias.lower()) - Store.of(self).add(alias) + @filter_types.setter + def filter_types(self, sequence): + """See `IMailingList`.""" + # First, delete all existing MIME type filter patterns. + store = Store.of(self) + results = store.find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.filter_mime)) + results.remove() + # Now add all the new filter types. + for mime_type in sequence: + content_filter = ContentFilter( + self, mime_type, FilterType.filter_mime) + store.add(content_filter) - def remove_acceptable_alias(self, alias): - Store.of(self).find( - AcceptableAlias, - And(AcceptableAlias.mailing_list == self, - AcceptableAlias.alias == alias.lower())).remove() + @property + def pass_types(self): + """See `IMailingList`.""" + results = Store.of(self).find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.pass_mime)) + for content_filter in results: + yield content_filter.filter_pattern + + @pass_types.setter + def pass_types(self, sequence): + """See `IMailingList`.""" + # First, delete all existing MIME type pass patterns. + store = Store.of(self) + results = store.find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.pass_mime)) + results.remove() + # Now add all the new filter types. + for mime_type in sequence: + content_filter = ContentFilter( + self, mime_type, FilterType.pass_mime) + store.add(content_filter) @property - def acceptable_aliases(self): - aliases = Store.of(self).find( - AcceptableAlias, - AcceptableAlias.mailing_list == self) - for alias in aliases: - yield alias.alias + def filter_extensions(self): + """See `IMailingList`.""" + results = Store.of(self).find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.filter_extension)) + for content_filter in results: + yield content_filter.filter_pattern + + @filter_extensions.setter + def filter_extensions(self, sequence): + """See `IMailingList`.""" + # First, delete all existing file extensions filter patterns. + store = Store.of(self) + results = store.find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.filter_extension)) + results.remove() + # Now add all the new filter types. + for mime_type in sequence: + content_filter = ContentFilter( + self, mime_type, FilterType.filter_extension) + store.add(content_filter) + + @property + def pass_extensions(self): + """See `IMailingList`.""" + results = Store.of(self).find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.pass_extension)) + for content_filter in results: + yield content_filter.pass_pattern + + @pass_extensions.setter + def pass_extensions(self, sequence): + """See `IMailingList`.""" + # First, delete all existing file extensions pass patterns. + store = Store.of(self) + results = store.find( + ContentFilter, + And(ContentFilter.mailing_list == self, + ContentFilter.filter_type == FilterType.pass_extension)) + results.remove() + # Now add all the new filter types. + for mime_type in sequence: + content_filter = ContentFilter( + self, mime_type, FilterType.pass_extension) + store.add(content_filter) @@ -339,3 +428,52 @@ class AcceptableAlias(Model): def __init__(self, mailing_list, alias): self.mailing_list = mailing_list self.alias = alias + + +class AcceptableAliasSet: + implements(IAcceptableAliasSet) + + def __init__(self, mailing_list): + self._mailing_list = mailing_list + + def clear(self): + """See `IAcceptableAliasSet`.""" + Store.of(self._mailing_list).find( + AcceptableAlias, + AcceptableAlias.mailing_list == self._mailing_list).remove() + + def add(self, alias): + if not (alias.startswith('^') or '@' in alias): + raise ValueError(alias) + alias = AcceptableAlias(self._mailing_list, alias.lower()) + Store.of(self._mailing_list).add(alias) + + def remove(self, alias): + Store.of(self._mailing_list).find( + AcceptableAlias, + And(AcceptableAlias.mailing_list == self._mailing_list, + AcceptableAlias.alias == alias.lower())).remove() + + @property + def aliases(self): + aliases = Store.of(self._mailing_list).find( + AcceptableAlias, + AcceptableAlias.mailing_list == self._mailing_list) + for alias in aliases: + yield alias.alias + + + +def adapt_mailing_list_to_acceptable_alias_set(iface, obj): + """Adapt an `IMailingList` to an `IAcceptableAliasSet`. + + :param iface: The interface to adapt to. + :type iface: `zope.interface.Interface` + :param obj: The object being adapted. + :type obj: `IMailingList` + :return: An `IAcceptableAliasSet` instance if adaptation succeeded or None + if it didn't. + """ + return (AcceptableAliasSet(obj) + if IMailingList.providedBy(obj) and iface is IAcceptableAliasSet + else None) diff --git a/src/mailman/database/mailman.sql b/src/mailman/database/mailman.sql index d64be9815..6f73c3974 100644 --- a/src/mailman/database/mailman.sql +++ b/src/mailman/database/mailman.sql @@ -54,6 +54,19 @@ CREATE INDEX ix_autoresponserecord_address_id CREATE INDEX ix_autoresponserecord_mailing_list_id ON autoresponserecord (mailing_list_id); +CREATE TABLE contentfilter ( + id INTEGER NOT NULL, + mailing_list_id INTEGER, + filter_pattern TEXT, + filter_type INTEGER, + PRIMARY KEY (id), + CONSTRAINT contentfilter_mailing_list_id + FOREIGN KEY (mailing_list_id) + REFERENCES mailinglist (id) + ); +CREATE INDEX ix_contentfilter_mailing_list_id + ON contentfilter (mailing_list_id); + CREATE TABLE language ( id INTEGER NOT NULL, code TEXT, @@ -99,6 +112,8 @@ CREATE TABLE mailinglist ( bounce_unrecognized_goes_to_list_owner BOOLEAN, bounce_you_are_disabled_warnings INTEGER, bounce_you_are_disabled_warnings_interval TEXT, + -- Content filtering. + filter_content BOOLEAN, collapse_alternatives BOOLEAN, convert_html_to_plaintext BOOLEAN, default_member_moderation BOOLEAN, @@ -113,10 +128,6 @@ CREATE TABLE mailinglist ( discard_these_nonmembers BLOB, emergency BOOLEAN, encode_ascii_prefixes BOOLEAN, - filter_action INTEGER, - filter_content BOOLEAN, - filter_filename_extensions BLOB, - filter_mime_types BLOB, first_strip_reply_to BOOLEAN, forward_auto_discards BOOLEAN, gateway_to_mail BOOLEAN, @@ -145,8 +156,6 @@ CREATE TABLE mailinglist ( nondigestable BOOLEAN, nonmember_rejection_notice TEXT, obscure_addresses BOOLEAN, - pass_filename_extensions BLOB, - pass_mime_types BLOB, personalize TEXT, pipeline TEXT, post_id INTEGER, diff --git a/src/mailman/database/mime.py b/src/mailman/database/mime.py new file mode 100644 index 000000000..31c2aacbe --- /dev/null +++ b/src/mailman/database/mime.py @@ -0,0 +1,52 @@ +# Copyright (C) 2009 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Module stuff.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'ContentFilter' + ] + + +from storm.locals import Bool, Int, Reference, Unicode +from zope.interface import implements + +from mailman.database.model import Model +from mailman.database.types import Enum +from mailman.interfaces.mime import IContentFilter + + + +class ContentFilter(Model): + """A single filter criteria.""" + implements(IContentFilter) + + id = Int(primary=True) + + mailing_list_id = Int() + mailing_list = Reference(mailing_list_id, 'MailingList.id') + + filter_type = Enum() + filter_pattern = Unicode() + + def __init__(self, mailing_list, filter_pattern, filter_type): + self.mailing_list = mailing_list + self.filter_pattern = filter_pattern + self.filter_type = filter_type diff --git a/src/mailman/interfaces/mailinglist.py b/src/mailman/interfaces/mailinglist.py index 2cfb9f737..1c1cbc869 100644 --- a/src/mailman/interfaces/mailinglist.py +++ b/src/mailman/interfaces/mailinglist.py @@ -22,6 +22,8 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ 'DigestFrequency', + 'IAcceptableAlias', + 'IAcceptableAliasSet', 'IMailingList', 'Personalization', 'ReplyToMunging', @@ -281,10 +283,74 @@ class IMailingList(Interface): that gets created to accumlate messages for the digest. """) - def clear_acceptable_aliases(): + filter_content = Attribute( + """Flag specifying whether to filter a message's content. + + Filtering is performed on MIME type and file name extension. + """) + + convert_html_to_plaintext = Attribute( + """Flag specifying whether text/html parts should be converted. + + When True, after filtering, if there are any text/html parts left in + the original message, they are converted to text/plain. + """) + + collapse_alternatives = Attribute( + """Flag specifying whether multipart/alternatives should be collapsed. + + After all MIME content filtering is complete, collapsing alternatives + replaces the outer multipart/alternative parts with the first + subpart. + """) + + filter_types = Attribute( + """Sequence of MIME types that should be filtered out. + + These can be either main types or main/sub types. Set this attribute + to a sequence to change it, or to None to empty it. + """) + + pass_types = Attribute( + """Sequence of MIME types to explicitly pass. + + These can be either main types or main/sub types. Set this attribute + to a sequence to change it, or to None to empty it. Pass types are + consulted after filter types, and only if `pass_types` is non-empty. + """) + + filter_extensions = Attribute( + """Sequence of file extensions that should be filtered out. + + Set this attribute to a sequence to change it, or to None to empty it. + """) + + pass_extensions = Attribute( + """Sequence of file extensions to explicitly pass. + + Set this attribute to a sequence to change it, or to None to empty it. + Pass extensions are consulted after filter extensions, and only if + `pass_extensions` is non-empty. + """) + + + + +class IAcceptableAlias(Interface): + """An acceptable alias for implicit destinations.""" + + mailing_list = Attribute('The associated mailing list.') + + address = Attribute('The address or pattern to match against recipients.') + + +class IAcceptableAliasSet(Interface): + """The set of acceptable aliases for a mailing list.""" + + def clear(): """Clear the set of acceptable posting aliases.""" - def add_acceptable_alias(alias): + def add(alias): """Add the given address as an acceptable aliases for posting. :param alias: The email address to accept as a recipient for implicit @@ -296,7 +362,7 @@ class IMailingList(Interface): '@' sign in it. """ - def remove_acceptable_alias(alias): + def remove(alias): """Remove the given address as an acceptable aliases for posting. :param alias: The email address to no longer accept as a recipient for @@ -304,14 +370,5 @@ class IMailingList(Interface): :type alias: string """ - acceptable_aliases = Attribute( + aliases = Attribute( """An iterator over all the acceptable aliases.""") - - - -class IAcceptableAlias(Interface): - """An acceptable alias for implicit destinations.""" - - mailing_list = Attribute('The associated mailing list.') - - address = Attribute('The address or pattern to match against recipients.') diff --git a/src/mailman/interfaces/mime.py b/src/mailman/interfaces/mime.py new file mode 100644 index 000000000..a1d74dc69 --- /dev/null +++ b/src/mailman/interfaces/mime.py @@ -0,0 +1,68 @@ +# Copyright (C) 2009 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""MIME content filtering.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'FilterAction', + 'FilterType', + 'IContentFilter', + ] + + +from munepy import Enum +from zope.interface import Interface, Attribute + + + +class FilterAction(Enum): + # Discard a message that matches the content type filter. + discard = 0 + # Bounce the message back to the original author. + bounce = 1 + # Discard and forward the message on to the list owner. + forward = 2 + # Discard, but preserve it. + preserve = 3 + + +class FilterType(Enum): + # Filter MIME type. + filter_mime = 0 + # Pass MIME type. + pass_mime = 1 + # Filter file extension. + filter_extension = 2 + # Pass file extension. + pass_extension = 3 + + + +class IContentFilter(Interface): + """A single content filter settings for a mailing list.""" + + mailing_list = Attribute( + """The mailing list for this content filter.""") + + filter_pattern = Attribute( + """The filter/pass content pattern.""") + + filter_type = Attribute( + """Type of filter.""") diff --git a/src/mailman/pipeline/docs/filtering.txt b/src/mailman/pipeline/docs/filtering.txt index 70ca3098d..f895220f0 100644 --- a/src/mailman/pipeline/docs/filtering.txt +++ b/src/mailman/pipeline/docs/filtering.txt @@ -3,12 +3,9 @@ Content filtering Mailman can filter the content of messages posted to a mailing list by stripping MIME subparts, and possibly reorganizing the MIME structure of a -message. It does this with the MimeDel handler module, although other -handlers can potentially do other kinds of finer level content filtering. +message. - >>> from mailman.pipeline.mime_delete import process - >>> mlist = config.db.list_manager.create(u'_xtest@example.com') - >>> mlist.preferred_language = u'en' + >>> mlist = create_list(u'test@example.com') Several mailing list options control content filtering. First, the feature must be enabled, then there are two options that control which MIME types get @@ -17,8 +14,8 @@ text/html parts will get converted to plain text. Let's set up some defaults for these variables, then we'll explain them in more detail below. >>> mlist.filter_content = True - >>> mlist.filter_mime_types = [] - >>> mlist.pass_mime_types = [] + >>> mlist.filter_types = [] + >>> mlist.pass_types = [] >>> mlist.convert_html_to_plaintext = False @@ -29,9 +26,11 @@ A simple filtering setting will just search the content types of the messages parts, discarding all parts with a matching MIME type. If the message's outer content type matches the filter, the entire message will be discarded. - >>> mlist.filter_mime_types = ['image/jpeg'] - >>> # XXX Change this to an enum - >>> mlist.filter_action = 0 # Discard + >>> from mailman.interfaces.mime import FilterAction + + >>> mlist.filter_types = [u'image/jpeg'] + >>> mlist.filter_action = FilterAction.discard + >>> msg = message_from_string("""\ ... From: aperson@example.com ... Content-Type: image/jpeg @@ -39,6 +38,8 @@ content type matches the filter, the entire message will be discarded. ... ... xxxxx ... """) + + >>> process = config.handlers['mime-delete'].process >>> process(mlist, msg, {}) Traceback (most recent call last): ... @@ -85,20 +86,21 @@ just that subpart will be stripped. ... From: aperson@example.com ... Content-Type: multipart/mixed; boundary=BOUNDARY ... MIME-Version: 1.0 - ... + ... ... --BOUNDARY ... Content-Type: image/jpeg ... MIME-Version: 1.0 - ... + ... ... xxx - ... + ... ... --BOUNDARY ... Content-Type: image/gif ... MIME-Version: 1.0 - ... + ... ... yyy ... --BOUNDARY-- ... """) + >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -136,24 +138,24 @@ subpart. ... From: aperson@example.com ... Content-Type: multipart/mixed; boundary=BOUNDARY ... MIME-Version: 1.0 - ... + ... ... --BOUNDARY ... Content-Type: multipart/alternative; boundary=BOUND2 ... MIME-Version: 1.0 - ... + ... ... --BOUND2 ... Content-Type: image/jpeg ... MIME-Version: 1.0 - ... + ... ... xxx - ... + ... ... --BOUND2 ... Content-Type: image/gif ... MIME-Version: 1.0 - ... + ... ... yyy ... --BOUND2-- - ... + ... ... --BOUNDARY-- ... """) >>> process(mlist, msg, {}) @@ -176,21 +178,22 @@ part with just one subpart, the entire message is converted to the left over part's content type. In other words, the left over inner part is promoted to being the outer part. - >>> mlist.filter_mime_types.append('text/html') + >>> mlist.filter_types = [u'image/jpeg', u'text/html'] >>> msg = message_from_string("""\ ... From: aperson@example.com ... Content-Type: multipart/alternative; boundary=AAA - ... + ... ... --AAA ... Content-Type: text/html - ... + ... ... <b>This is some html</b> ... --AAA ... Content-Type: text/plain - ... + ... ... This is plain text ... --AAA-- ... """) + >>> process(mlist, msg, {}) >>> print msg.as_string() From: aperson@example.com @@ -201,7 +204,7 @@ being the outer part. Clean up. - >>> ignore = mlist.filter_mime_types.pop() + >>> mlist.filter_types = [u'image/jpeg'] Conversion to plain text @@ -245,7 +248,7 @@ name of the file containing the message payload to filter. ... From: aperson@example.com ... Content-Type: text/html ... MIME-Version: 1.0 - ... + ... ... <html><head></head> ... <body></body></html> ... """) @@ -272,39 +275,39 @@ the entire inner multipart/mixed is discarded. >>> msg = message_from_string("""\ ... From: aperson@example.com ... Content-Type: multipart/mixed; boundary=AAA - ... + ... ... --AAA ... Content-Type: multipart/mixed; boundary=BBB - ... + ... ... --BBB ... Content-Type: image/jpeg - ... + ... ... xxx ... --BBB ... Content-Type: image/jpeg - ... + ... ... yyy ... --BBB--- ... --AAA ... Content-Type: multipart/alternative; boundary=CCC - ... + ... ... --CCC ... Content-Type: text/html - ... + ... ... <h2>This is a header</h2> - ... + ... ... --CCC ... Content-Type: text/plain - ... + ... ... A different message ... --CCC-- ... --AAA ... Content-Type: image/gif - ... + ... ... zzz ... --AAA ... Content-Type: image/gif - ... + ... ... aaa ... --AAA-- ... """) diff --git a/src/mailman/pipeline/mime_delete.py b/src/mailman/pipeline/mime_delete.py index 3c4e4154f..17da13c4a 100644 --- a/src/mailman/pipeline/mime_delete.py +++ b/src/mailman/pipeline/mime_delete.py @@ -54,17 +54,12 @@ log = logging.getLogger('mailman.error') def process(mlist, msg, msgdata): - # Short-circuits - if not mlist.filter_content: - return - if msgdata.get('isdigest'): - return # We also don't care about our own digests or plaintext ctype = msg.get_content_type() mtype = msg.get_content_maintype() # Check to see if the outer type matches one of the filter types - filtertypes = mlist.filter_mime_types - passtypes = mlist.pass_mime_types + filtertypes = set(mlist.filter_types) + passtypes = set(mlist.pass_types) if ctype in filtertypes or mtype in filtertypes: dispose(mlist, msg, msgdata, _("The message's content type was explicitly disallowed")) @@ -74,8 +69,8 @@ def process(mlist, msg, msgdata): dispose(mlist, msg, msgdata, _("The message's content type was not explicitly allowed")) # Filter by file extensions - filterexts = mlist.filter_filename_extensions - passexts = mlist.pass_filename_extensions + filterexts = set(mlist.filter_extensions) + passexts = set(mlist.pass_extensions) fext = get_file_ext(msg) if fext: if fext in filterexts: @@ -282,4 +277,9 @@ class MIMEDelete: description = _('Filter the MIME content of messages.') def process(self, mlist, msg, msgdata): + # Short-circuits + if not mlist.filter_content: + return + if msgdata.get('isdigest'): + return process(mlist, msg, msgdata) diff --git a/src/mailman/rules/docs/implicit-dest.txt b/src/mailman/rules/docs/implicit-dest.txt index c0439cfca..8666c1f5c 100644 --- a/src/mailman/rules/docs/implicit-dest.txt +++ b/src/mailman/rules/docs/implicit-dest.txt @@ -9,11 +9,17 @@ not explicitly mentioned in the set of message recipients. >>> print rule.name implicit-dest +In order to check for implicit destinations, we need to adapt the mailing list +to the appropriate interface. + + >>> from mailman.interfaces.mailinglist import IAcceptableAliasSet + >>> alias_set = IAcceptableAliasSet(mlist) + This rule matches messages that have an implicit destination, meaning that the mailing list's posting address isn't included in the explicit recipients. >>> mlist.require_explicit_destination = True - >>> mlist.clear_acceptable_aliases() + >>> alias_set.clear() >>> msg = message_from_string("""\ ... From: aperson@example.org @@ -50,7 +56,7 @@ then the rule will not match. >>> rule.check(mlist, msg, {}) True - >>> mlist.add_acceptable_alias(u'myfriend@example.com') + >>> alias_set.add(u'myfriend@example.com') >>> rule.check(mlist, msg, {}) False @@ -63,7 +69,7 @@ that Mailman pulled it from the appropriate news group. Additional aliases can be added. - >>> mlist.add_acceptable_alias(u'other@example.com') + >>> alias_set.add(u'other@example.com') >>> del msg['to'] >>> rule.check(mlist, msg, {}) True @@ -74,7 +80,7 @@ Additional aliases can be added. Aliases can be removed. - >>> mlist.remove_acceptable_alias(u'other@example.com') + >>> alias_set.remove(u'other@example.com') >>> rule.check(mlist, msg, {}) True @@ -84,7 +90,7 @@ Aliases can also be cleared. >>> rule.check(mlist, msg, {}) False - >>> mlist.clear_acceptable_aliases() + >>> alias_set.clear() >>> rule.check(mlist, msg, {}) True @@ -96,7 +102,7 @@ It's also possible to specify an alias pattern, i.e. a regular expression to match against the recipients. For example, we can say that if there is a recipient in the example.net domain, then the rule does not match. - >>> mlist.add_acceptable_alias(u'^.*@example.net') + >>> alias_set.add(u'^.*@example.net') >>> rule.check(mlist, msg, {}) True @@ -111,7 +117,7 @@ Bad aliases You cannot add an alias that looks like neither a pattern nor an email address. - >>> mlist.add_acceptable_alias('foobar') + >>> alias_set.add('foobar') Traceback (most recent call last): ... ValueError: foobar diff --git a/src/mailman/rules/implicit_dest.py b/src/mailman/rules/implicit_dest.py index 69e6f8434..f2e1f5446 100644 --- a/src/mailman/rules/implicit_dest.py +++ b/src/mailman/rules/implicit_dest.py @@ -30,6 +30,7 @@ from email.utils import getaddresses from zope.interface import implements from mailman.i18n import _ +from mailman.interfaces.mailinglist import IAcceptableAliasSet from mailman.interfaces.rules import IRule @@ -55,7 +56,9 @@ class ImplicitDestination: # a caret (i.e. ^), then it's a regular expression to match against. aliases = set() alias_patterns = set() - for alias in mlist.acceptable_aliases: + # Adapt the mailing list to the appropriate interface. + alias_set = IAcceptableAliasSet(mlist) + for alias in alias_set.aliases: if alias.startswith('^'): alias_patterns.add(alias) else: diff --git a/src/mailman/styles/default.py b/src/mailman/styles/default.py index ebfef6fca..ec8d8c776 100644 --- a/src/mailman/styles/default.py +++ b/src/mailman/styles/default.py @@ -94,20 +94,7 @@ from: .*@uplinkpro.com mlist.preferred_language = 'en' mlist.include_rfc2369_headers = True mlist.include_list_post_header = True - mlist.filter_mime_types = [] - mlist.pass_mime_types = [ - 'multipart/mixed', - 'multipart/alternative', - 'text/plain', - ] - mlist.filter_filename_extensions = [ - 'exe', 'bat', 'cmd', 'com', 'pif', 'scr', 'vbs', 'cpl', - ] - mlist.pass_filename_extensions = [] - mlist.filter_content = False mlist.collapse_alternatives = True - mlist.convert_html_to_plaintext = True - mlist.filter_action = 0 # Digest related variables mlist.digestable = True mlist.digest_is_default = False |
