Extended test_documentation.py to be able to find doctests in subdirectories
[mailman.git] / Mailman / rules / approved.py
blob98b158dd9d86b763e79a7a3c3376629da3e4c162
1 # Copyright (C) 2007 by the Free Software Foundation, Inc.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16 # USA.
18 """Look for moderator pre-approval."""
20 __all__ = ['Approved']
21 __metaclass__ = type
24 import re
25 from email.iterators import typed_subpart_iterator
26 from zope.interface import implements
28 from Mailman.i18n import _
29 from Mailman.interfaces import IRule
32 EMPTYSTRING = u''
36 class Approved:
37 """Look for moderator pre-approval."""
38 implements(IRule)
40 name = 'approved'
41 description = _('The message has a matching Approve or Approved header.')
43 def check(self, mlist, msg, msgdata):
44 """See `IRule`."""
45 # See if the message has an Approved or Approve header with a valid
46 # moderator password. Also look at the first non-whitespace line in
47 # the file to see if it looks like an Approved header.
48 missing = object()
49 password = msg.get('approved', msg.get('approve', missing))
50 if password is missing:
51 # Find the first text/plain part in the message
52 part = None
53 stripped = False
54 for part in typed_subpart_iterator(msg, 'text', 'plain'):
55 break
56 payload = part.get_payload(decode=True)
57 if payload is not None:
58 lines = payload.splitlines(True)
59 for lineno, line in enumerate(lines):
60 if line.strip() <> '':
61 break
62 if ':' in line:
63 header, value = line.split(':', 1)
64 if header.lower() in ('approved', 'approve'):
65 password = value.strip()
66 # Now strip the first line from the payload so the
67 # password doesn't leak.
68 del lines[lineno]
69 reset_payload(part, EMPTYSTRING.join(lines))
70 stripped = True
71 if stripped:
72 # Now try all the text parts in case it's
73 # multipart/alternative with the approved line in HTML or
74 # other text part. We make a pattern from the Approved line
75 # and delete it from all text/* parts in which we find it. It
76 # would be better to just iterate forward, but email
77 # compatability for pre Python 2.2 returns a list, not a true
78 # iterator.
80 # This will process all the multipart/alternative parts in the
81 # message as well as all other text parts. We shouldn't find
82 # the pattern outside the multipart/alternative parts, but if
83 # we do, it is probably best to delete it anyway as it does
84 # contain the password.
86 # Make a pattern to delete. We can't just delete a line
87 # because line of HTML or other fancy text may include
88 # additional message text. This pattern works with HTML. It
89 # may not work with rtf or whatever else is possible.
90 pattern = header + ':(\s|&nbsp;)*' + re.escape(password)
91 for part in typed_subpart_iterator(msg, 'text'):
92 payload = part.get_payload(decode=True)
93 if payload is not None:
94 if re.search(pattern, payload):
95 reset_payload(part, re.sub(pattern, '', payload))
96 else:
97 del msg['approved']
98 del msg['approve']
99 return password is not missing and password == mlist.moderator_password
103 def reset_payload(part, payload):
104 # Set decoded payload maintaining content-type, charset, format and delsp.
105 charset = part.get_content_charset() or 'us-ascii'
106 content_type = part.get_content_type()
107 format = part.get_param('format')
108 delsp = part.get_param('delsp')
109 del part['content-transfer-encoding']
110 del part['content-type']
111 part.set_payload(payload, charset)
112 part.set_type(content_type)
113 if format:
114 part.set_param('Format', format)
115 if delsp:
116 part.set_param('DelSp', delsp)