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
|
# Copyright (C) 2002-2009 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/>.
"""If the user wishes it, do not send duplicates of the same message.
This module keeps an in-memory dictionary of Message-ID: and recipient pairs.
If a message with an identical Message-ID: is about to be sent to someone who
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.
"""
from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
'AvoidDuplicates',
]
from email.utils import getaddresses, formataddr
from zope.interface import implements
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
COMMASPACE = ', '
# pylint: disable-msg=W0232,R0201
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('recipients')
# 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)
# Set the new list of recipients. XXX recips should always be a set.
msgdata['recipients'] = list(newrecips)
# RFC 2822 specifies zero or one CC header
if cc_addresses:
del msg['cc']
msg['CC'] = COMMASPACE.join(cc_addresses.values())
|