diff options
| author | Barry Warsaw | 2007-12-29 01:13:38 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2007-12-29 01:13:38 -0500 |
| commit | b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97 (patch) | |
| tree | e33933b88fc4fd7e108c174da6292946bd9128e7 | |
| parent | 306a1ceee0aad4c623f029d8809b40f7f55b9a6f (diff) | |
| download | mailman-b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97.tar.gz mailman-b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97.tar.zst mailman-b7fc1a2cd9cc63f9bfafb6d3e7a28f3b64f3ed97.zip | |
| -rw-r--r-- | Mailman/Handlers/Approve.py | 116 | ||||
| -rw-r--r-- | Mailman/app/rules.py | 22 | ||||
| -rw-r--r-- | Mailman/docs/approve.txt | 316 | ||||
| -rw-r--r-- | Mailman/interfaces/rules.py | 3 | ||||
| -rw-r--r-- | Mailman/rules/approved.py | 119 | ||||
| -rw-r--r-- | Mailman/rules/emergency.py | 2 |
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| )*' + 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 |
