# 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.
<p>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
<a href="?VARHELP=contentfilter/filter_mime_types">filter
types</a>. If the attachment type matches an entry in the filter
types, it is discarded.
<p>Then, if there are <a
href="?VARHELP=contentfilter/pass_mime_types">pass types</a>
defined, any attachment type that does <em>not</em> match a
pass type is also discarded. If there are no pass types defined,
this check is skipped.
<p>After this initial filtering, any <tt>multipart</tt>
attachments that are empty are removed. If the outer message is
left empty after this filtering, then the whole message is
discarded.
<p> Then, each <tt>multipart/alternative</tt> section will
be replaced by just the first alternative that is non-empty after
filtering if
<a href="?VARHELP=contentfilter/collapse_alternatives"
>collapse_alternatives</a> is enabled.
<p>Finally, any <tt>text/html</tt> parts that are left in the
message may be converted to <tt>text/plain</tt> if
<a href="?VARHELP=contentfilter/convert_html_to_plaintext"
>convert_html_to_plaintext</a> 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 <tt>type/subtype</tt>,
e.g. <tt>image/gif</tt>. Leave off the subtype to remove all
parts with a matching major content type, e.g. <tt>image</tt>.
<p>Blank lines are ignored.
<p>See also <a href="?VARHELP=contentfilter/pass_mime_types"
>pass_mime_types</a> 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 <a href="?VARHELP=contentfilter/filter_mime_types"
>filter_mime_types</a>.
<p><b>Note:</b> if you add entries to this list but don't add
<tt>multipart</tt> 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 <tt>text/html</tt> 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 take when the message matches one of
the content filtering rules, meaning, the top-level
content type matches one of the <a
href="?VARHELP=contentfilter/filter_mime_types"
>filter_mime_types</a>, or the top-level content type does
<strong>not</strong> match one of the
<a href="?VARHELP=contentfilter/pass_mime_types"
>pass_mime_types</a>, or if after filtering the subparts of the
message, the message ends up empty.
<p>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.
<p>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