aboutsummaryrefslogtreecommitdiffstats
path: root/Mailman/Gui/ContentFilter.py
blob: a2fad54b2f1896274ef7c16b09395c78829c72a1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# 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