1 # Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """The header-matching chain."""
30 from zope
.interface
import implements
32 from mailman
.chains
.base
import Chain
, Link
33 from mailman
.config
import config
34 from mailman
.i18n
import _
35 from mailman
.interfaces
.chain
import IChainIterator
, LinkAction
36 from mailman
.interfaces
.rules
import IRule
39 log
= logging
.getLogger('mailman.vette')
44 """Create a Link object.
46 :param entry: a 2- or 3-tuple describing a link. If a 2-tuple, it is a
47 header and a pattern, and a default chain of 'hold' will be used. If
48 a 3-tuple, the third item is the chain name to use.
52 header
, pattern
= entry
55 header
, pattern
, chain_name
= entry
56 # We don't assert that the chain exists here because the jump
57 # chain may not yet have been created.
59 raise AssertionError('Bad link description: %s' % entry
)
60 rule
= HeaderMatchRule(header
, pattern
)
61 chain
= config
.chains
[chain_name
]
62 return Link(rule
, LinkAction
.jump
, chain
)
66 class HeaderMatchRule
:
67 """Header matching rule used by header-match chain."""
70 # Sequential rule counter.
73 def __init__(self
, header
, pattern
):
75 self
._pattern
= pattern
76 self
.name
= 'header-match-%002d' % HeaderMatchRule
._count
77 HeaderMatchRule
._count
+= 1
78 self
.description
= u
'%s: %s' % (header
, pattern
)
79 # XXX I think we should do better here, somehow recording that a
80 # particular header matched a particular pattern, but that gets ugly
81 # with RFC 2822 headers. It also doesn't match well with the rule
82 # name concept. For now, we just record the rather useless numeric
83 # rule name. I suppose we could do the better hit recording in the
84 # check() method, and set self.record = False.
87 def check(self
, mlist
, msg
, msgdata
):
89 for value
in msg
.get_all(self
._header
, []):
90 if re
.search(self
._pattern
, value
, re
.IGNORECASE
):
96 class HeaderMatchChain(Chain
):
97 """Default header matching chain.
99 This could be extended by header match rules in the database.
103 super(HeaderMatchChain
, self
).__init
__(
104 'header-match', _('The built-in header matching chain'))
105 # The header match rules are not global, so don't register them.
106 # These are the only rules that the header match chain can execute.
108 # Initialize header check rules with those from the global
109 # HEADER_MATCHES variable.
110 for entry
in config
.header_matches
:
111 self
._links
.append(make_link(entry
))
112 # Keep track of how many global header matching rules we've seen.
113 # This is so the flush() method will only delete those that were added
114 # via extend() or append_link().
115 self
._permanent
_link
_count
= len(self
._links
)
117 def extend(self
, header
, pattern
, chain_name
='hold'):
118 """Extend the existing header matches.
120 :param header: The case-insensitive header field name.
121 :param pattern: The pattern to match the header's value again. The
122 match is not anchored and is done case-insensitively.
123 :param chain: Option chain to jump to if the pattern matches any of
124 the named header values. If not given, the 'hold' chain is used.
126 self
._links
.append(make_link((header
, pattern
, chain_name
)))
129 """See `IMutableChain`."""
130 del self
._links
[self
._permanent
_link
_count
:]
132 def get_links(self
, mlist
, msg
, msgdata
):
134 list_iterator
= HeaderMatchIterator(mlist
)
135 return itertools
.chain(iter(self
._links
), iter(list_iterator
))
138 for link
in self
._links
:
143 class HeaderMatchIterator
:
144 """An iterator of both the global and list-specific chain links."""
146 implements(IChainIterator
)
148 def __init__(self
, mlist
):
152 """See `IChainIterator`."""
153 for entry
in self
._mlist
.header_matches
:
154 yield make_link(entry
)