# Copyright (C) 2002-2005 by the Free Software Foundation, Inc. # # This program 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 2 # of the License, or (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, # USA. """GUI component managing the content filtering options.""" from Mailman import mm_cfg from Mailman.i18n import _ from Mailman.Gui.GUIBase import GUIBase NL = '\n' class ContentFilter(GUIBase): def GetConfigCategory(self): return 'contentfilter', _('Content filtering') def GetConfigInfo(self, mlist, category, subcat=None): if category <> 'contentfilter': return None WIDTH = mm_cfg.TEXTFIELDWIDTH actions = [_('Discard'), _('Reject'), _('Forward to List Owner')] if mm_cfg.OWNERS_CAN_PRESERVE_FILTERED_MESSAGES: actions.append(_('Preserve')) return [ _("""Policies concerning the content of list traffic.
Content filtering works like this: when a message is received by the list and you have enabled content filtering, the individual attachments are first compared to the filter types. If the attachment type matches an entry in the filter types, it is discarded.
Then, if there are pass types defined, any attachment type that does not match a pass type is also discarded. If there are no pass types defined, this check is skipped.
After this initial filtering, any multipart attachments that are empty are removed. If the outer message is left empty after this filtering, then the whole message is discarded.
Then, each multipart/alternative section will be replaced by just the first alternative that is non-empty after filtering if collapse_alternatives is enabled.
Finally, any text/html parts that are left in the message may be converted to text/plain if convert_html_to_plaintext is enabled and the site is configured to allow these conversions."""), ('filter_content', mm_cfg.Radio, (_('No'), _('Yes')), 0, _("""Should Mailman filter the content of list traffic according to the settings below?""")), ('filter_mime_types', mm_cfg.Text, (10, WIDTH), 0, _("""Remove message attachments that have a matching content type."""), _("""Use this option to remove each message attachment that matches one of these content types. Each line should contain a string naming a MIME type/subtype, e.g. image/gif. Leave off the subtype to remove all parts with a matching major content type, e.g. image.
Blank lines are ignored.
See also pass_mime_types for a content type whitelist.""")), ('pass_mime_types', mm_cfg.Text, (10, WIDTH), 0, _("""Remove message attachments that don't have a matching content type. Leave this field blank to skip this filter test."""), _("""Use this option to remove each message attachment that does not have a matching content type. Requirements and formats are exactly like filter_mime_types.
Note: if you add entries to this list but don't add multipart to this list, any messages with attachments will be rejected by the pass filter.""")), ('filter_filename_extensions', mm_cfg.Text, (10, WIDTH), 0, _("""Remove message attachments that have a matching filename extension."""),), ('pass_filename_extensions', mm_cfg.Text, (10, WIDTH), 0, _("""Remove message attachments that don't have a matching filename extension. Leave this field blank to skip this filter test."""),), ('collapse_alternatives', mm_cfg.Radio, (_('No'), _('Yes')), 0, _("""Should Mailman collapse multipart/alternative to its first part content?""")), ('convert_html_to_plaintext', mm_cfg.Radio, (_('No'), _('Yes')), 0, _("""Should Mailman convert text/html parts to plain text? This conversion happens after MIME attachments have been stripped.""")), ('filter_action', mm_cfg.Radio, tuple(actions), 0, _("""Action to take when a message matches the content filtering rules."""), _("""One of these actions is taken when the message matches one of the content filtering rules, meaning, the top-level content type matches one of the filter_mime_types, or the top-level content type does not match one of the pass_mime_types, or if after filtering the subparts of the message, the message ends up empty.
Note this action is not taken if after filtering the message still contains content. In that case the message is always forwarded on to the list membership.
When messages are discarded, a log entry is written containing the Message-ID of the discarded message. When messages are rejected or forwarded to the list owner, a reason for the rejection is included in the bounce message to the original author. When messages are preserved, they are saved in a special queue directory on disk for the site administrator to view (and possibly rescue) but otherwise discarded. This last option is only available if enabled by the site administrator.""")), ] def _setValue(self, mlist, property, val, doc): if property in ('filter_mime_types', 'pass_mime_types'): types = [] for spectype in [s.strip() for s in val.splitlines()]: ok = 1 slashes = spectype.count('/') if slashes == 0 and not spectype: ok = 0 elif slashes == 1: maintype, subtype = [s.strip().lower() for s in spectype.split('/')] if not maintype or not subtype: ok = 0 elif slashes > 1: ok = 0 if not ok: doc.addError(_('Bad MIME type ignored: %(spectype)s')) else: types.append(spectype.strip().lower()) if property == 'filter_mime_types': mlist.filter_mime_types = types elif property == 'pass_mime_types': mlist.pass_mime_types = types elif property in ('filter_filename_extensions', 'pass_filename_extensions'): fexts = [] for ext in [s.strip() for s in val.splitlines()]: fexts.append(ext.lower()) if property == 'filter_filename_extensions': mlist.filter_filename_extensions = fexts elif property == 'pass_filename_extensions': mlist.pass_filename_extensions = fexts else: GUIBase._setValue(self, mlist, property, val, doc) def getValue(self, mlist, kind, property, params): if property == 'filter_mime_types': return NL.join(mlist.filter_mime_types) if property == 'pass_mime_types': return NL.join(mlist.pass_mime_types) if property == 'filter_filename_extensions': return NL.join(mlist.filter_filename_extensions) if property == 'pass_filename_extensions': return NL.join(mlist.pass_filename_extensions) return None