summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2007-12-29 01:13:38 -0500
committerBarry Warsaw2007-12-29 01:13:38 -0500
commitb7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97 (patch)
treee33933b88fc4fd7e108c174da6292946bd9128e7
parent306a1ceee0aad4c623f029d8809b40f7f55b9a6f (diff)
downloadmailman-b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97.tar.gz
mailman-b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97.tar.zst
mailman-b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97.zip
-rw-r--r--Mailman/Handlers/Approve.py116
-rw-r--r--Mailman/app/rules.py22
-rw-r--r--Mailman/docs/approve.txt316
-rw-r--r--Mailman/interfaces/rules.py3
-rw-r--r--Mailman/rules/approved.py119
-rw-r--r--Mailman/rules/emergency.py2
6 files changed, 330 insertions, 248 deletions
diff --git a/Mailman/Handlers/Approve.py b/Mailman/Handlers/Approve.py
deleted file mode 100644
index 1198e6ea7..000000000
--- a/Mailman/Handlers/Approve.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Copyright (C) 1998-2007 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.
-
-"""Determine whether the message is pre-approved for delivery."""
-
-import re
-
-from email.Iterators import typed_subpart_iterator
-
-from Mailman import Errors
-from Mailman.configuration import config
-
-EMPTYSTRING = ''
-
-
-
-def process(mlist, msg, msgdata):
- # Short circuits
- if msgdata.get('approved'):
- # Digests, Usenet postings, and some other messages come pre-approved.
- # XXX we may want to further filter Usenet messages, so the test above
- # may not be entirely correct.
- return
- # See if the message has an Approved or Approve header with a valid
- # moderator password. Also look at the first non-whitespace line in the
- # file to see if it looks like an Approved header.
- missing = object()
- password = msg.get('approved', msg.get('approve', missing))
- if password is missing:
- # Find the first text/plain part in the message
- part = None
- stripped = False
- for part in typed_subpart_iterator(msg, 'text', 'plain'):
- break
- # XXX I'm not entirely sure why, but it is possible for the payload of
- # the part to be None, and you can't splitlines() on None.
- if part and part.get_payload() is not None:
- lines = part.get_payload(decode=True).splitlines(True)
- for lineno, line in enumerate(lines):
- if line.strip():
- break
- if ':' in line:
- header, value = line.split(':', 1)
- if header.lower() in ('approved', 'approve'):
- password = value.strip()
- # Now strip the first line from the payload so the
- # password doesn't leak.
- del lines[lineno]
- reset_payload(part, EMPTYSTRING.join(lines))
- stripped = True
- if stripped:
- # MAS: Bug 1181161 - Now try all the text parts in case it's
- # multipart/alternative with the approved line in HTML or other
- # text part. We make a pattern from the Approved line and delete
- # it from all text/* parts in which we find it. It would be
- # better to just iterate forward, but email compatability for pre
- # Python 2.2 returns a list, not a true iterator.
- #
- # This will process all the multipart/alternative parts in the
- # message as well as all other text parts. We shouldn't find the
- # pattern outside the multipart/alternative parts, but if we do,
- # it is probably best to delete it anyway as it does contain the
- # password.
- #
- # Make a pattern to delete. We can't just delete a line because
- # line of HTML or other fancy text may include additional message
- # text. This pattern works with HTML. It may not work with rtf
- # or whatever else is possible.
- pattern = header + ':(\s| )*' + re.escape(password)
- for part in typed_subpart_iterator(msg, 'text'):
- if part is not None and part.get_payload() is not None:
- lines = part.get_payload(decode=True)
- if re.search(pattern, lines):
- reset_payload(part, re.sub(pattern, '', lines))
- if password is not missing and password == mlist.moderator_password:
- # BAW: should we definitely deny if the password exists but does not
- # match? For now we'll let it percolate up for further determination.
- msgdata['approved'] = True
- # Used by the Emergency module
- msgdata['adminapproved'] = True
- # Has this message already been posted to this list?
- beentheres = [s.strip().lower() for s in msg.get_all('x-beenthere', [])]
- if mlist.posting_address in beentheres:
- raise Errors.LoopError
-
-
-def reset_payload(part, payload):
- # Set decoded payload maintaining content-type, format and delsp.
- # TK: Messages with 'charset=' cause trouble. So, instead of
- # part.get_content_charset('us-ascii') ...
- cset = part.get_content_charset() or 'us-ascii'
- ctype = part.get_content_type()
- format = part.get_param('format')
- delsp = part.get_param('delsp')
- del part['content-transfer-encoding']
- del part['content-type']
- part.set_payload(payload, cset)
- part.set_type(ctype)
- if format:
- part.set_param('Format', format)
- if delsp:
- part.set_param('DelSp', delsp)
diff --git a/Mailman/app/rules.py b/Mailman/app/rules.py
index 152694177..37f5d9af4 100644
--- a/Mailman/app/rules.py
+++ b/Mailman/app/rules.py
@@ -17,6 +17,11 @@
"""Process all rules defined by entry points."""
+__all__ = [
+ 'find_rule',
+ 'process',
+ ]
+
from Mailman.app.plugins import get_plugins
@@ -24,8 +29,7 @@ from Mailman.app.plugins import get_plugins
def process(mlist, msg, msgdata, rule_set=None):
"""Default rule processing plugin.
- Rules are processed in random order so a rule should not permanently alter
- a message or the message metadata.
+ Rules are processed in random order.
:param msg: The message object.
:param msgdata: The message metadata.
@@ -44,3 +48,17 @@ def process(mlist, msg, msgdata, rule_set=None):
if rule.check(mlist, msg, msgdata):
rule_matches.add(rule.name)
return rule_matches
+
+
+
+def find_rule(rule_name):
+ """Find the named rule.
+
+ :param rule_name: The name of the rule to find.
+ :return: The named rule, or None if no such rule exists.
+ """
+ for rule_set_class in get_plugins('mailman.rules'):
+ rule = rule_set_class().get(rule_name)
+ if rule is not None:
+ return rule
+ return None
diff --git a/Mailman/docs/approve.txt b/Mailman/docs/approve.txt
index 56afc1dd4..ea07058f8 100644
--- a/Mailman/docs/approve.txt
+++ b/Mailman/docs/approve.txt
@@ -13,98 +13,72 @@ approval queue. This has several use cases:
In order to support this, a mailing list can be given a 'moderator password'
which is shared among all the administrators.
- >>> from Mailman.Handlers.Approve import process
>>> from Mailman.configuration import config
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
+ >>> mlist.moderator_password = u'abcxyz'
+
+The 'approved' rule determines whether the message contains the proper
+approval or not.
+ >>> from Mailman.app.rules import find_rule
+ >>> rule = find_rule('approved')
+ >>> rule.name
+ 'approved'
-Short circuiting
-----------------
-The message may have been approved by some other means, as evident in the
-message metadata. In this case, the handler returns immediately.
+No approval
+-----------
+
+If the message has no Approve or Approved header, then the rule does not
+match.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
...
... An important message.
... """)
- >>> msgdata = {'approved': True}
- >>> process(mlist, msg, msgdata)
- >>> print msg.as_string()
- From: aperson@example.com
- <BLANKLINE>
- An important message.
- <BLANKLINE>
- >>> msgdata
- {'approved': True}
+ >>> rule.check(mlist, msg, {})
+ False
+If the message has an Approve or Approved header with a value that does not
+match the moderator password, then the rule does not match. However, the
+header is still removed.
-The Approved header
--------------------
+ >>> msg['Approve'] = u'12345'
+ >>> rule.check(mlist, msg, {})
+ False
+ >>> print msg['approve']
+ None
-If the moderator password is given in an Approved header, then the message
-gets sent through with no further posting moderation. The Approved header is
-not stripped in this handler module, but instead in the Cleanse module. This
-ensures that no moderator approval password in the headers will leak out.
+ >>> del msg['approve']
+ >>> msg['Approved'] = u'12345'
+ >>> rule.check(mlist, msg, {})
+ False
+ >>> print msg['approved']
+ None
- >>> mlist.moderator_password = u'abcxyz'
- >>> msg['Approved'] = u'abcxyz'
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
- >>> print msg.as_string()
- From: aperson@example.com
- Approved: abcxyz
- <BLANKLINE>
- An important message.
- <BLANKLINE>
- >>> sorted(msgdata.items())
- [('adminapproved', True), ('approved', True)]
+ >>> del msg['approved']
-But if the wrong password is given, then the message is not marked as being
-approved. The header is still removed though.
- >>> del msg['Approved']
- >>> msg['Approved'] = u'123456'
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
- >>> print msg.as_string()
- From: aperson@example.com
- Approved: 123456
- <BLANKLINE>
- An important message.
- <BLANKLINE>
- >>> msgdata
- {}
+Using an approval header
+------------------------
-In the spirit of being liberal in what you accept, using an Approve header is
-completely synonymous.
+If the moderator password is given in an Approve header, then the rule
+matches, and the Approve header is stripped.
- >>> del msg['Approved']
>>> msg['Approve'] = u'abcxyz'
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
- >>> print msg.as_string()
- From: aperson@example.com
- Approve: abcxyz
- <BLANKLINE>
- An important message.
- <BLANKLINE>
- >>> sorted(msgdata.items())
- [('adminapproved', True), ('approved', True)]
+ >>> rule.check(mlist, msg, {})
+ True
+ >>> print msg['approve']
+ None
- >>> del msg['Approve']
- >>> msg['Approve'] = u'123456'
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
- >>> print msg.as_string()
- From: aperson@example.com
- Approve: 123456
- <BLANKLINE>
- An important message.
- <BLANKLINE>
- >>> msgdata
- {}
+Similarly, for the Approved header.
+
+ >>> msg['Approved'] = u'abcxyz'
+ >>> rule.check(mlist, msg, {})
+ True
+ >>> print msg['approved']
+ None
Using a pseudo-header
@@ -113,17 +87,20 @@ Using a pseudo-header
Different mail user agents have varying degrees to which they support custom
headers like Approve and Approved. For this reason, Mailman also supports
using a 'pseudo-header', which is really just the first non-whitespace line in
-the payload of the message of the message. If this pseudo-header looks like a
-matching Approve or Approved header, the message is similarly allowed to pass.
+the payload of the message. If this pseudo-header looks like a matching
+Approve or Approved header, the message is similarly allowed to pass.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
...
- ... Approved: abcxyz
+ ... Approve: abcxyz
... An important message.
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ True
+
+The pseudo-header is removed.
+
>>> print msg.as_string()
From: aperson@example.com
Content-Transfer-Encoding: 7bit
@@ -132,17 +109,18 @@ matching Approve or Approved header, the message is similarly allowed to pass.
<BLANKLINE>
An important message.
<BLANKLINE>
- >>> sorted(msgdata.items())
- [('adminapproved', True), ('approved', True)]
+
+Similarly for the Approved header.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
...
- ... Approve: abcxyz
+ ... Approved: abcxyz
... An important message.
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ True
+
>>> print msg.as_string()
From: aperson@example.com
Content-Transfer-Encoding: 7bit
@@ -151,8 +129,6 @@ matching Approve or Approved header, the message is similarly allowed to pass.
<BLANKLINE>
An important message.
<BLANKLINE>
- >>> sorted(msgdata.items())
- [('adminapproved', True), ('approved', True)]
As before, a mismatch in the pseudo-header does not approve the message, but
the pseudo-header line is still removed.
@@ -160,11 +136,12 @@ the pseudo-header line is still removed.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
...
- ... Approved: 123456
+ ... Approve: 123456
... An important message.
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ False
+
>>> print msg.as_string()
From: aperson@example.com
Content-Transfer-Encoding: 7bit
@@ -173,17 +150,18 @@ the pseudo-header line is still removed.
<BLANKLINE>
An important message.
<BLANKLINE>
- >>> msgdata
- {}
+
+Similarly for the Approved header.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
...
- ... Approve: 123456
+ ... Approved: 123456
... An important message.
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ False
+
>>> print msg.as_string()
From: aperson@example.com
Content-Transfer-Encoding: 7bit
@@ -192,8 +170,6 @@ the pseudo-header line is still removed.
<BLANKLINE>
An important message.
<BLANKLINE>
- >>> msgdata
- {}
MIME multipart support
@@ -211,18 +187,21 @@ used with MIME documents.
... --AAA
... Content-Type: application/x-ignore
...
- ... Approved: 123456
+ ... Approve: 123456
... The above line will be ignored.
...
... --AAA
... Content-Type: text/plain
...
- ... Approved: abcxyz
+ ... Approve: abcxyz
... An important message.
... --AAA--
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ True
+
+Like before, the pseudo-header is removed, but only from the text parts.
+
>>> print msg.as_string()
From: aperson@example.com
MIME-Version: 1.0
@@ -231,7 +210,7 @@ used with MIME documents.
--AAA
Content-Type: application/x-ignore
<BLANKLINE>
- Approved: 123456
+ Approve: 123456
The above line will be ignored.
<BLANKLINE>
--AAA
@@ -242,8 +221,8 @@ used with MIME documents.
An important message.
--AAA--
<BLANKLINE>
- >>> sorted(msgdata.items())
- [('adminapproved', True), ('approved', True)]
+
+The same goes for the Approved message.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
@@ -253,18 +232,21 @@ used with MIME documents.
... --AAA
... Content-Type: application/x-ignore
...
- ... Approve: 123456
+ ... Approved: 123456
... The above line will be ignored.
...
... --AAA
... Content-Type: text/plain
...
- ... Approve: abcxyz
+ ... Approved: abcxyz
... An important message.
... --AAA--
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ True
+
+And the header is removed.
+
>>> print msg.as_string()
From: aperson@example.com
MIME-Version: 1.0
@@ -273,7 +255,7 @@ used with MIME documents.
--AAA
Content-Type: application/x-ignore
<BLANKLINE>
- Approve: 123456
+ Approved: 123456
The above line will be ignored.
<BLANKLINE>
--AAA
@@ -284,8 +266,6 @@ used with MIME documents.
An important message.
--AAA--
<BLANKLINE>
- >>> sorted(msgdata.items())
- [('adminapproved', True), ('approved', True)]
Here, the correct password is in the non-text/plain part, so it is ignored.
@@ -307,8 +287,11 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
... An important message.
... --AAA--
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ False
+
+And yet the pseudo-header is still stripped.
+
>>> print msg.as_string()
From: aperson@example.com
MIME-Version: 1.0
@@ -327,8 +310,8 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
<BLANKLINE>
An important message.
--AAA--
- >>> msgdata
- {}
+
+As before, the same goes for the Approved header.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
@@ -338,18 +321,21 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
... --AAA
... Content-Type: application/x-ignore
...
- ... Approve: abcxyz
+ ... Approved: abcxyz
... The above line will be ignored.
...
... --AAA
... Content-Type: text/plain
...
- ... Approve: 123456
+ ... Approved: 123456
... An important message.
... --AAA--
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ False
+
+And the pseudo-header is removed.
+
>>> print msg.as_string()
From: aperson@example.com
MIME-Version: 1.0
@@ -358,7 +344,7 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
--AAA
Content-Type: application/x-ignore
<BLANKLINE>
- Approve: abcxyz
+ Approved: abcxyz
The above line will be ignored.
<BLANKLINE>
--AAA
@@ -368,8 +354,14 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
<BLANKLINE>
An important message.
--AAA--
- >>> msgdata
- {}
+
+
+Stripping text/html parts
+-------------------------
+
+Because some mail readers will include both a text/plain part and a text/html
+alternative, the 'approved' rule has to search the alternatives and strip
+anything that looks like an Approve or Approved headers.
>>> msg = message_from_string(u"""\
... From: aperson@example.com
@@ -377,30 +369,100 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
... Content-Type: multipart/mixed; boundary="AAA"
...
... --AAA
- ... Content-Type: application/x-ignore
+ ... Content-Type: text/html
+ ...
+ ... <html>
+ ... <head></head>
+ ... <body>
+ ... <b>Approved: abcxyz</b>
+ ... <p>The above line will be ignored.
+ ... </body>
+ ... </html>
+ ...
+ ... --AAA
+ ... Content-Type: text/plain
...
... Approved: abcxyz
- ... The above line will be ignored.
+ ... An important message.
+ ... --AAA--
+ ... """)
+ >>> rule.check(mlist, msg, {})
+ True
+
+And the header-like text in the text/html part was stripped.
+
+ >>> print msg.as_string()
+ From: aperson@example.com
+ MIME-Version: 1.0
+ Content-Type: multipart/mixed; boundary="AAA"
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/html; charset="us-ascii"
+ <BLANKLINE>
+ <html>
+ <head></head>
+ <body>
+ <b></b>
+ <p>The above line will be ignored.
+ </body>
+ </html>
+ <BLANKLINE>
+ --AAA
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ <BLANKLINE>
+ An important message.
+ --AAA--
+ <BLANKLINE>
+
+This is true even if the rule does not match.
+
+ >>> msg = message_from_string(u"""\
+ ... From: aperson@example.com
+ ... MIME-Version: 1.0
+ ... Content-Type: multipart/mixed; boundary="AAA"
+ ...
+ ... --AAA
+ ... Content-Type: text/html
+ ...
+ ... <html>
+ ... <head></head>
+ ... <body>
+ ... <b>Approve: 123456</b>
+ ... <p>The above line will be ignored.
+ ... </body>
+ ... </html>
...
... --AAA
... Content-Type: text/plain
...
- ... Approved: 123456
+ ... Approve: 123456
... An important message.
... --AAA--
... """)
- >>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> rule.check(mlist, msg, {})
+ False
+
>>> print msg.as_string()
From: aperson@example.com
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="AAA"
<BLANKLINE>
--AAA
- Content-Type: application/x-ignore
+ Content-Transfer-Encoding: 7bit
+ MIME-Version: 1.0
+ Content-Type: text/html; charset="us-ascii"
<BLANKLINE>
- Approved: abcxyz
- The above line will be ignored.
+ <html>
+ <head></head>
+ <body>
+ <b></b>
+ <p>The above line will be ignored.
+ </body>
+ </html>
<BLANKLINE>
--AAA
Content-Transfer-Encoding: 7bit
@@ -410,5 +472,3 @@ Here, the correct password is in the non-text/plain part, so it is ignored.
An important message.
--AAA--
<BLANKLINE>
- >>> msgdata
- {}
diff --git a/Mailman/interfaces/rules.py b/Mailman/interfaces/rules.py
index 5ef741852..13edcf481 100644
--- a/Mailman/interfaces/rules.py
+++ b/Mailman/interfaces/rules.py
@@ -35,6 +35,7 @@ class IRule(Interface):
def check(mlist, msg, msgdata):
"""Run the rule.
+ :param mlist: The mailing list object.
:param msg: The message object.
:param msgdata: The message metadata.
:return: A boolean specifying whether the rule was matched or not.
@@ -45,7 +46,7 @@ class IRule(Interface):
class IRuleSet(Interface):
"""A rule processor."""
- rules = Attribute('The set of all rules this processor knows about')
+ rules = Attribute('The set of all rules this processor knows about.')
def __getitem__(rule_name):
"""Return the named rule.
diff --git a/Mailman/rules/approved.py b/Mailman/rules/approved.py
new file mode 100644
index 000000000..e5c13f770
--- /dev/null
+++ b/Mailman/rules/approved.py
@@ -0,0 +1,119 @@
+# Copyright (C) 2007 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.
+
+"""Look for a matching Approve header."""
+
+__all__ = ['approve_rule']
+__metaclass__ = type
+
+
+import re
+from email.iterators import typed_subpart_iterator
+from zope.interface import implements
+
+from Mailman.i18n import _
+from Mailman.interfaces import IRule
+
+
+EMPTYSTRING = u''
+
+
+
+class Approved:
+ implements(IRule)
+
+ name = 'approved'
+ description = _('The message has a matching Approve or Approved header.')
+
+ def check(self, mlist, msg, msgdata):
+ """See `IRule`."""
+ # See if the message has an Approved or Approve header with a valid
+ # moderator password. Also look at the first non-whitespace line in
+ # the file to see if it looks like an Approved header.
+ missing = object()
+ password = msg.get('approved', msg.get('approve', missing))
+ if password is missing:
+ # Find the first text/plain part in the message
+ part = None
+ stripped = False
+ for part in typed_subpart_iterator(msg, 'text', 'plain'):
+ break
+ payload = part.get_payload(decode=True)
+ if payload is not None:
+ lines = payload.splitlines(True)
+ for lineno, line in enumerate(lines):
+ if line.strip() <> '':
+ break
+ if ':' in line:
+ header, value = line.split(':', 1)
+ if header.lower() in ('approved', 'approve'):
+ password = value.strip()
+ # Now strip the first line from the payload so the
+ # password doesn't leak.
+ del lines[lineno]
+ reset_payload(part, EMPTYSTRING.join(lines))
+ stripped = True
+ if stripped:
+ # Now try all the text parts in case it's
+ # multipart/alternative with the approved line in HTML or
+ # other text part. We make a pattern from the Approved line
+ # and delete it from all text/* parts in which we find it. It
+ # would be better to just iterate forward, but email
+ # compatability for pre Python 2.2 returns a list, not a true
+ # iterator.
+ #
+ # This will process all the multipart/alternative parts in the
+ # message as well as all other text parts. We shouldn't find
+ # the pattern outside the multipart/alternative parts, but if
+ # we do, it is probably best to delete it anyway as it does
+ # contain the password.
+ #
+ # Make a pattern to delete. We can't just delete a line
+ # because line of HTML or other fancy text may include
+ # additional message text. This pattern works with HTML. It
+ # may not work with rtf or whatever else is possible.
+ pattern = header + ':(\s|&nbsp;)*' + re.escape(password)
+ for part in typed_subpart_iterator(msg, 'text'):
+ payload = part.get_payload(decode=True)
+ if payload is not None:
+ if re.search(pattern, payload):
+ reset_payload(part, re.sub(pattern, '', payload))
+ else:
+ del msg['approved']
+ del msg['approve']
+ return password is not missing and password == mlist.moderator_password
+
+
+
+def reset_payload(part, payload):
+ # Set decoded payload maintaining content-type, charset, format and delsp.
+ charset = part.get_content_charset() or 'us-ascii'
+ content_type = part.get_content_type()
+ format = part.get_param('format')
+ delsp = part.get_param('delsp')
+ del part['content-transfer-encoding']
+ del part['content-type']
+ part.set_payload(payload, charset)
+ part.set_type(content_type)
+ if format:
+ part.set_param('Format', format)
+ if delsp:
+ part.set_param('DelSp', delsp)
+
+
+
+approve_rule = Approved()
diff --git a/Mailman/rules/emergency.py b/Mailman/rules/emergency.py
index 088ba4ec8..16cb0db8a 100644
--- a/Mailman/rules/emergency.py
+++ b/Mailman/rules/emergency.py
@@ -17,7 +17,7 @@
"""The emergency hold rule."""
-__all__ = ['Emergency']
+__all__ = ['emergency_rule']
__metaclass__ = type