summaryrefslogtreecommitdiff
path: root/Mailman/chains/headers.py
blob: a802eaab4842b702e6ed354dd1126f246130adf5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# Copyright (C) 2007-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 header-matching chain."""

__all__ = ['HeaderMatchChain']
__metaclass__ = type


import re
import logging

from zope.interface import implements

from Mailman.interfaces import IRule, LinkAction
from Mailman.chains.base import Chain, Link
from Mailman.i18n import _
from Mailman.configuration import config


log = logging.getLogger('mailman.vette')



class HeaderMatchRule:
    """Header matching rule used by header-match chain."""
    implements(IRule)

    # Sequential rule counter.
    _count = 1

    def __init__(self, header, pattern):
        self._header = header
        self._pattern = pattern
        self.name = 'header-match-%002d' % HeaderMatchRule._count
        HeaderMatchRule._count += 1
        self.description = u'%s: %s' % (header, pattern)
        # XXX I think we should do better here, somehow recording that a
        # particular header matched a particular pattern, but that gets ugly
        # with RFC 2822 headers.  It also doesn't match well with the rule
        # name concept.  For now, we just record the rather useless numeric
        # rule name.  I suppose we could do the better hit recording in the
        # check() method, and set self.record = False.
        self.record = True

    def check(self, mlist, msg, msgdata):
        """See `IRule`."""
        for value in msg.get_all(self._header, []):
            if re.search(self._pattern, value, re.IGNORECASE):
                return True
        return False



class HeaderMatchChain(Chain):
    """Default header matching chain.

    This could be extended by header match rules in the database.
    """

    def __init__(self):
        super(HeaderMatchChain, self).__init__(
            'header-match', _('The built-in header matching chain'))
        # The header match rules are not global, so don't register them.
        # These are the only rules that the header match chain can execute.
        self._links = []
        self._rules = {}
        # Initialize header check rules with those from the global
        # HEADER_MATCHES variable.
        for entry in config.HEADER_MATCHES:
            if len(entry) == 2:
                header, pattern = entry
                chain = 'hold'
            elif len(entry) == 3:
                header, pattern, chain = entry
                # We don't assert that the chain exists here because the jump
                # chain may not yet have been created.
            else:
                raise AssertionError(
                    'Bad entry for HEADER_MATCHES: %s' % entry)
            self.extend(header, pattern, chain)

    def extend(self, header, pattern, chain='hold'):
        """Extend the existing header matches.

        :param header: The case-insensitive header field name.
        :param pattern: The pattern to match the header's value again.  The
            match is not anchored and is done case-insensitively.
        :param chain: Option chain to jump to if the pattern matches any of
            the named header values.  If not given, the 'hold' chain is used.
        """
        rule = HeaderMatchRule(header, pattern)
        self._rules[rule.name] = rule
        link = Link(rule.name, LinkAction.jump, chain)
        self._links.append(link)

    def get_rule(self, name):
        """See `IChain`.

        Only local rules are findable by this chain.
        """
        return self._rules[name]