diff options
| author | Barry Warsaw | 2017-07-26 23:24:18 +0000 |
|---|---|---|
| committer | Barry Warsaw | 2017-07-26 23:24:18 +0000 |
| commit | ade94e8f833a9472ba9be52a08f4e2f381e7a212 (patch) | |
| tree | 5fba570b0c6f4c0919009cb6f455c18bc732192a /src/mailman/chains | |
| parent | 02826321d0430d7ffc1f674eeff4221941689ef7 (diff) | |
| parent | bea94cb9538a55b1376afd42c2ce751efce62cfe (diff) | |
| download | mailman-ade94e8f833a9472ba9be52a08f4e2f381e7a212.tar.gz mailman-ade94e8f833a9472ba9be52a08f4e2f381e7a212.tar.zst mailman-ade94e8f833a9472ba9be52a08f4e2f381e7a212.zip | |
Diffstat (limited to 'src/mailman/chains')
| -rw-r--r-- | src/mailman/chains/base.py | 19 | ||||
| -rw-r--r-- | src/mailman/chains/headers.py | 5 | ||||
| -rw-r--r-- | src/mailman/chains/hold.py | 11 | ||||
| -rw-r--r-- | src/mailman/chains/reject.py | 4 | ||||
| -rw-r--r-- | src/mailman/chains/tests/test_headers.py | 55 | ||||
| -rw-r--r-- | src/mailman/chains/tests/test_hold.py | 7 | ||||
| -rw-r--r-- | src/mailman/chains/tests/test_reject.py | 2 |
7 files changed, 94 insertions, 9 deletions
diff --git a/src/mailman/chains/base.py b/src/mailman/chains/base.py index 59125ba69..9507d8fbc 100644 --- a/src/mailman/chains/base.py +++ b/src/mailman/chains/base.py @@ -18,6 +18,7 @@ """Base class for terminal chains.""" from mailman.config import config +from mailman.core.i18n import _ from mailman.interfaces.chain import ( IChain, IChainIterator, IChainLink, IMutableChain, LinkAction) from mailman.interfaces.rules import IRule @@ -27,6 +28,24 @@ from zope.interface import implementer @public +def format_reasons(reasons): + """Translate and format hold and rejection reasons. + + :param reasons: A list of reasons from the rules that hit. Each reason is + a string to be translated or a tuple consisting of a string with {} + replacements and one or more replacement values. + :returns: A list of the translated and formatted strings. + """ + new_reasons = [] + for reason in reasons: + if isinstance(reason, tuple): + new_reasons.append(_(reason[0]).format(*reason[1:])) + else: + new_reasons.append(_(reason)) + return new_reasons + + +@public @implementer(IChainLink) class Link: """A chain link.""" diff --git a/src/mailman/chains/headers.py b/src/mailman/chains/headers.py index 6fc061fe4..01ba96971 100644 --- a/src/mailman/chains/headers.py +++ b/src/mailman/chains/headers.py @@ -105,6 +105,11 @@ class HeaderMatchRule: if isinstance(value, Header): value = value.encode() if re.search(self.pattern, value, re.IGNORECASE): + msgdata['moderation_sender'] = msg.sender + with _.defer_translation(): + # This will be translated at the point of use. + msgdata.setdefault('moderation_reasons', []).append( + (_('Header "{}" matched a header rule'), str(value))) return True return False diff --git a/src/mailman/chains/hold.py b/src/mailman/chains/hold.py index 4e72b685a..edc80da3c 100644 --- a/src/mailman/chains/hold.py +++ b/src/mailman/chains/hold.py @@ -24,7 +24,7 @@ from email.mime.text import MIMEText from email.utils import formatdate, make_msgid from mailman.app.moderator import hold_message from mailman.app.replybot import can_acknowledge -from mailman.chains.base import TerminalChainBase +from mailman.chains.base import TerminalChainBase, format_reasons from mailman.config import config from mailman.core.i18n import _ from mailman.email.message import UserNotification @@ -57,8 +57,8 @@ def _compose_reasons(msgdata, column=66): # Rules can add reasons to the metadata. reasons = msgdata.get('moderation_reasons', [_('N/A')]) return NL.join( - [(SPACE * 4) + wrap(_(reason), column=column) - for reason in reasons]) + [(SPACE * 4) + wrap(reason, column=column) + for reason in format_reasons(reasons)]) def autorespond_to_sender(mlist, sender, language=None): @@ -142,7 +142,7 @@ class HoldChain(TerminalChainBase): rule_misses = msgdata.get('rule_misses') if rule_misses: msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses) - reasons = msgdata.get('moderation_reasons', ['n/a']) + reasons = format_reasons(msgdata.get('moderation_reasons', ['n/a'])) # Hold the message by adding it to the list's request database. request_id = hold_message(mlist, msg, msgdata, SEMISPACE.join(reasons)) # Calculate a confirmation token to send to the author of the @@ -251,7 +251,8 @@ also appear in the first line of the body of the reply.""")), # Log the held message. Log messages are not translated, so recast # the reasons in the English. with _.using('en'): - reasons = msgdata.get('moderation_reasons', ['N/A']) + reasons = format_reasons( + msgdata.get('moderation_reasons', ['N/A'])) log.info('HOLD: %s post from %s held, message-id=%s: %s', mlist.fqdn_listname, msg.sender, msg.get('message-id', 'n/a'), SEMISPACE.join(reasons)) diff --git a/src/mailman/chains/reject.py b/src/mailman/chains/reject.py index 3284bba9f..31f66c8fa 100644 --- a/src/mailman/chains/reject.py +++ b/src/mailman/chains/reject.py @@ -20,7 +20,7 @@ import logging from mailman.app.bounces import bounce_message -from mailman.chains.base import TerminalChainBase +from mailman.chains.base import TerminalChainBase, format_reasons from mailman.core.i18n import _ from mailman.interfaces.chain import RejectEvent from mailman.interfaces.pipeline import RejectMessage @@ -65,7 +65,7 @@ reasons: The original message as received by Mailman is attached. """).format( list_name=mlist.display_name, # noqa: E122 - reasons=NEWLINE.join(reasons) + reasons=NEWLINE.join(format_reasons(reasons)) )) bounce_message(mlist, msg, error) log.info('REJECT: %s', msg.get('message-id', 'n/a')) diff --git a/src/mailman/chains/tests/test_headers.py b/src/mailman/chains/tests/test_headers.py index cd4c932cc..2aae503b2 100644 --- a/src/mailman/chains/tests/test_headers.py +++ b/src/mailman/chains/tests/test_headers.py @@ -25,7 +25,8 @@ from mailman.chains.headers import HeaderMatchRule, make_link from mailman.config import config from mailman.core.chains import process from mailman.email.message import Message -from mailman.interfaces.chain import DiscardEvent, HoldEvent, LinkAction +from mailman.interfaces.chain import ( + DiscardEvent, HoldEvent, LinkAction, RejectEvent) from mailman.interfaces.mailinglist import IHeaderMatchList from mailman.testing.helpers import ( LogFileMark, configuration, event_subscribers, @@ -343,3 +344,55 @@ A message body. # ...and are actually the identical objects. for link1, link2 in zip(links_1, links_2): self.assertIs(link1.rule, link2.rule) + + def test_hold_returns_reason(self): + # Test that a match with hold action returns a reason + msg = mfs("""\ +From: anne@example.com +To: test@example.com +Subject: Bad subject +Message-ID: <ant> + +body + +""") + msgdata = {} + header_matches = IHeaderMatchList(self._mlist) + header_matches.append('Subject', 'Bad', 'hold') + # This event subscriber records the event that occurs when the message + # is processed by the owner chain. + events = [] + with event_subscribers(events.append): + process(self._mlist, msg, msgdata, start_chain='header-match') + self.assertEqual(len(events), 1) + event = events[0] + self.assertIsInstance(event, HoldEvent) + self.assertEqual(msgdata['moderation_reasons'], + [('Header "{}" matched a header rule', + 'Bad subject')]) + + def test_reject_returns_reason(self): + # Test that a match with reject action returns a reason + msg = mfs("""\ +From: anne@example.com +To: test@example.com +Subject: Bad subject +Message-ID: <ant> + +body + +""") + msgdata = {} + header_matches = IHeaderMatchList(self._mlist) + header_matches.append('Subject', 'Bad', 'reject') + # This event subscriber records the event that occurs when the message + # is processed by the owner chain. + events = [] + with event_subscribers(events.append): + process(self._mlist, msg, msgdata, start_chain='header-match') + self.assertEqual(len(events), 1) + event = events[0] + self.assertIsInstance(event, RejectEvent) + self.assertEqual(msgdata['moderation_reasons'], + [('Header "{}" matched a header rule', + 'Bad subject')]) diff --git a/src/mailman/chains/tests/test_hold.py b/src/mailman/chains/tests/test_hold.py index b973b874c..560916e3b 100644 --- a/src/mailman/chains/tests/test_hold.py +++ b/src/mailman/chains/tests/test_hold.py @@ -111,6 +111,7 @@ A message body. msgdata = dict(moderation_reasons=[ 'TEST-REASON-1', 'TEST-REASON-2', + ('TEST-{}-REASON-{}', 'FORMAT', 3), ]) logfile = LogFileMark('mailman.vette') process_chain(self._mlist, msg, msgdata, start_chain='hold') @@ -126,18 +127,22 @@ A message body. self.fail('Unexpected message: %s' % item.msg) self.assertIn(' TEST-REASON-1', payloads['owner']) self.assertIn(' TEST-REASON-2', payloads['owner']) + self.assertIn(' TEST-FORMAT-REASON-3', payloads['owner']) self.assertIn(' TEST-REASON-1', payloads['sender']) self.assertIn(' TEST-REASON-2', payloads['sender']) + self.assertIn(' TEST-FORMAT-REASON-3', payloads['sender']) logged = logfile.read() self.assertIn('TEST-REASON-1', logged) self.assertIn('TEST-REASON-2', logged) + self.assertIn('TEST-FORMAT-REASON-3', logged) # Check the reason passed to hold_message(). requests = IListRequests(self._mlist) self.assertEqual(requests.count_of(RequestType.held_message), 1) request = requests.of_type(RequestType.held_message)[0] key, data = requests.get_request(request.id) self.assertEqual( - data.get('_mod_reason'), 'TEST-REASON-1; TEST-REASON-2') + data.get('_mod_reason'), + 'TEST-REASON-1; TEST-REASON-2; TEST-FORMAT-REASON-3') def test_hold_chain_no_reasons_given(self): msg = mfs("""\ diff --git a/src/mailman/chains/tests/test_reject.py b/src/mailman/chains/tests/test_reject.py index f6fd6a8fe..8fce1de4a 100644 --- a/src/mailman/chains/tests/test_reject.py +++ b/src/mailman/chains/tests/test_reject.py @@ -45,12 +45,14 @@ Subject: Ignore msgdata = dict(moderation_reasons=[ 'TEST-REASON-1', 'TEST-REASON-2', + ('TEST-{}-REASON-{}', 'FORMAT', 3), ]) process_chain(self._mlist, self._msg, msgdata, start_chain='reject') bounces = get_queue_messages('virgin', expected_count=1) payload = bounces[0].msg.get_payload(0).as_string() self.assertIn('TEST-REASON-1', payload) self.assertIn('TEST-REASON-2', payload) + self.assertIn('TEST-FORMAT-REASON-3', payload) def test_no_reason(self): # There may be no moderation reasons. |
