summaryrefslogtreecommitdiff
path: root/Mailman/app/chains.py
diff options
context:
space:
mode:
authorBarry Warsaw2008-01-23 23:47:50 -0500
committerBarry Warsaw2008-01-23 23:47:50 -0500
commitdf637148d8fa2d5c101a990ee6766ea8547f000a (patch)
tree92e3e1c218c6f59031a0d0383a98a38e38dc2f9f /Mailman/app/chains.py
parent4460aad316db5c8af9b84c392e67441acaac9d72 (diff)
downloadmailman-df637148d8fa2d5c101a990ee6766ea8547f000a.tar.gz
mailman-df637148d8fa2d5c101a990ee6766ea8547f000a.tar.zst
mailman-df637148d8fa2d5c101a990ee6766ea8547f000a.zip
More changes to rules and chains.
Now a link has a rule, action, chain, and function, not all of which needs to be specified. The action is a LinkAction enum adn specifies what to do should the rule match. The use of the chain or function depends on what the action is. Several interface changes now make it easier to jump to other chains, push (i.e. detour) to chains, etc. Rules can also now specify that they should not be recorded in X-* headers. Added a TruthRule which always matches.
Diffstat (limited to 'Mailman/app/chains.py')
-rw-r--r--Mailman/app/chains.py163
1 files changed, 105 insertions, 58 deletions
diff --git a/Mailman/app/chains.py b/Mailman/app/chains.py
index 8ac9e9ce7..38c7325ab 100644
--- a/Mailman/app/chains.py
+++ b/Mailman/app/chains.py
@@ -21,9 +21,13 @@ from __future__ import with_statement
__all__ = [
'AcceptChain',
+ 'Chain',
'DiscardChain',
'HoldChain',
+ 'Link',
'RejectChain',
+ 'initialize',
+ 'process',
]
__metaclass__ = type
__i18n_templates__ = True
@@ -44,7 +48,8 @@ from Mailman.app.moderator import hold_message
from Mailman.app.replybot import autorespond_to_sender, can_acknowledge
from Mailman.configuration import config
from Mailman.i18n import _
-from Mailman.interfaces import IChain, IChainLink, IMutableChain, IPendable
+from Mailman.interfaces import (
+ IChain, IChainLink, IMutableChain, IPendable, LinkAction)
from Mailman.queue import Switchboard
log = logging.getLogger('mailman.vette')
@@ -59,7 +64,37 @@ class HeldMessagePendable(dict):
-class DiscardChain:
+class Link:
+ """A chain link."""
+ implements(IChainLink)
+
+ def __init__(self, rule, action=None, chain=None, function=None):
+ self.rule = rule
+ self.action = (LinkAction.defer if action is None else action)
+ self.chain = chain
+ self.function = function
+
+
+
+class TerminalChainBase:
+ """A base chain that always matches and executes a method.
+
+ The method is called 'process' and must be provided by the subclass.
+ """
+ implements(IChain)
+
+ def __iter__(self):
+ """See `IChain`."""
+ # First, yield a link that always runs the process method.
+ yield Link('truth', LinkAction.run, function=self.process)
+ # Now yield a rule that stops all processing.
+ yield Link('truth', LinkAction.stop)
+
+ def process(self, mlist, msg, msgdata):
+ raise NotImplementedError
+
+
+class DiscardChain(TerminalChainBase):
"""Discard a message."""
implements(IChain)
@@ -73,7 +108,7 @@ class DiscardChain:
-class HoldChain:
+class HoldChain(TerminalChainBase):
"""Hold a message."""
implements(IChain)
@@ -199,7 +234,7 @@ also appear in the first line of the body of the reply.""")),
-class RejectChain:
+class RejectChain(TerminalChainBase):
"""Reject/bounce a message."""
implements(IChain)
@@ -223,7 +258,7 @@ class RejectChain:
-class AcceptChain:
+class AcceptChain(TerminalChainBase):
"""Accept the message for posting."""
implements(IChain)
@@ -247,15 +282,6 @@ class AcceptChain:
-class Link:
- """A chain link."""
- implements(IChainLink)
-
- def __init__(self, rule, jump):
- self.rule = rule
- self.jump = jump
-
-
class Chain:
"""Default built-in moderation chain."""
implements(IMutableChain)
@@ -276,42 +302,63 @@ class Chain:
"""See `IMutableChain`."""
self._links = []
- def process(self, mlist, msg, msgdata):
- """See `IMutableChain`."""
- msgdata['rule_hits'] = hits = []
- msgdata['rule_misses'] = misses = []
- jump = None
+ def __iter__(self):
+ """See `IChain`."""
for link in self._links:
- # The None rule always match.
- if link.rule is None:
- jump = link.jump
- break
- # If the rule hits, jump to the given chain.
- rule = config.rules.get(link.rule)
- if rule is None:
- elog.error('Rule not found: %s', rule)
- elif rule.check(mlist, msg, msgdata):
+ yield link
+
+
+
+def process(start_chain, mlist, msg, msgdata):
+ """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.
+ """
+ # Find the starting chain.
+ current_chain = iter(config.chains[start_chain])
+ chain_stack = []
+ msgdata['rule_hits'] = hits = []
+ msgdata['rule_misses'] = misses = []
+ while current_chain:
+ try:
+ link = current_chain.next()
+ except StopIteration:
+ # This chain is exhausted. Pop the last chain on the stack and
+ # continue.
+ if len(chain_stack) == 0:
+ return
+ current_chain = chain_stack.pop()
+ continue
+ # Process this link.
+ rule = config.rules[link.rule]
+ if rule.check(mlist, msg, msgdata):
+ if rule.record:
hits.append(link.rule)
- # None is a special jump meaning "keep processing this chain".
- if link.jump is not None:
- jump = link.jump
- break
+ # The rule matched so run its action.
+ if link.action is LinkAction.jump:
+ current_chain = iter(config.chains[link.chain])
+ elif link.action is LinkAction.detour:
+ # Push the current chain so that we can return to it when the
+ # next chain is finished.
+ chain_stack.append(current_chain)
+ current_chain = iter(config.chains[link.chain])
+ elif link.action is LinkAction.stop:
+ # Stop all processing.
+ return
+ elif link.action is LinkAction.defer:
+ # Just process the next link in the chain.
+ pass
+ elif link.action is LinkAction.run:
+ link.function(mlist, msg, msgdata)
else:
- misses.append(link.rule)
+ raise AssertionError('Unknown link action: %s' % link.action)
else:
- # We got through the entire chain without a jumping rule match, so
- # we really don't know what to do. Rather than raise an
- # exception, jump to the discard chain.
- log.info('Jumping to the discard chain by default.')
- jump = 'discard'
- # Find the named chain.
- chain = config.chains.get(jump)
- if chain is None:
- elog.error('Chain not found: %s', chain)
- # Well, what now? Nothing much left to do but discard the
- # message, which we can do by simply returning.
- else:
- chain.process(mlist, msg, msgdata)
+ # The rule did not match; keep going.
+ if rule.record:
+ misses.append(link.rule)
@@ -324,20 +371,20 @@ def initialize():
config.chains[chain.name] = chain
# Set up a couple of other default chains.
default = Chain('built-in', _('The built-in moderation chain.'))
- default.append_link(Link('approved', 'accept'))
- default.append_link(Link('emergency', 'hold'))
- default.append_link(Link('loop', 'discard'))
+ default.append_link(Link('approved', LinkAction.jump, 'accept'))
+ default.append_link(Link('emergency', LinkAction.jump, 'hold'))
+ default.append_link(Link('loop', LinkAction.jump, 'discard'))
# Do all these before deciding whether to hold the message for moderation.
- default.append_link(Link('administrivia', None))
- default.append_link(Link('implicit-dest', None))
- default.append_link(Link('max-recipients', None))
- default.append_link(Link('max-size', None))
- default.append_link(Link('news-moderation', None))
- default.append_link(Link('no-subject', None))
- default.append_link(Link('suspicious', None))
+ default.append_link(Link('administrivia', LinkAction.defer))
+ default.append_link(Link('implicit-dest', LinkAction.defer))
+ default.append_link(Link('max-recipients', LinkAction.defer))
+ default.append_link(Link('max-size', LinkAction.defer))
+ default.append_link(Link('news-moderation', LinkAction.defer))
+ default.append_link(Link('no-subject', LinkAction.defer))
+ default.append_link(Link('suspicious-header', LinkAction.defer))
# Now if any of the above hit, jump to the hold chain.
- default.append_link(Link('any', 'hold'))
+ default.append_link(Link('any', LinkAction.jump, 'hold'))
# Finally, the builtin chain defaults to acceptance.
- default.append_link(Link(None, 'accept'))
+ default.append_link(Link('truth', LinkAction.jump, 'accept'))
# XXX Read chains from the database and initialize them.
pass