summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2008-02-17 17:34:21 -0500
committerBarry Warsaw2008-02-17 17:34:21 -0500
commit69d158b13ae9cfa37040c2e7a664ca266b42050b (patch)
tree07f48ee990b6bab514f86199eaa250a04280120c
parentb36de8a6a5b84021c003b728274f7e9e95861c9d (diff)
downloadmailman-69d158b13ae9cfa37040c2e7a664ca266b42050b.tar.gz
mailman-69d158b13ae9cfa37040c2e7a664ca266b42050b.tar.zst
mailman-69d158b13ae9cfa37040c2e7a664ca266b42050b.zip
-rw-r--r--Mailman/Defaults.py32
-rw-r--r--Mailman/app/chains.py2
-rw-r--r--Mailman/app/pipelines.py98
-rw-r--r--Mailman/configuration.py1
-rw-r--r--Mailman/initialize.py2
-rw-r--r--Mailman/interfaces/handler.py37
-rw-r--r--Mailman/interfaces/pipeline.py32
-rw-r--r--Mailman/pipeline/__init__.py50
-rw-r--r--Mailman/pipeline/acknowledge.py83
-rw-r--r--Mailman/pipeline/after_delivery.py27
-rw-r--r--Mailman/pipeline/avoid_duplicates.py140
-rw-r--r--Mailman/pipeline/calculate_recipients.py104
-rw-r--r--Mailman/pipeline/cleanse.py71
-rw-r--r--Mailman/pipeline/cleanse_dkim.py29
-rw-r--r--Mailman/pipeline/cook_headers.py21
-rw-r--r--Mailman/pipeline/decorate.py21
-rw-r--r--Mailman/pipeline/docs/acknowledge.txt16
-rw-r--r--Mailman/pipeline/docs/after-delivery.txt4
-rw-r--r--Mailman/pipeline/docs/archives.txt16
-rw-r--r--Mailman/pipeline/docs/avoid-duplicates.txt16
-rw-r--r--Mailman/pipeline/docs/calc-recips.txt8
-rw-r--r--Mailman/pipeline/docs/cleanse.txt8
-rw-r--r--Mailman/pipeline/docs/file-recips.txt10
-rw-r--r--Mailman/pipeline/docs/nntp.txt10
-rw-r--r--Mailman/pipeline/file_recipients.py54
-rw-r--r--Mailman/pipeline/mime_delete.py19
-rw-r--r--Mailman/pipeline/moderate.py21
-rw-r--r--Mailman/pipeline/replybot.py20
-rw-r--r--Mailman/pipeline/scrubber.py20
-rw-r--r--Mailman/pipeline/smtp_direct.py30
-rw-r--r--Mailman/pipeline/tagger.py24
-rw-r--r--Mailman/pipeline/to_archive.py43
-rw-r--r--Mailman/pipeline/to_digest.py21
-rw-r--r--Mailman/pipeline/to_outgoing.py70
-rw-r--r--Mailman/pipeline/to_usenet.py55
-rw-r--r--Mailman/queue/docs/outgoing.txt14
-rw-r--r--Mailman/rules/__init__.py2
-rw-r--r--setup.py1
38 files changed, 875 insertions, 357 deletions
diff --git a/Mailman/Defaults.py b/Mailman/Defaults.py
index d9d1ea717..aab290d26 100644
--- a/Mailman/Defaults.py
+++ b/Mailman/Defaults.py
@@ -488,38 +488,6 @@ NNTP_REWRITE_DUPLICATE_HEADERS = [
# may wish to remove these headers by setting this to Yes.
REMOVE_DKIM_HEADERS = No
-# All `normal' messages which are delivered to the entire list membership go
-# through this pipeline of handler modules. Lists themselves can override the
-# global pipeline by defining a `pipeline' attribute.
-GLOBAL_PIPELINE = [
- # These are the modules that do tasks common to all delivery paths.
- 'SpamDetect',
- 'Approve',
- 'Replybot',
- 'Moderate',
- 'Hold',
- 'MimeDel',
- 'Scrubber',
- 'Emergency',
- 'Tagger',
- 'CalcRecips',
- 'AvoidDuplicates',
- 'Cleanse',
- 'CleanseDKIM',
- 'CookHeaders',
- # And now we send the message to the digest mbox file, and to the arch and
- # news queues. Runners will provide further processing of the message,
- # specific to those delivery paths.
- 'ToDigest',
- 'ToArchive',
- 'ToUsenet',
- # Now we'll do a few extra things specific to the member delivery
- # (outgoing) path, finally leaving the message in the outgoing queue.
- 'AfterDelivery',
- 'Acknowledge',
- 'ToOutgoing',
- ]
-
# This is the pipeline which messages sent to the -owner address go through
OWNER_PIPELINE = [
'SpamDetect',
diff --git a/Mailman/app/chains.py b/Mailman/app/chains.py
index 2bf0b9ae1..6b34b1dfb 100644
--- a/Mailman/app/chains.py
+++ b/Mailman/app/chains.py
@@ -38,10 +38,10 @@ from Mailman.interfaces import LinkAction
def process(mlist, msg, msgdata, start_chain='built-in'):
"""Process the message through a chain.
- :param start_chain: The name of the chain to start the processing with.
:param mlist: the IMailingList for this message.
:param msg: The Message object.
:param msgdata: The message metadata dictionary.
+ :param start_chain: The name of the chain to start the processing with.
"""
# Set up some bookkeeping.
chain_stack = []
diff --git a/Mailman/app/pipelines.py b/Mailman/app/pipelines.py
new file mode 100644
index 000000000..20cc37b3a
--- /dev/null
+++ b/Mailman/app/pipelines.py
@@ -0,0 +1,98 @@
+# Copyright (C) 2008 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.
+
+"""Pipeline processor."""
+
+__metaclass__ = type
+__all__ = [
+ 'initialize',
+ 'process',
+ ]
+
+
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from Mailman.app.plugins import get_plugins
+from Mailman.configuration import config
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler, IPipeline
+
+
+
+def process(mlist, msg, msgdata, pipeline_name='built-in'):
+ """Process the message through the given pipeline.
+
+ :param mlist: the IMailingList for this message.
+ :param msg: The Message object.
+ :param msgdata: The message metadata dictionary.
+ :param pipeline_name: The name of the pipeline to process through.
+ """
+ pipeline = config.pipelines[pipeline_name]
+ for handler in pipeline:
+ handler.process(mlist, msg, msgdata)
+
+
+
+class BuiltInPipeline:
+ """The built-in pipeline."""
+
+ implements(IPipeline)
+
+ name = 'built-in'
+ description = _('The built-in pipeline.')
+
+ _default_handlers = (
+ 'mimedel',
+ 'scrubber',
+ 'tagger',
+ 'calculate-recipients',
+ 'avoid-duplicates',
+ 'cleanse',
+ 'cleanse_dkim',
+ 'cook_headers',
+ 'to_digest',
+ 'to_archive',
+ 'to_usenet',
+ 'after_delivery',
+ 'acknowledge',
+ 'to_outgoing',
+ )
+
+ def __init__(self):
+ self._handlers = []
+ for handler_name in self._default_handlers:
+ self._handler.append(config.handlers[handler_name])
+
+ def __iter__(self):
+ """See `IPipeline`."""
+ for handler in self._handlers:
+ yield handler
+
+
+
+def initialize():
+ """Initialize the pipelines."""
+ # Find all handlers in the registered plugins.
+ for handler_finder in get_plugins('mailman.handlers'):
+ for handler_class in handler_finder():
+ handler = handler_class()
+ verifyObject(IHandler, handler)
+ assert handler.name not in config.handlers, (
+ 'Duplicate handler "%s" found in %s' %
+ (handler.name, handler_finder))
+ config.handlers[handler.name] = handler
diff --git a/Mailman/configuration.py b/Mailman/configuration.py
index 9c850295c..4c9d0a050 100644
--- a/Mailman/configuration.py
+++ b/Mailman/configuration.py
@@ -179,6 +179,7 @@ class Configuration(object):
# Create the registry of rules and chains.
self.chains = {}
self.rules = {}
+ self.handlers = {}
def add_domain(self, email_host, url_host=None):
"""Add a virtual domain.
diff --git a/Mailman/initialize.py b/Mailman/initialize.py
index 9ab4949cd..4b4aa4898 100644
--- a/Mailman/initialize.py
+++ b/Mailman/initialize.py
@@ -67,8 +67,10 @@ def initialize_2(debug=False):
# circular imports.
from Mailman.app.chains import initialize as initialize_chains
from Mailman.app.rules import initialize as initialize_rules
+ from Mailman.app.pipelines import initialize as initialize_pipelines
initialize_rules()
initialize_chains()
+ initialize_pipelines()
def initialize(config_path=None, propagate_logs=False):
diff --git a/Mailman/interfaces/handler.py b/Mailman/interfaces/handler.py
new file mode 100644
index 000000000..5a87bf882
--- /dev/null
+++ b/Mailman/interfaces/handler.py
@@ -0,0 +1,37 @@
+# Copyright (C) 2008 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.
+
+"""Interface describing a pipeline handler."""
+
+from zope.interface import Interface, Attribute
+
+
+
+class IHandler(Interface):
+ """A basic pipeline handler."""
+
+ name = Attribute('Handler name; must be unique.')
+
+ description = Attribute('A brief description of the handler.')
+
+ def process(mlist, msg, msgdata):
+ """Run the handler.
+
+ :param mlist: The mailing list object.
+ :param msg: The message object.
+ :param msgdata: The message metadata.
+ """
diff --git a/Mailman/interfaces/pipeline.py b/Mailman/interfaces/pipeline.py
new file mode 100644
index 000000000..7e7b06bed
--- /dev/null
+++ b/Mailman/interfaces/pipeline.py
@@ -0,0 +1,32 @@
+# Copyright (C) 2008 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.
+
+"""Interface for describing pipelines."""
+
+from zope.interface import Interface, Attribute
+
+
+
+class IPipeline(Interface):
+ """A pipeline of handlers."""
+
+ name = Attribute('Pipeline name; must be unique.')
+ description = Attribute('A brief description of this pipeline.')
+
+ def __iter__():
+ """Iterate over all the handlers in this pipeline."""
+
diff --git a/Mailman/pipeline/__init__.py b/Mailman/pipeline/__init__.py
index e69de29bb..0a31b72f0 100644
--- a/Mailman/pipeline/__init__.py
+++ b/Mailman/pipeline/__init__.py
@@ -0,0 +1,50 @@
+# Copyright (C) 2008 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.
+
+"""The built in set of pipeline handlers."""
+
+__metaclass__ = type
+__all__ = ['initialize']
+
+
+import os
+import sys
+
+from Mailman.interfaces import IHandler
+
+
+
+def initialize():
+ """Initialize the built-in handlers.
+
+ Rules are auto-discovered by searching for IHandler implementations in all
+ importable modules in this subpackage.
+ """
+ # Find all rules found in all modules inside our package.
+ import Mailman.pipeline
+ here = os.path.dirname(Mailman.pipeline.__file__)
+ for filename in os.listdir(here):
+ basename, extension = os.path.splitext(filename)
+ if extension <> '.py':
+ continue
+ module_name = 'Mailman.pipeline.' + basename
+ __import__(module_name, fromlist='*')
+ module = sys.modules[module_name]
+ for name in getattr(module, '__all__', ()):
+ handler = getattr(module, name)
+ if IHandler.implementedBy(handler):
+ yield handler
diff --git a/Mailman/pipeline/acknowledge.py b/Mailman/pipeline/acknowledge.py
index cf8e3d338..5bbbf52dc 100644
--- a/Mailman/pipeline/acknowledge.py
+++ b/Mailman/pipeline/acknowledge.py
@@ -15,51 +15,66 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-"""Send an acknowledgement of the successful post to the sender.
+"""Send an acknowledgment of the successful post to the sender.
This only happens if the sender has set their AcknowledgePosts attribute.
-This module must appear after the deliverer in the message pipeline in order
-to send acks only after successful delivery.
-
"""
+__metaclass__ = type
+__all__ = ['Acknowledge']
+
+
+from zope.interface import implements
+
from Mailman import Errors
from Mailman import Message
from Mailman import Utils
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.interfaces import IHandler
__i18n_templates__ = True
-def process(mlist, msg, msgdata):
- # Extract the sender's address and find them in the user database
- sender = msgdata.get('original_sender', msg.get_sender())
- member = mlist.members.get_member(sender)
- if member is None:
- return
- ack = member.acknowledge_posts
- if not ack:
- return
- # Okay, they want acknowledgement of their post. Give them their original
- # subject. BAW: do we want to use the decoded header?
- origsubj = msgdata.get('origsubj', msg.get('subject', _('(no subject)')))
- # Get the user's preferred language
- lang = msgdata.get('lang', member.preferred_language)
- # Now get the acknowledgement template
- realname = mlist.real_name
- text = Utils.maketext(
- 'postack.txt',
- {'subject' : Utils.oneline(origsubj, Utils.GetCharSet(lang)),
- 'listname' : realname,
- 'listinfo_url': mlist.script_url('listinfo'),
- 'optionsurl' : member.options_url,
- }, lang=lang, mlist=mlist, raw=True)
- # Craft the outgoing message, with all headers and attributes
- # necessary for general delivery. Then enqueue it to the outgoing
- # queue.
- subject = _('$realname post acknowledgment')
- usermsg = Message.UserNotification(sender, mlist.bounces_address,
- subject, text, lang)
- usermsg.send(mlist)
+class Acknowledge:
+ """Send an acknowledgment."""
+ implements(IHandler)
+
+ name = 'acknowledge'
+ description = _("""Send an acknowledgment of a posting.""")
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ # Extract the sender's address and find them in the user database
+ sender = msgdata.get('original_sender', msg.get_sender())
+ member = mlist.members.get_member(sender)
+ if member is None or not member.acknowledge_posts:
+ # Either the sender is not a member, in which case we can't know
+ # whether they want an acknowlegment or not, or they are a member
+ # who definitely does not want an acknowlegment.
+ return
+ # Okay, they are a member that wants an acknowledgment of their post.
+ # Give them their original subject. BAW: do we want to use the
+ # decoded header?
+ original_subject = msgdata.get(
+ 'origsubj', msg.get('subject', _('(no subject)')))
+ # Get the user's preferred language.
+ lang = msgdata.get('lang', member.preferred_language)
+ # Now get the acknowledgement template.
+ realname = mlist.real_name
+ text = Utils.maketext(
+ 'postack.txt',
+ {'subject' : Utils.oneline(original_subject,
+ Utils.GetCharSet(lang)),
+ 'listname' : realname,
+ 'listinfo_url': mlist.script_url('listinfo'),
+ 'optionsurl' : member.options_url,
+ }, lang=lang, mlist=mlist, raw=True)
+ # Craft the outgoing message, with all headers and attributes
+ # necessary for general delivery. Then enqueue it to the outgoing
+ # queue.
+ subject = _('$realname post acknowledgment')
+ usermsg = Message.UserNotification(sender, mlist.bounces_address,
+ subject, text, lang)
+ usermsg.send(mlist)
diff --git a/Mailman/pipeline/after_delivery.py b/Mailman/pipeline/after_delivery.py
index 512b83249..759b64a78 100644
--- a/Mailman/pipeline/after_delivery.py
+++ b/Mailman/pipeline/after_delivery.py
@@ -15,15 +15,30 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-"""Perform some bookkeeping after a successful post.
+"""Perform some bookkeeping after a successful post."""
+
+__metaclass__ = type
+__all__ = ['AfterDelivery']
-This module must appear after the delivery module in the message pipeline.
-"""
import datetime
+from zope.interface import implements
+
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
+
-def process(mlist, msg, msgdata):
- mlist.last_post_time = datetime.datetime.now()
- mlist.post_id += 1
+class AfterDelivery:
+ """Perform some bookkeeping after a successful post."""
+
+ implements(IHandler)
+
+ name = 'after-delivery'
+ description = _('Perform some bookkeeping after a successful post.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHander`."""
+ mlist.last_post_time = datetime.datetime.now()
+ mlist.post_id += 1
diff --git a/Mailman/pipeline/avoid_duplicates.py b/Mailman/pipeline/avoid_duplicates.py
index 2c52a7781..78a65c8c8 100644
--- a/Mailman/pipeline/avoid_duplicates.py
+++ b/Mailman/pipeline/avoid_duplicates.py
@@ -23,71 +23,91 @@ has already received a copy, we either drop the message, add a duplicate
warning header, or pass it through, depending on the user's preferences.
"""
+__metaclass__ = type
+__all__ = ['AvoidDuplicates']
+
+
from email.Utils import getaddresses, formataddr
+from zope.interface import implements
+
from Mailman.configuration import config
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
+
COMMASPACE = ', '
-def process(mlist, msg, msgdata):
- recips = msgdata.get('recips')
- # Short circuit
- if not recips:
- return
- # Seed this set with addresses we don't care about dup avoiding.
- listaddrs = set((mlist.posting_address,
- mlist.bounces_address,
- mlist.owner_address,
- mlist.request_address))
- explicit_recips = listaddrs.copy()
- # Figure out the set of explicit recipients
- cc_addresses = {}
- for header in ('to', 'cc', 'resent-to', 'resent-cc'):
- addrs = getaddresses(msg.get_all(header, []))
- header_addresses = dict((addr, formataddr((name, addr)))
- for name, addr in addrs
- if addr)
- if header == 'cc':
- # Yes, it's possible that an address is mentioned in multiple CC
- # headers using different names. In that case, the last real name
- # will win, but that doesn't seem like such a big deal. Besides,
- # how else would you chose?
- cc_addresses.update(header_addresses)
- # Ignore the list addresses for purposes of dup avoidance.
- explicit_recips |= set(header_addresses)
- # Now strip out the list addresses
- explicit_recips -= listaddrs
- if not explicit_recips:
- # No one was explicitly addressed, so we can't do any dup collapsing
- return
- newrecips = set()
- for r in recips:
- # If this recipient is explicitly addressed...
- if r in explicit_recips:
- send_duplicate = True
- # If the member wants to receive duplicates, or if the recipient
- # is not a member at all, they will get a copy.
- # header.
- member = mlist.members.get_member(r)
- if member and not member.receive_list_copy:
- send_duplicate = False
- # We'll send a duplicate unless the user doesn't wish it. If
- # personalization is enabled, the add-dupe-header flag will add a
- # X-Mailman-Duplicate: yes header for this user's message.
- if send_duplicate:
- msgdata.setdefault('add-dup-header', set()).add(r)
+class AvoidDuplicates:
+ """If the user wishes it, do not send duplicates of the same message."""
+
+ implements(IHandler)
+
+ name = 'avoid-duplicates'
+ description = _('Suppress some duplicates of the same message.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ recips = msgdata.get('recips')
+ # Short circuit
+ if not recips:
+ return
+ # Seed this set with addresses we don't care about dup avoiding.
+ listaddrs = set((mlist.posting_address,
+ mlist.bounces_address,
+ mlist.owner_address,
+ mlist.request_address))
+ explicit_recips = listaddrs.copy()
+ # Figure out the set of explicit recipients.
+ cc_addresses = {}
+ for header in ('to', 'cc', 'resent-to', 'resent-cc'):
+ addrs = getaddresses(msg.get_all(header, []))
+ header_addresses = dict((addr, formataddr((name, addr)))
+ for name, addr in addrs
+ if addr)
+ if header == 'cc':
+ # Yes, it's possible that an address is mentioned in multiple
+ # CC headers using different names. In that case, the last
+ # real name will win, but that doesn't seem like such a big
+ # deal. Besides, how else would you chose?
+ cc_addresses.update(header_addresses)
+ # Ignore the list addresses for purposes of dup avoidance.
+ explicit_recips |= set(header_addresses)
+ # Now strip out the list addresses.
+ explicit_recips -= listaddrs
+ if not explicit_recips:
+ # No one was explicitly addressed, so we can't do any dup
+ # collapsing
+ return
+ newrecips = set()
+ for r in recips:
+ # If this recipient is explicitly addressed...
+ if r in explicit_recips:
+ send_duplicate = True
+ # If the member wants to receive duplicates, or if the
+ # recipient is not a member at all, they will get a copy.
+ # header.
+ member = mlist.members.get_member(r)
+ if member and not member.receive_list_copy:
+ send_duplicate = False
+ # We'll send a duplicate unless the user doesn't wish it. If
+ # personalization is enabled, the add-dupe-header flag will
+ # add a X-Mailman-Duplicate: yes header for this user's
+ # message.
+ if send_duplicate:
+ msgdata.setdefault('add-dup-header', set()).add(r)
+ newrecips.add(r)
+ elif r in cc_addresses:
+ del cc_addresses[r]
+ else:
+ # Otherwise, this is the first time they've been in the recips
+ # list. Add them to the newrecips list and flag them as
+ # having received this message.
newrecips.add(r)
- elif r in cc_addresses:
- del cc_addresses[r]
- else:
- # Otherwise, this is the first time they've been in the recips
- # list. Add them to the newrecips list and flag them as having
- # received this message.
- newrecips.add(r)
- # Set the new list of recipients. XXX recips should always be a set.
- msgdata['recips'] = list(newrecips)
- # RFC 2822 specifies zero or one CC header
- if cc_addresses:
- del msg['cc']
- msg['CC'] = COMMASPACE.join(cc_addresses.values())
+ # Set the new list of recipients. XXX recips should always be a set.
+ msgdata['recips'] = list(newrecips)
+ # RFC 2822 specifies zero or one CC header
+ if cc_addresses:
+ del msg['cc']
+ msg['CC'] = COMMASPACE.join(cc_addresses.values())
diff --git a/Mailman/pipeline/calculate_recipients.py b/Mailman/pipeline/calculate_recipients.py
index 5cc552c25..f825e2d62 100644
--- a/Mailman/pipeline/calculate_recipients.py
+++ b/Mailman/pipeline/calculate_recipients.py
@@ -23,62 +23,78 @@ on the `recips' attribute of the message. This attribute is used by the
SendmailDeliver and BulkDeliver modules.
"""
+__metaclass__ = type
+__all__ = ['CalculateRecipients']
+
+from zope.interface import implements
+
from Mailman import Errors
from Mailman import Message
from Mailman import Utils
from Mailman.configuration import config
from Mailman.i18n import _
-from Mailman.interfaces import DeliveryStatus
+from Mailman.interfaces import DeliveryStatus, IHandler
-def process(mlist, msg, msgdata):
- # Short circuit if we've already calculated the recipients list,
- # regardless of whether the list is empty or not.
- if 'recips' in msgdata:
- return
- # Should the original sender should be included in the recipients list?
- include_sender = True
- sender = msg.get_sender()
- member = mlist.members.get_member(sender)
- if member and not member.receive_own_postings:
- include_sender = False
- # Support for urgent messages, which bypasses digests and disabled
- # delivery and forces an immediate delivery to all members Right Now. We
- # are specifically /not/ allowing the site admins password to work here
- # because we want to discourage the practice of sending the site admin
- # password through email in the clear. (see also Approve.py)
- missing = []
- password = msg.get('urgent', missing)
- if password is not missing:
- if mlist.Authenticate((config.AuthListModerator,
- config.AuthListAdmin),
- password):
- recips = mlist.getMemberCPAddresses(mlist.getRegularMemberKeys() +
- mlist.getDigestMemberKeys())
- msgdata['recips'] = recips
+class CalculateRecipients:
+ """Calculate the regular (i.e. non-digest) recipients of the message."""
+
+ implements(IHandler)
+
+ name = 'calculate-recipients'
+ description = _('Calculate the regular recipients of the message.')
+
+ def process(self, mlist, msg, msgdata):
+ # Short circuit if we've already calculated the recipients list,
+ # regardless of whether the list is empty or not.
+ if 'recips' in msgdata:
return
- else:
- # Bad Urgent: password, so reject it instead of passing it on. I
- # think it's better that the sender know they screwed up than to
- # deliver it normally.
- realname = mlist.real_name
- text = _("""\
+ # Should the original sender should be included in the recipients list?
+ include_sender = True
+ sender = msg.get_sender()
+ member = mlist.members.get_member(sender)
+ if member and not member.receive_own_postings:
+ include_sender = False
+ # Support for urgent messages, which bypasses digests and disabled
+ # delivery and forces an immediate delivery to all members Right Now.
+ # We are specifically /not/ allowing the site admins password to work
+ # here because we want to discourage the practice of sending the site
+ # admin password through email in the clear. (see also Approve.py)
+ #
+ # XXX This is broken.
+ missing = object()
+ password = msg.get('urgent', missing)
+ if password is not missing:
+ if mlist.Authenticate((config.AuthListModerator,
+ config.AuthListAdmin),
+ password):
+ recips = mlist.getMemberCPAddresses(
+ mlist.getRegularMemberKeys() +
+ mlist.getDigestMemberKeys())
+ msgdata['recips'] = recips
+ return
+ else:
+ # Bad Urgent: password, so reject it instead of passing it on.
+ # I think it's better that the sender know they screwed up
+ # than to deliver it normally.
+ realname = mlist.real_name
+ text = _("""\
Your urgent message to the %(realname)s mailing list was not authorized for
delivery. The original message as received by Mailman is attached.
""")
- raise Errors.RejectMessage, Utils.wrap(text)
- # Calculate the regular recipients of the message
- recips = set(member.address.address
- for member in mlist.regular_members.members
- if member.delivery_status == DeliveryStatus.enabled)
- # Remove the sender if they don't want to receive their own posts
- if not include_sender and member.address.address in recips:
- recips.remove(member.address.address)
- # Handle topic classifications
- do_topic_filters(mlist, msg, msgdata, recips)
- # Bookkeeping
- msgdata['recips'] = recips
+ raise Errors.RejectMessage, Utils.wrap(text)
+ # Calculate the regular recipients of the message
+ recips = set(member.address.address
+ for member in mlist.regular_members.members
+ if member.delivery_status == DeliveryStatus.enabled)
+ # Remove the sender if they don't want to receive their own posts
+ if not include_sender and member.address.address in recips:
+ recips.remove(member.address.address)
+ # Handle topic classifications
+ do_topic_filters(mlist, msg, msgdata, recips)
+ # Bookkeeping
+ msgdata['recips'] = recips
diff --git a/Mailman/pipeline/cleanse.py b/Mailman/pipeline/cleanse.py
index 24e1af340..dad6e9127 100644
--- a/Mailman/pipeline/cleanse.py
+++ b/Mailman/pipeline/cleanse.py
@@ -17,42 +17,55 @@
"""Cleanse certain headers from all messages."""
+__metaclass__ = type
+__all__ = ['Cleanse']
+
+
import logging
from email.Utils import formataddr
+from zope.interface import implements
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
from Mailman.pipeline.cook_headers import uheader
+
log = logging.getLogger('mailman.post')
-def process(mlist, msg, msgdata):
- # Always remove this header from any outgoing messages. Be sure to do
- # this after the information on the header is actually used, but before a
- # permanent record of the header is saved.
- del msg['approved']
- # Remove this one too.
- del msg['approve']
- # Also remove this header since it can contain a password
- del msg['urgent']
- # We remove other headers from anonymous lists
- if mlist.anonymous_list:
- log.info('post to %s from %s anonymized',
- mlist.fqdn_listname, msg.get('from'))
- del msg['from']
- del msg['reply-to']
- del msg['sender']
- # Hotmail sets this one
- del msg['x-originating-email']
- i18ndesc = str(uheader(mlist, mlist.description, 'From'))
- msg['From'] = formataddr((i18ndesc, mlist.posting_address))
- msg['Reply-To'] = mlist.posting_address
- # Some headers can be used to fish for membership
- del msg['return-receipt-to']
- del msg['disposition-notification-to']
- del msg['x-confirm-reading-to']
- # Pegasus mail uses this one... sigh
- del msg['x-pmrqc']
- # Don't let this header be spoofed. See RFC 5064.
- del msg['archived-at']
+class Cleanse:
+ """Cleanse certain headers from all messages."""
+
+ implements(IHandler)
+
+ name = 'cleanse'
+ description = _('Cleanse certain headers from all messages.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ # Remove headers that could contain passwords.
+ del msg['approved']
+ del msg['approve']
+ del msg['urgent']
+ # We remove other headers from anonymous lists.
+ if mlist.anonymous_list:
+ log.info('post to %s from %s anonymized',
+ mlist.fqdn_listname, msg.get('from'))
+ del msg['from']
+ del msg['reply-to']
+ del msg['sender']
+ # Hotmail sets this one
+ del msg['x-originating-email']
+ i18ndesc = str(uheader(mlist, mlist.description, 'From'))
+ msg['From'] = formataddr((i18ndesc, mlist.posting_address))
+ msg['Reply-To'] = mlist.posting_address
+ # Some headers can be used to fish for membership.
+ del msg['return-receipt-to']
+ del msg['disposition-notification-to']
+ del msg['x-confirm-reading-to']
+ # Pegasus mail uses this one... sigh.
+ del msg['x-pmrqc']
+ # Don't let this header be spoofed. See RFC 5064.
+ del msg['archived-at']
diff --git a/Mailman/pipeline/cleanse_dkim.py b/Mailman/pipeline/cleanse_dkim.py
index f35350acc..3deabe22f 100644
--- a/Mailman/pipeline/cleanse_dkim.py
+++ b/Mailman/pipeline/cleanse_dkim.py
@@ -15,7 +15,7 @@
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
-"""Remove any 'DomainKeys' (or similar) header lines.
+"""Remove any 'DomainKeys' (or similar) headers.
The values contained in these header lines are intended to be used by the
recipient to detect forgery or tampering in transit, and the modifications
@@ -25,12 +25,29 @@ and it will also give the MTA the opportunity to regenerate valid keys
originating at the Mailman server for the outgoing message.
"""
+__metaclass__ = type
+__all__ = ['CleanseDKIM']
+
+
+from zope.interface import implements
+
from Mailman.configuration import config
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
-def process(mlist, msg, msgdata):
- if config.REMOVE_DKIM_HEADERS:
- del msg['domainkey-signature']
- del msg['dkim-signature']
- del msg['authentication-results']
+class CleanseDKIM:
+ """Remove DomainKeys headers."""
+
+ implements(IHandler)
+
+ name = 'cleanse-dkim'
+ description = _('Remove DomainKeys headers.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ if config.REMOVE_DKIM_HEADERS:
+ del msg['domainkey-signature']
+ del msg['dkim-signature']
+ del msg['authentication-results']
diff --git a/Mailman/pipeline/cook_headers.py b/Mailman/pipeline/cook_headers.py
index 4797de62b..0a63a727e 100644
--- a/Mailman/pipeline/cook_headers.py
+++ b/Mailman/pipeline/cook_headers.py
@@ -17,19 +17,24 @@
"""Cook a message's headers."""
+__metaclass__ = type
+__all__ = ['CookHeaders']
+
+
import re
from email.Charset import Charset
from email.Errors import HeaderParseError
from email.Header import Header, decode_header, make_header
from email.Utils import parseaddr, formataddr, getaddresses
+from zope.interface import implements
from Mailman import Utils
from Mailman import Version
from Mailman.app.archiving import get_archiver
from Mailman.configuration import config
from Mailman.i18n import _
-from Mailman.interfaces import Personalization, ReplyToMunging
+from Mailman.interfaces import IHandler, Personalization, ReplyToMunging
CONTINUATION = ',\n\t'
COMMASPACE = ', '
@@ -336,3 +341,17 @@ def ch_oneline(headerstr):
except (LookupError, UnicodeError, ValueError, HeaderParseError):
# possibly charset problem. return with undecoded string in one line.
return ''.join(headerstr.splitlines()), 'us-ascii'
+
+
+
+class CookHeaders:
+ """Modify message headers."""
+
+ implements(IHandler)
+
+ name = 'cook-headers'
+ description = _('Modify message headers.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/decorate.py b/Mailman/pipeline/decorate.py
index f4b6551e3..c7ecb431a 100644
--- a/Mailman/pipeline/decorate.py
+++ b/Mailman/pipeline/decorate.py
@@ -17,17 +17,24 @@
"""Decorate a message by sticking the header and footer around it."""
+__metaclass__ = type
+__all__ = ['Decorate']
+
+
import re
import logging
from email.MIMEText import MIMEText
from string import Template
+from zope.interface import implements
from Mailman import Errors
from Mailman import Utils
from Mailman.Message import Message
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.interfaces import IHandler
+
log = logging.getLogger('mailman.error')
@@ -205,3 +212,17 @@ def decorate(mlist, template, extradict=None):
text = Template(template).safe_substitute(d)
# Turn any \r\n line endings into just \n
return re.sub(r' *\r?\n', r'\n', text)
+
+
+
+class Decorate:
+ """Decorate a message with headers and footers."""
+
+ implements(IHandler)
+
+ name = 'decorate'
+ description = _('Decorate a message with headers and footers.')
+
+ def process(self, mlist, msg, msgdata):
+ "See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/docs/acknowledge.txt b/Mailman/pipeline/docs/acknowledge.txt
index 615697e70..3dd92854f 100644
--- a/Mailman/pipeline/docs/acknowledge.txt
+++ b/Mailman/pipeline/docs/acknowledge.txt
@@ -5,7 +5,7 @@ When a user posts a message to a mailing list, and that user has chosen to
receive acknowledgments of their postings, Mailman will sent them such an
acknowledgment.
- >>> from Mailman.pipeline.acknowledge import process
+ >>> handler = config.handlers['acknowledge']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> mlist.real_name = u'XTest'
>>> mlist.preferred_language = u'en'
@@ -39,7 +39,7 @@ Non-members can't get acknowledgments of their posts to the mailing list.
... From: bperson@example.com
...
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> virginq.files
[]
@@ -50,7 +50,8 @@ person is also not a member, no acknowledgment will be sent either.
... From: bperson@example.com
...
... """)
- >>> process(mlist, msg, dict(original_sender=u'cperson@example.com'))
+ >>> handler.process(mlist, msg,
+ ... dict(original_sender=u'cperson@example.com'))
>>> virginq.files
[]
@@ -64,7 +65,7 @@ Unless the user has requested acknowledgments, they will not get one.
... From: aperson@example.com
...
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> virginq.files
[]
@@ -77,7 +78,8 @@ will be sent.
>>> address_2.subscribe(mlist, MemberRole.member)
<Member: dperson@example.com on _xtest@example.com as MemberRole.member>
- >>> process(mlist, msg, dict(original_sender=u'dperson@example.com'))
+ >>> handler.process(mlist, msg,
+ ... dict(original_sender=u'dperson@example.com'))
>>> virginq.files
[]
@@ -97,7 +99,7 @@ The receipt will include the original message's subject in the response body,
... Subject: Something witty and insightful
...
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> len(virginq.files)
1
>>> qmsg, qdata = virginq.dequeue(virginq.files[0])
@@ -131,7 +133,7 @@ If there is no subject, then the receipt will use a generic message.
... From: aperson@example.com
...
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> len(virginq.files)
1
>>> qmsg, qdata = virginq.dequeue(virginq.files[0])
diff --git a/Mailman/pipeline/docs/after-delivery.txt b/Mailman/pipeline/docs/after-delivery.txt
index fa4c6914d..138cc5bc4 100644
--- a/Mailman/pipeline/docs/after-delivery.txt
+++ b/Mailman/pipeline/docs/after-delivery.txt
@@ -6,8 +6,8 @@ by the rest of the handlers in the incoming queue pipeline, a couple of
bookkeeping pieces of information are updated.
>>> import datetime
- >>> from Mailman.pipeline.after_delivery import process
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['after-delivery']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> post_time = datetime.datetime.now() - datetime.timedelta(minutes=10)
>>> mlist.last_post_time = post_time
@@ -21,7 +21,7 @@ attributes.
...
... Something interesting.
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> mlist.last_post_time > post_time
True
>>> mlist.post_id
diff --git a/Mailman/pipeline/docs/archives.txt b/Mailman/pipeline/docs/archives.txt
index f902ab59e..41fc49a00 100644
--- a/Mailman/pipeline/docs/archives.txt
+++ b/Mailman/pipeline/docs/archives.txt
@@ -7,9 +7,9 @@ delivery processes while messages are archived. This also allows external
archivers to work in a separate process from the main Mailman delivery
processes.
- >>> from Mailman.pipeline.to_archive import process
>>> from Mailman.queue import Switchboard
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['to-archive']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> mlist.preferred_language = u'en'
>>> switchboard = Switchboard(config.ARCHQUEUE_DIR)
@@ -34,7 +34,7 @@ For example, no digests should ever get archived.
...
... A message of great import.
... """)
- >>> process(mlist, msg, dict(isdigest=True))
+ >>> handler.process(mlist, msg, dict(isdigest=True))
>>> switchboard.files
[]
@@ -42,7 +42,7 @@ If the mailing list is not configured to archive, then even regular deliveries
won't be archived.
>>> mlist.archive = False
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> switchboard.files
[]
@@ -58,7 +58,7 @@ archived. Confusingly, this header's value is actually ignored.
...
... A message of great import.
... """)
- >>> process(mlist, msg, dict(isdigest=True))
+ >>> handler.process(mlist, msg, dict(isdigest=True))
>>> switchboard.files
[]
@@ -70,7 +70,7 @@ Even a 'no' value will stop the archiving of the message.
...
... A message of great import.
... """)
- >>> process(mlist, msg, dict(isdigest=True))
+ >>> handler.process(mlist, msg, dict(isdigest=True))
>>> switchboard.files
[]
@@ -83,7 +83,7 @@ header's case folded value must be 'no' in order to prevent archiving.
...
... A message of great import.
... """)
- >>> process(mlist, msg, dict(isdigest=True))
+ >>> handler.process(mlist, msg, dict(isdigest=True))
>>> switchboard.files
[]
@@ -95,7 +95,7 @@ But if the value is 'yes', then the message will be archived.
...
... A message of great import.
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> len(switchboard.files)
1
>>> filebase = switchboard.files[0]
@@ -118,7 +118,7 @@ message will get archived.
...
... A message of great import.
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> len(switchboard.files)
1
>>> filebase = switchboard.files[0]
diff --git a/Mailman/pipeline/docs/avoid-duplicates.txt b/Mailman/pipeline/docs/avoid-duplicates.txt
index 5e38eaae8..2050e6443 100644
--- a/Mailman/pipeline/docs/avoid-duplicates.txt
+++ b/Mailman/pipeline/docs/avoid-duplicates.txt
@@ -6,8 +6,8 @@ reduce the reception of duplicate messages. It does this by removing certain
recipients from the list of recipients that earlier handler modules
(e.g. CalcRecips) calculates.
- >>> from Mailman.pipeline.avoid_duplicates import process
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['avoid-duplicates']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
Create some members we're going to use.
@@ -36,7 +36,7 @@ The module short-circuits if there are no recipients.
... Something
... """)
>>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> msgdata
{}
>>> print msg.as_string()
@@ -63,7 +63,7 @@ will get a list copy.
... Something of great import.
... """)
>>> msgdata = recips.copy()
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'aperson@example.com', u'bperson@example.com']
>>> print msg.as_string()
@@ -81,7 +81,7 @@ If they're mentioned on the CC line, they won't get a list copy.
... Something of great import.
... """)
>>> msgdata = recips.copy()
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'bperson@example.com']
>>> print msg.as_string()
@@ -101,7 +101,7 @@ But if they're mentioned on the CC line and have receive_list_copy set to True
... Something of great import.
... """)
>>> msgdata = recips.copy()
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'aperson@example.com', u'bperson@example.com']
>>> print msg.as_string()
@@ -120,7 +120,7 @@ Other headers checked for recipients include the To...
... Something of great import.
... """)
>>> msgdata = recips.copy()
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'bperson@example.com']
>>> print msg.as_string()
@@ -139,7 +139,7 @@ Other headers checked for recipients include the To...
... Something of great import.
... """)
>>> msgdata = recips.copy()
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'bperson@example.com']
>>> print msg.as_string()
@@ -158,7 +158,7 @@ Other headers checked for recipients include the To...
... Something of great import.
... """)
>>> msgdata = recips.copy()
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'bperson@example.com']
>>> print msg.as_string()
diff --git a/Mailman/pipeline/docs/calc-recips.txt b/Mailman/pipeline/docs/calc-recips.txt
index f2a9b3113..193206a64 100644
--- a/Mailman/pipeline/docs/calc-recips.txt
+++ b/Mailman/pipeline/docs/calc-recips.txt
@@ -5,8 +5,8 @@ Every message that makes it through to the list membership gets sent to a set
of recipient addresses. These addresses are calculated by one of the handler
modules and depends on a host of factors.
- >>> from Mailman.pipeline.calculate_recipients import process
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['calculate-recipients']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
Recipients are calculate from the list members, so add a bunch of members to
@@ -52,7 +52,7 @@ but not all of the recipients.
... """)
>>> recips = set((u'qperson@example.com', u'zperson@example.com'))
>>> msgdata = dict(recips=recips)
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'qperson@example.com', u'zperson@example.com']
@@ -64,7 +64,7 @@ Regular delivery recipients are those people who get messages from the list as
soon as they are posted. In other words, these folks are not digest members.
>>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'aperson@example.com', u'bperson@example.com', u'cperson@example.com']
@@ -77,7 +77,7 @@ Members can elect not to receive a list copy of their own postings.
... Something of great import.
... """)
>>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[u'aperson@example.com', u'bperson@example.com']
diff --git a/Mailman/pipeline/docs/cleanse.txt b/Mailman/pipeline/docs/cleanse.txt
index 09cd49f08..698557120 100644
--- a/Mailman/pipeline/docs/cleanse.txt
+++ b/Mailman/pipeline/docs/cleanse.txt
@@ -5,8 +5,8 @@ All messages posted to a list get their headers cleansed. Some headers are
related to additional permissions that can be granted to the message and other
headers can be used to fish for membership.
- >>> from Mailman.pipeline.cleanse import process
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['cleanse']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
Headers such as Approved, Approve, and Urgent are used to grant special
@@ -25,7 +25,7 @@ headers contain passwords, they must be removed from any posted message.
...
... Blah blah blah
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> print msg.as_string()
From: aperson@example.com
Subject: A message of great import
@@ -51,7 +51,7 @@ Pegasus mail. I don't remember what program uses X-Confirm-Reading-To though
...
... How are you doing?
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> print msg.as_string()
From: bperson@example.com
Reply-To: bperson@example.org
@@ -85,7 +85,7 @@ Hotmail apparently sets X-Originating-Email.
...
... How are you doing?
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> print msg.as_string()
Subject: a message to you
From: A Test Mailing List <_xtest@example.com>
diff --git a/Mailman/pipeline/docs/file-recips.txt b/Mailman/pipeline/docs/file-recips.txt
index a5824ce70..8b1ab8024 100644
--- a/Mailman/pipeline/docs/file-recips.txt
+++ b/Mailman/pipeline/docs/file-recips.txt
@@ -5,8 +5,8 @@ Mailman can calculate the recipients for a message from a Sendmail-style
include file. This file must be called members.txt and it must live in the
list's data directory.
- >>> from Mailman.pipeline.file_recipients import process
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['file-recipients']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
@@ -22,7 +22,7 @@ returns.
... A message.
... """)
>>> msgdata = {'recips': 7}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> print msg.as_string()
From: aperson@example.com
<BLANKLINE>
@@ -47,7 +47,7 @@ empty.
IOError: [Errno ...]
No such file or directory: u'.../_xtest@example.com/members.txt'
>>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
[]
@@ -70,7 +70,7 @@ addresses are returned as the set of recipients.
... fp.close()
>>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
['bperson@example.com', 'cperson@example.com', 'dperson@example.com',
'eperson@example.com', 'fperson@example.com', 'gperson@example.com']
@@ -91,7 +91,7 @@ in the recipients list.
... A message.
... """)
>>> msgdata = {}
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> sorted(msgdata['recips'])
['bperson@example.com', 'dperson@example.com',
'eperson@example.com', 'fperson@example.com', 'gperson@example.com']
diff --git a/Mailman/pipeline/docs/nntp.txt b/Mailman/pipeline/docs/nntp.txt
index 0a8fe625d..eec95b7c2 100644
--- a/Mailman/pipeline/docs/nntp.txt
+++ b/Mailman/pipeline/docs/nntp.txt
@@ -5,9 +5,9 @@ Mailman has an NNTP gateway, whereby messages posted to the mailing list can
be forwarded onto an NNTP newsgroup. Typically this means Usenet, but since
NNTP is to Usenet as IP is to the web, it's more general than that.
- >>> from Mailman.pipeline.to_usenet import process
>>> from Mailman.queue import Switchboard
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['to-usenet']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> mlist.preferred_language = u'en'
>>> switchboard = Switchboard(config.NEWSQUEUE_DIR)
@@ -26,7 +26,7 @@ the newsgroup. The feature could be disabled, as is the default.
...
... Something of great import.
... """)
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> switchboard.files
[]
@@ -34,13 +34,13 @@ Even if enabled, messages that came from the newsgroup are never gated back to
the newsgroup.
>>> mlist.gateway_to_news = True
- >>> process(mlist, msg, {'fromusenet': True})
+ >>> handler.process(mlist, msg, {'fromusenet': True})
>>> switchboard.files
[]
Neither are digests ever gated to the newsgroup.
- >>> process(mlist, msg, {'isdigest': True})
+ >>> handler.process(mlist, msg, {'isdigest': True})
>>> switchboard.files
[]
@@ -50,7 +50,7 @@ messages are gated to.
>>> mlist.linked_newsgroup = u'comp.lang.thing'
>>> mlist.nntp_host = u'news.example.com'
- >>> process(mlist, msg, {})
+ >>> handler.process(mlist, msg, {})
>>> len(switchboard.files)
1
>>> filebase = switchboard.files[0]
diff --git a/Mailman/pipeline/file_recipients.py b/Mailman/pipeline/file_recipients.py
index 0bee969f6..dfae732f0 100644
--- a/Mailman/pipeline/file_recipients.py
+++ b/Mailman/pipeline/file_recipients.py
@@ -19,28 +19,46 @@
from __future__ import with_statement
+__metaclass__ = type
+__all__ = ['FileRecipients']
+
+
import os
import errno
+from zope.interface import implements
+
from Mailman import Errors
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
-def process(mlist, msg, msgdata):
- if 'recips' in msgdata:
- return
- filename = os.path.join(mlist.full_path, 'members.txt')
- try:
- with open(filename) as fp:
- addrs = set(line.strip() for line in fp)
- except IOError, e:
- if e.errno <> errno.ENOENT:
- raise
- msgdata['recips'] = set()
- return
- # If the sender is a member of the list, remove them from the file recips.
- sender = msg.get_sender()
- member = mlist.members.get_member(sender)
- if member is not None:
- addrs.discard(member.address.address)
- msgdata['recips'] = addrs
+class FileRecipients:
+ """Get the normal delivery recipients from an include file."""
+
+ implements(IHandler)
+
+ name = 'file-recipients'
+ description = _('Get the normal delivery recipients from an include file.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ if 'recips' in msgdata:
+ return
+ filename = os.path.join(mlist.full_path, 'members.txt')
+ try:
+ with open(filename) as fp:
+ addrs = set(line.strip() for line in fp)
+ except IOError, e:
+ if e.errno <> errno.ENOENT:
+ raise
+ msgdata['recips'] = set()
+ return
+ # If the sender is a member of the list, remove them from the file
+ # recipients.
+ sender = msg.get_sender()
+ member = mlist.members.get_member(sender)
+ if member is not None:
+ addrs.discard(member.address.address)
+ msgdata['recips'] = addrs
diff --git a/Mailman/pipeline/mime_delete.py b/Mailman/pipeline/mime_delete.py
index f8a9f4ddd..89037039b 100644
--- a/Mailman/pipeline/mime_delete.py
+++ b/Mailman/pipeline/mime_delete.py
@@ -24,6 +24,10 @@ wrapping only single sections after other processing are replaced by their
contents.
"""
+__metaclass__ = type
+__all__ = ['MIMEDelete']
+
+
import os
import errno
import logging
@@ -31,6 +35,7 @@ import tempfile
from email.Iterators import typed_subpart_iterator
from os.path import splitext
+from zope.interface import implements
from Mailman import Errors
from Mailman.Message import UserNotification
@@ -38,6 +43,7 @@ from Mailman.Utils import oneline
from Mailman.Version import VERSION
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.interfaces import IHandler
from Mailman.queue import Switchboard
log = logging.getLogger('mailman.error')
@@ -259,3 +265,16 @@ def get_file_ext(m):
else:
fext = ''
return fext
+
+
+
+class MIMEDelete:
+ """Filter the MIME content of messages."""
+
+ implements(IHandler)
+
+ name = 'mime-delete'
+ description = _('Filter the MIME content of messages.')
+
+ def process(self, mlist, msg, msgdata):
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/moderate.py b/Mailman/pipeline/moderate.py
index fed77e6f9..1f5fe6b99 100644
--- a/Mailman/pipeline/moderate.py
+++ b/Mailman/pipeline/moderate.py
@@ -25,22 +25,21 @@ from email.MIMEText import MIMEText
from Mailman import Errors
from Mailman import Message
from Mailman import Utils
-from Mailman.Handlers import Hold
from Mailman.configuration import config
from Mailman.i18n import _
-class ModeratedMemberPost(Hold.ModeratedPost):
- # BAW: I wanted to use the reason below to differentiate between this
- # situation and normal ModeratedPost reasons. Greg Ward and Stonewall
- # Ballard thought the language was too harsh and mentioned offense taken
- # by some list members. I'd still like this class's reason to be
- # different than the base class's reason, but we'll use this until someone
- # can come up with something more clever but inoffensive.
- #
- # reason = _('Posts by member are currently quarantined for moderation')
- pass
+## class ModeratedMemberPost(Hold.ModeratedPost):
+## # BAW: I wanted to use the reason below to differentiate between this
+## # situation and normal ModeratedPost reasons. Greg Ward and Stonewall
+## # Ballard thought the language was too harsh and mentioned offense taken
+## # by some list members. I'd still like this class's reason to be
+## # different than the base class's reason, but we'll use this until someone
+## # can come up with something more clever but inoffensive.
+## #
+## # reason = _('Posts by member are currently quarantined for moderation')
+## pass
diff --git a/Mailman/pipeline/replybot.py b/Mailman/pipeline/replybot.py
index ec48097af..a75f79647 100644
--- a/Mailman/pipeline/replybot.py
+++ b/Mailman/pipeline/replybot.py
@@ -17,15 +17,21 @@
"""Handler for auto-responses."""
+__metaclass__ = type
+__all__ = ['Replybot']
+
+
import time
import logging
import datetime
from string import Template
+from zope.interface import implements
from Mailman import Message
from Mailman import Utils
from Mailman.i18n import _
+from Mailman.interfaces import IHandler
log = logging.getLogger('mailman.error')
@@ -109,3 +115,17 @@ def process(mlist, msg, msgdata):
mlist.request_responses[sender] = quiet_until
else:
mlist.postings_responses[sender] = quiet_until
+
+
+
+class Replybot:
+ """Send automatic responses."""
+
+ implements(IHandler)
+
+ name = 'replybot'
+ description = _('Send automatic responses.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/scrubber.py b/Mailman/pipeline/scrubber.py
index fb1b6e602..5552a60fc 100644
--- a/Mailman/pipeline/scrubber.py
+++ b/Mailman/pipeline/scrubber.py
@@ -19,6 +19,10 @@
from __future__ import with_statement
+__metaclass__ = type
+__all__ = ['Scrubber']
+
+
import os
import re
import sha
@@ -32,12 +36,14 @@ from email.generator import Generator
from email.utils import make_msgid, parsedate
from locknix.lockfile import Lock
from mimetypes import guess_all_extensions
+from zope.interface import implements
from Mailman import Utils
from Mailman.Errors import DiscardMessage
from Mailman.app.archiving import get_archiver
from Mailman.configuration import config
from Mailman.i18n import _
+from Mailman.interfaces import IHandler
# Path characters for common platforms
pre = re.compile(r'[/\\:]')
@@ -498,3 +504,17 @@ def save_attachment(mlist, msg, dir, filter_html=True):
# Bracket the URL instead.
url = '<' + baseurl + '%s/%s%s%s>' % (dir, filebase, extra, ext)
return url
+
+
+
+class Scrubber:
+ """Cleanse a message for archiving."""
+
+ implements(IHandler)
+
+ name = 'scrubber'
+ description = _('Cleanse a message for archiving.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/smtp_direct.py b/Mailman/pipeline/smtp_direct.py
index e3d4e7b8c..96f3449d5 100644
--- a/Mailman/pipeline/smtp_direct.py
+++ b/Mailman/pipeline/smtp_direct.py
@@ -26,6 +26,10 @@ Note: This file only handles single threaded delivery. See SMTPThreaded.py
for a threaded implementation.
"""
+__metaclass__ = type
+__all__ = ['SMTPDirect']
+
+
import copy
import time
import email
@@ -36,13 +40,15 @@ import smtplib
from email.Charset import Charset
from email.Header import Header
from email.Utils import formataddr
+from zope.interface import implements
from Mailman import Errors
from Mailman import Utils
-from Mailman.Handlers import Decorate
from Mailman.SafeDict import MsgSafeDict
from Mailman.configuration import config
-from Mailman.interfaces import Personalization
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler, Personalization
+
DOT = '.'
@@ -130,7 +136,8 @@ def process(mlist, msg, msgdata):
if deliveryfunc is None:
# Be sure never to decorate the message more than once!
if not msgdata.get('decorated'):
- Decorate.process(mlist, msg, msgdata)
+ handler = config.handlers['decorate']
+ handler.process(mlist, msg, msgdata)
msgdata['decorated'] = True
deliveryfunc = bulkdeliver
refused = {}
@@ -273,6 +280,7 @@ def chunkify(recips, chunksize):
def verpdeliver(mlist, msg, msgdata, envsender, failures, conn):
+ handler = config.handlers['decorate']
for recip in msgdata['recips']:
# We now need to stitch together the message with its header and
# footer. If we're VERPIng, we have to calculate the envelope sender
@@ -285,7 +293,7 @@ def verpdeliver(mlist, msg, msgdata, envsender, failures, conn):
msgdata['recips'] = [recip]
# Make a copy of the message and decorate + delivery that
msgcopy = copy.deepcopy(msg)
- Decorate.process(mlist, msgcopy, msgdata)
+ handler.process(mlist, msgcopy, msgdata)
# Calculate the envelope sender, which we may be VERPing
if msgdata.get('verp'):
bmailbox, bdomain = Utils.ParseEmail(envsender)
@@ -387,3 +395,17 @@ def bulkdeliver(mlist, msg, msgdata, envsender, failures, conn):
for r in recips:
refused[r] = (-1, error)
failures.update(refused)
+
+
+
+class SMTPDirect:
+ """SMTP delivery."""
+
+ implements(IHandler)
+
+ name = 'smtp-direct'
+ description = _('SMTP delivery.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/tagger.py b/Mailman/pipeline/tagger.py
index 0a87d8407..cde8547df 100644
--- a/Mailman/pipeline/tagger.py
+++ b/Mailman/pipeline/tagger.py
@@ -17,12 +17,22 @@
"""Extract topics from the original mail message."""
+__metaclass__ = type
+__all__ = ['Tagger']
+
+
import re
import email
import email.Errors
import email.Iterators
import email.Parser
+from zope.interface import implements
+
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
+
+
OR = '|'
CRNL = '\r\n'
EMPTYSTRING = ''
@@ -155,3 +165,17 @@ class _ForgivingParser(email.Parser.HeaderParser):
# Make sure we retain the last header
if lastheader:
container[lastheader] = NLTAB.join(lastvalue)
+
+
+
+class Tagger:
+ """Tag messages with topic matches."""
+
+ implements(IHandler)
+
+ name = 'tagger'
+ description = _('Tag messages with topic matches.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/to_archive.py b/Mailman/pipeline/to_archive.py
index e67513c84..f85488ded 100644
--- a/Mailman/pipeline/to_archive.py
+++ b/Mailman/pipeline/to_archive.py
@@ -17,21 +17,38 @@
"""Add the message to the archives."""
+__metaclass__ = type
+__all__ = ['ToArchive']
+
+
+from zope.interface import implements
+
from Mailman.configuration import config
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
from Mailman.queue import Switchboard
-def process(mlist, msg, msgdata):
- # short circuits
- if msgdata.get('isdigest') or not mlist.archive:
- return
- # Common practice seems to favor "X-No-Archive: yes". No other value for
- # this header seems to make sense, so we'll just test for it's presence.
- # I'm keeping "X-Archive: no" for backwards compatibility.
- if 'x-no-archive' in msg or msg.get('x-archive', '').lower() == 'no':
- return
- # Send the message to the archiver queue
- archq = Switchboard(config.ARCHQUEUE_DIR)
- # Send the message to the queue
- archq.enqueue(msg, msgdata)
+class ToArchive:
+ """Add the message to the archives."""
+
+ implements(IHandler)
+
+ name = 'to-archive'
+ description = _('Add the message to the archives.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ # Short circuits.
+ if msgdata.get('isdigest') or not mlist.archive:
+ return
+ # Common practice seems to favor "X-No-Archive: yes". No other value
+ # for this header seems to make sense, so we'll just test for it's
+ # presence. I'm keeping "X-Archive: no" for backwards compatibility.
+ if 'x-no-archive' in msg or msg.get('x-archive', '').lower() == 'no':
+ return
+ # Send the message to the archiver queue
+ archq = Switchboard(config.ARCHQUEUE_DIR)
+ # Send the message to the queue
+ archq.enqueue(msg, msgdata)
diff --git a/Mailman/pipeline/to_digest.py b/Mailman/pipeline/to_digest.py
index f126ac3fe..3da5dda07 100644
--- a/Mailman/pipeline/to_digest.py
+++ b/Mailman/pipeline/to_digest.py
@@ -27,6 +27,10 @@
from __future__ import with_statement
+__metaclass__ = type
+__all__ = ['ToDigest']
+
+
import os
import re
import copy
@@ -42,6 +46,7 @@ from email.mime.message import MIMEMessage
from email.mime.text import MIMEText
from email.parser import Parser
from email.utils import formatdate, getaddresses, make_msgid
+from zope.interface import implements
from Mailman import Errors
from Mailman import Message
@@ -52,7 +57,7 @@ from Mailman.Mailbox import Mailbox
from Mailman.configuration import config
from Mailman.pipeline.decorate import decorate
from Mailman.pipeline.scrubber import process as scrubber
-from Mailman.interfaces import DeliveryMode, DeliveryStatus
+from Mailman.interfaces import DeliveryMode, DeliveryStatus, IHandler
from Mailman.queue import Switchboard
_ = i18n._
@@ -418,3 +423,17 @@ def send_i18n_digests(mlist, mboxfp):
recips=plainrecips,
listname=mlist.fqdn_listname,
isdigest=True)
+
+
+
+class ToDigest:
+ """Add the message to the digest, possibly sending it."""
+
+ implements(IHandler)
+
+ name = 'to-digest'
+ description = _('Add the message to the digest, possibly sending it.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ process(mlist, msg, msgdata)
diff --git a/Mailman/pipeline/to_outgoing.py b/Mailman/pipeline/to_outgoing.py
index 21ba8b0d5..81d66ce4b 100644
--- a/Mailman/pipeline/to_outgoing.py
+++ b/Mailman/pipeline/to_outgoing.py
@@ -22,36 +22,52 @@ posted to the list membership. Anything else that needs to go out to some
recipient should just be placed in the out queue directly.
"""
+__metaclass__ = type
+__all__ = ['ToOutgoing']
+
+
+from zope.interface import implements
+
from Mailman.configuration import config
-from Mailman.interfaces import Personalization
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler, Personalization
from Mailman.queue import Switchboard
-def process(mlist, msg, msgdata):
- interval = config.VERP_DELIVERY_INTERVAL
- # Should we VERP this message? If personalization is enabled for this
- # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it.
- # Also, if personalization is /not/ enabled, but VERP_DELIVERY_INTERVAL is
- # set (and we've hit this interval), then again, this message should be
- # VERPed. Otherwise, no.
- #
- # Note that the verp flag may already be set, e.g. by mailpasswds using
- # VERP_PASSWORD_REMINDERS. Preserve any existing verp flag.
- if 'verp' in msgdata:
- pass
- elif mlist.personalize <> Personalization.none:
- if config.VERP_PERSONALIZED_DELIVERIES:
+class ToOutgoing:
+ """Send the message to the outgoing queue."""
+
+ implements(IHandler)
+
+ name = 'to-outgoing'
+ description = _('Send the message to the outgoing queue.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ interval = config.VERP_DELIVERY_INTERVAL
+ # Should we VERP this message? If personalization is enabled for this
+ # list and VERP_PERSONALIZED_DELIVERIES is true, then yes we VERP it.
+ # Also, if personalization is /not/ enabled, but
+ # VERP_DELIVERY_INTERVAL is set (and we've hit this interval), then
+ # again, this message should be VERPed. Otherwise, no.
+ #
+ # Note that the verp flag may already be set, e.g. by mailpasswds
+ # using VERP_PASSWORD_REMINDERS. Preserve any existing verp flag.
+ if 'verp' in msgdata:
+ pass
+ elif mlist.personalize <> Personalization.none:
+ if config.VERP_PERSONALIZED_DELIVERIES:
+ msgdata['verp'] = True
+ elif interval == 0:
+ # Never VERP
+ pass
+ elif interval == 1:
+ # VERP every time
msgdata['verp'] = True
- elif interval == 0:
- # Never VERP
- pass
- elif interval == 1:
- # VERP every time
- msgdata['verp'] = True
- else:
- # VERP every `interval' number of times
- msgdata['verp'] = not (int(mlist.post_id) % interval)
- # And now drop the message in qfiles/out
- outq = Switchboard(config.OUTQUEUE_DIR)
- outq.enqueue(msg, msgdata, listname=mlist.fqdn_listname)
+ else:
+ # VERP every `interval' number of times
+ msgdata['verp'] = not (int(mlist.post_id) % interval)
+ # And now drop the message in qfiles/out
+ outq = Switchboard(config.OUTQUEUE_DIR)
+ outq.enqueue(msg, msgdata, listname=mlist.fqdn_listname)
diff --git a/Mailman/pipeline/to_usenet.py b/Mailman/pipeline/to_usenet.py
index f7edb2381..080ee3519 100644
--- a/Mailman/pipeline/to_usenet.py
+++ b/Mailman/pipeline/to_usenet.py
@@ -17,9 +17,17 @@
"""Move the message to the mail->news queue."""
+__metaclass__ = type
+__all__ = ['ToUsenet']
+
+
import logging
+from zope.interface import implements
+
from Mailman.configuration import config
+from Mailman.i18n import _
+from Mailman.interfaces import IHandler
from Mailman.queue import Switchboard
COMMASPACE = ', '
@@ -28,22 +36,31 @@ log = logging.getLogger('mailman.error')
-def process(mlist, msg, msgdata):
- # short circuits
- if not mlist.gateway_to_news or \
- msgdata.get('isdigest') or \
- msgdata.get('fromusenet'):
- return
- # sanity checks
- error = []
- if not mlist.linked_newsgroup:
- error.append('no newsgroup')
- if not mlist.nntp_host:
- error.append('no NNTP host')
- if error:
- log.error('NNTP gateway improperly configured: %s',
- COMMASPACE.join(error))
- return
- # Put the message in the news runner's queue
- newsq = Switchboard(config.NEWSQUEUE_DIR)
- newsq.enqueue(msg, msgdata, listname=mlist.fqdn_listname)
+class ToUsenet:
+ """Move the message to the outgoing news queue."""
+
+ implements(IHandler)
+
+ name = 'to-usenet'
+ description = _('Move the message to the outgoing news queue.')
+
+ def process(self, mlist, msg, msgdata):
+ """See `IHandler`."""
+ # Short circuits.
+ if not mlist.gateway_to_news or \
+ msgdata.get('isdigest') or \
+ msgdata.get('fromusenet'):
+ return
+ # sanity checks
+ error = []
+ if not mlist.linked_newsgroup:
+ error.append('no newsgroup')
+ if not mlist.nntp_host:
+ error.append('no NNTP host')
+ if error:
+ log.error('NNTP gateway improperly configured: %s',
+ COMMASPACE.join(error))
+ return
+ # Put the message in the news runner's queue
+ newsq = Switchboard(config.NEWSQUEUE_DIR)
+ newsq.enqueue(msg, msgdata, listname=mlist.fqdn_listname)
diff --git a/Mailman/queue/docs/outgoing.txt b/Mailman/queue/docs/outgoing.txt
index a9c031408..8b7024c77 100644
--- a/Mailman/queue/docs/outgoing.txt
+++ b/Mailman/queue/docs/outgoing.txt
@@ -9,9 +9,9 @@ term somewhat incorrectly, but within the spirit of the standard, which
basically describes how to encode the recipient's address in the originator
headers for unambigous bounce processing.
- >>> from Mailman.pipeline.to_outgoing import process
>>> from Mailman.queue import Switchboard
>>> from Mailman.configuration import config
+ >>> handler = config.handlers['to-outgoing']
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
>>> switchboard = Switchboard(config.OUTQUEUE_DIR)
@@ -35,7 +35,7 @@ When certain conditions are met, the message will be VERP'd. For example, if
the message metadata already has a VERP key, this message will be VERP'd.
>>> msgdata = dict(foo=1, bar=2, verp=True)
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> print msg.as_string()
Subject: Here is a message
<BLANKLINE>
@@ -72,7 +72,7 @@ VERP'd.
>>> from Mailman.interfaces import Personalization
>>> mlist.personalize = Personalization.individual
>>> msgdata = dict(foo=1, bar=2)
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> msgdata['verp']
True
>>> queue_size()
@@ -83,7 +83,7 @@ personalized lists will not VERP.
>>> config.VERP_PERSONALIZED_DELIVERIES = False
>>> msgdata = dict(foo=1, bar=2)
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> print msgdata.get('verp')
None
>>> queue_size()
@@ -99,7 +99,7 @@ to zero, which means non-personalized messages will never be VERP'd.
>>> config.VERP_DELIVERY_INTERVAL = 0
>>> mlist.personalize = Personalization.none
>>> msgdata = dict(foo=1, bar=2)
- >>> process(mlist, msg, msgdata)
+ >>> handler.process(mlist, msg, msgdata)
>>> print msgdata.get('verp')
None
>>> queue_size()
@@ -110,7 +110,7 @@ If the interval is set to 1, then every message will be VERP'd.
>>> config.VERP_DELIVERY_INTERVAL = 1
>>> for i in range(10):
... msgdata = dict(foo=1, bar=2)
- ... process(mlist, msg, msgdata)
+ ... handler.process(mlist, msg, msgdata)
... print i, msgdata['verp']
0 True
1 True
@@ -132,7 +132,7 @@ will be VERP'd.
>>> for i in range(10):
... mlist.post_id = i
... msgdata = dict(foo=1, bar=2)
- ... process(mlist, msg, msgdata)
+ ... handler.process(mlist, msg, msgdata)
... print i, msgdata.get('verp', False)
0 True
1 False
diff --git a/Mailman/rules/__init__.py b/Mailman/rules/__init__.py
index 51e8c562f..5eb7d87d9 100644
--- a/Mailman/rules/__init__.py
+++ b/Mailman/rules/__init__.py
@@ -17,8 +17,8 @@
"""The built in rule set."""
-__all__ = ['initialize']
__metaclass__ = type
+__all__ = ['initialize']
import os
diff --git a/setup.py b/setup.py
index 437f6ba2c..8d6bd88c6 100644
--- a/setup.py
+++ b/setup.py
@@ -90,6 +90,7 @@ Any other spelling is incorrect.""",
'mailman.styles' : 'default = Mailman.app.styles:DefaultStyle',
'mailman.mta' : 'stock = Mailman.MTA:Manual',
'mailman.rules' : 'default = Mailman.rules:initialize',
+ 'mailman.handlers' : 'default = Mailman.pipeline:initialize',
},
# Third-party requirements.
install_requires = [