summaryrefslogtreecommitdiff
path: root/src/mailman/handlers/rfc_2369.py
blob: ab7fc76eaa23286a4245961492dcc20f65d423e7 (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
117
118
119
120
121
122
123
124
# Copyright (C) 2011-2015 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
# GNU Mailman 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 3 of the License, or (at your option)
# any later version.
#
# GNU Mailman 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
# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.

"""RFC 2369 List-* and related headers."""

__all__ = [
    'RFC2369',
    ]


from email.utils import formataddr
from mailman.core.i18n import _
from mailman.handlers.cook_headers import uheader
from mailman.interfaces.archiver import ArchivePolicy
from mailman.interfaces.mailinglist import IListArchiverSet
from mailman.interfaces.handler import IHandler
from zope.interface import implementer


CONTINUATION = ',\n\t'



def process(mlist, msg, msgdata):
    """Add the RFC 2369 List-* and related headers."""
    # Some people really hate the List-* headers.  It seems that the free
    # version of Eudora (possibly on for some platforms) does not hide these
    # headers by default, pissing off their users.  Too bad.  Fix the MUAs.
    if not mlist.include_rfc2369_headers:
        return
    list_id = '{0.list_name}.{0.mail_host}'.format(mlist)
    if mlist.description:
        # Don't wrap the header since here we just want to get it properly RFC
        # 2047 encoded.
        i18ndesc = uheader(mlist, mlist.description, 'List-Id', maxlinelen=998)
        listid_h = formataddr((str(i18ndesc), list_id))
    else:
        # Without a description, we need to ensure the MUST brackets.
        listid_h = '<{}>'.format(list_id)
    # No other agent should add a List-ID header except Mailman.
    del msg['list-id']
    msg['List-Id'] = listid_h
    # For internally crafted messages, we also add a (nonstandard),
    # "X-List-Administrivia: yes" header.  For all others (i.e. those coming
    # from list posts), we add a bunch of other RFC 2369 headers.
    requestaddr = mlist.request_address
    subfieldfmt = '<{}>, <mailto:{}>'
    listinfo = mlist.script_url('listinfo')
    headers = []
    # XXX reduced_list_headers used to suppress List-Help, List-Subject, and
    # List-Unsubscribe from UserNotification.  That doesn't seem to make sense
    # any more, so always add those three headers (others will still be
    # suppressed).
    headers.extend((
        ('List-Help', '<mailto:{}?subject=help>'.format(requestaddr)),
        ('List-Unsubscribe',
         subfieldfmt.format(listinfo, mlist.leave_address)),
        ('List-Subscribe', subfieldfmt.format(listinfo, mlist.join_address)),
        ))
    if not msgdata.get('reduced_list_headers'):
        # List-Post: is controlled by a separate attribute, which is somewhat
        # misnamed.  RFC 2369 requires a value of NO if posting is not
        # allowed, i.e. for an announce-only list.
        list_post = ('<mailto:{}>'.format(mlist.posting_address)
                     if mlist.allow_list_posts
                     else 'NO')
        headers.append(('List-Post', list_post))
        # Add RFC 2369 and 5064 archiving headers, if archiving is enabled.
        if mlist.archive_policy is not ArchivePolicy.never:
            archiver_set = IListArchiverSet(mlist)
            for archiver in archiver_set.archivers:
                if not archiver.is_enabled:
                    continue
                archiver_url = archiver.system_archiver.list_url(mlist)
                if archiver_url is not None:
                    headers.append(('List-Archive',
                                    '<{}>'.format(archiver_url)))
                permalink = archiver.system_archiver.permalink(mlist, msg)
                if permalink is not None:
                    headers.append(('Archived-At', '<{}>'.format(permalink)))
    # XXX RFC 2369 also defines a List-Owner header which we are not currently
    # supporting, but should.
    #
    # Some headers will appear more than once in the new set, e.g. the
    # List-Archive and Archived-At headers.  We want to delete any RFC 2369
    # headers from the original message, but make sure to preserve all of the
    # new headers we're adding.  Go through the list of new headers twice,
    # first removing any old ones, then adding all the new ones.
    for h, v in headers:
        del msg[h]
    for h, v in sorted(headers):
        # Wrap these lines if they are too long.  78 character width probably
        # shouldn't be hardcoded, but is at least text-MUA friendly.  The
        # adding of 2 is for the colon-space separator.
        if len(h) + 2 + len(v) > 78:
            v = CONTINUATION.join(v.split(', '))
        msg[h] = v



@implementer(IHandler)
class RFC2369:
    """Add the RFC 2369 List-* headers."""

    name = 'rfc-2369'
    description = _('Add the RFC 2369 List-* headers.')

    def process(self, mlist, msg, msgdata):
        """See `IHandler`."""
        process(mlist, msg, msgdata)