Bump copyright years.
[mailman.git] / src / mailman / handlers / member_recipients.py
blob0f99bf709b9509d4f0367fcc46b008847b8982c0
1 # Copyright (C) 1998-2014 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)
8 # any later version.
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
13 # more details.
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 """Calculate the regular (i.e. non-digest) recipients of the message.
20 This module calculates the non-digest recipients for the message based on the
21 list's membership and configuration options. It places the list of recipients
22 on the `recipients' attribute of the message. This attribute is used by the
23 SendmailDeliver and BulkDeliver modules.
24 """
26 from __future__ import absolute_import, print_function, unicode_literals
28 __metaclass__ = type
29 __all__ = [
30 'MemberRecipients',
34 from zope.interface import implementer
36 from mailman.config import config
37 from mailman.core import errors
38 from mailman.core.i18n import _
39 from mailman.interfaces.handler import IHandler
40 from mailman.interfaces.member import DeliveryStatus
41 from mailman.utilities.string import wrap
45 @implementer(IHandler)
46 class MemberRecipients:
47 """Calculate the regular (i.e. non-digest) recipients of the message."""
49 name = 'member-recipients'
50 description = _('Calculate the regular recipients of the message.')
52 def process(self, mlist, msg, msgdata):
53 """See `IHandler`."""
54 # Short circuit if we've already calculated the recipients list,
55 # regardless of whether the list is empty or not.
56 if 'recipients' in msgdata:
57 return
58 # Should the original sender should be included in the recipients list?
59 include_sender = True
60 member = mlist.members.get_member(msg.sender)
61 if member and not member.receive_own_postings:
62 include_sender = False
63 # Support for urgent messages, which bypasses digests and disabled
64 # delivery and forces an immediate delivery to all members Right Now.
65 # We are specifically /not/ allowing the site admins password to work
66 # here because we want to discourage the practice of sending the site
67 # admin password through email in the clear. (see also Approve.py)
69 # XXX This is broken.
70 missing = object()
71 password = msg.get('urgent', missing)
72 if password is not missing:
73 if mlist.Authenticate((config.AuthListModerator,
74 config.AuthListAdmin),
75 password):
76 recipients = mlist.getMemberCPAddresses(
77 mlist.getRegularMemberKeys() +
78 mlist.getDigestMemberKeys())
79 msgdata['recipients'] = recipients
80 return
81 else:
82 # Bad Urgent: password, so reject it instead of passing it on.
83 # I think it's better that the sender know they screwed up
84 # than to deliver it normally.
85 text = _("""\
86 Your urgent message to the $mlist.display_name mailing list was not authorized
87 for delivery. The original message as received by Mailman is attached.
88 """)
89 raise errors.RejectMessage(wrap(text))
90 # Calculate the regular recipients of the message
91 recipients = set(member.address.email
92 for member in mlist.regular_members.members
93 if member.delivery_status == DeliveryStatus.enabled)
94 # Remove the sender if they don't want to receive their own posts
95 if not include_sender and member.address.email in recipients:
96 recipients.remove(member.address.email)
97 # Handle topic classifications
98 do_topic_filters(mlist, msg, msgdata, recipients)
99 # Bookkeeping
100 msgdata['recipients'] = recipients
104 def do_topic_filters(mlist, msg, msgdata, recipients):
105 """Filter out recipients based on topics."""
106 if not mlist.topics_enabled:
107 # MAS: if topics are currently disabled for the list, send to all
108 # regardless of ReceiveNonmatchingTopics
109 return
110 hits = msgdata.get('topichits')
111 zap_recipients = []
112 if hits:
113 # The message hit some topics, so only deliver this message to those
114 # who are interested in one of the hit topics.
115 for user in recipients:
116 utopics = mlist.getMemberTopics(user)
117 if not utopics:
118 # This user is not interested in any topics, so they get all
119 # postings.
120 continue
121 # BAW: Slow, first-match, set intersection!
122 for topic in utopics:
123 if topic in hits:
124 # The user wants this message
125 break
126 else:
127 # The user was interested in topics, but not any of the ones
128 # this message matched, so zap him.
129 zap_recipients.append(user)
130 else:
131 # The semantics for a message that did not hit any of the pre-canned
132 # topics is to troll through the membership list, looking for users
133 # who selected at least one topic of interest, but turned on
134 # ReceiveNonmatchingTopics.
135 for user in recipients:
136 if not mlist.getMemberTopics(user):
137 # The user did not select any topics of interest, so he gets
138 # this message by default.
139 continue
140 if not mlist.getMemberOption(
141 user, config.ReceiveNonmatchingTopics):
142 # The user has interest in some topics, but elects not to
143 # receive message that match no topics, so zap him.
144 zap_recipients.append(user)
145 # Otherwise, the user wants non-matching messages.
146 # Prune out the non-receiving users
147 for user in zap_recipients:
148 recipients.remove(user)