Commit 056eb0e7 authored by Mark Sapiro's avatar Mark Sapiro

Merge branch 'fix_725' into 'master'

Defend against an incorrectly folded header from MS Outlook.

Closes #725

See merge request !652
parents 20a070ee 6bf8e507
Pipeline #149922558 passed with stage
in 11 minutes and 28 seconds
......@@ -19,6 +19,8 @@ Bugs
and footers. (Closes #701)
* RFC 2369 headers are now added to notification messages. (Closes #710)
* Bounce probes are now encoded in the correct charset. (Closes #712)
* We now appropriately parse an incorrectly folded Cc: header from MS Outlook.
(Closes #725)
REST
......
......@@ -23,6 +23,8 @@ 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.
"""
import re
from email.utils import getaddresses, formataddr
from mailman.core.i18n import _
from mailman.interfaces.handler import IHandler
......@@ -55,8 +57,16 @@ class AvoidDuplicates:
explicit_recips = listaddrs.copy()
# Figure out the set of explicit recipients.
cc_addresses = {}
# MS Outlook creates a defective message when composing with several
# Cc addresses of the form `real name (dept) <[email protected]>`,
# Outlook quotes "real name (dept)" and then folds the header between
# `name and (dept)` resulting in a header including the entry
# '"real name\r\n (dept)" <[email protected]>' which is non-compliant
# and parses incorrectly, so we "unfold" headers here.
for header in ('to', 'cc', 'resent-to', 'resent-cc'):
addrs = getaddresses(msg.get_all(header, []))
hdrs_unfolded = [re.sub('[\r\n]', '', value) for value in
msg.get_all(header, [])]
addrs = getaddresses(hdrs_unfolded)
header_addresses = dict((addr, formataddr((name, addr)))
for name, addr in addrs
if addr)
......
# Copyright (C) 2014-2020 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 <https://www.gnu.org/licenses/>.
"""Test the avoid_duplicates handler."""
import unittest
from mailman.app.lifecycle import create_list
from mailman.handlers.avoid_duplicates import AvoidDuplicates
from mailman.interfaces.member import MemberRole
from mailman.interfaces.usermanager import IUserManager
from mailman.testing.helpers import specialized_message_from_string as mfs
from mailman.testing.layers import ConfigLayer
from zope.component import getUtility
class TestAvoidDuplicates(unittest.TestCase):
"""Test the avoid_duplicates handler."""
layer = ConfigLayer
def setUp(self):
self._mlist = create_list('[email protected]')
self._mlist.send_welcome_message = False
self._manager = getUtility(IUserManager)
anne = self._manager.create_address('[email protected]')
bart = self._manager.create_address('[email protected]')
self._anne = self._mlist.subscribe(anne, MemberRole.member)
self._bart = self._mlist.subscribe(bart, MemberRole.member)
self._anne.preferences.receive_list_copy = True
self._bart.preferences.receive_list_copy = False
def test_delete_from_cc_and_recips(self):
# CC to member with receive_list_copy = False is dropped and member
# is dropped from recipients.
msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: A subject
Cc: [email protected], [email protected], [email protected]
X-Mailman-Version: X.Y
More things to say.
""")
msgdata = dict(recipients=set(['[email protected]',
'[email protected]']))
AvoidDuplicates().process(self._mlist, msg, msgdata)
self.assertEqual(list(msgdata.get('recipients', [])),
['[email protected]'])
# Don't test the whole message. The order in the Cc: varies with
# Python version.
ccs = msg.get('cc', '[email protected]')
self.assertIn('[email protected]', ccs)
self.assertIn('[email protected]', ccs)
self.assertNotIn('[email protected]', ccs)
del msg['cc']
self.assertMultiLineEqual(msg.as_string(), """\
From: [email protected]
To: [email protected]
Subject: A subject
X-Mailman-Version: X.Y
More things to say.
""")
def test_bogus_ms_outlook_header_folding(self):
# MS Outlook creates a defective message when composing with several
# Cc addresses of the form `real name (dept) <[email protected]>`,
# Outlook quotes "real name (dept)" and then folds the header between
# `name and (dept)` resulting in a header including the entry
# '"real name\r\n (dept)" <[email protected]>' which is non-compliant
# and parses incorrectly. Test our unfolding defense.
msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: A subject
Cc: [email protected], [email protected], "real name\r
(dept)" <[email protected]>
X-Mailman-Version: X.Y
More things to say.
""")
msgdata = dict(recipients=set(['[email protected]']))
AvoidDuplicates().process(self._mlist, msg, msgdata)
# Don't test the whole message. The order in the Cc: varies with
# Python version.
ccs = msg.get('cc')
self.assertIn('[email protected]', ccs)
self.assertIn('[email protected]', ccs)
self.assertIn('"real name (dept)" <[email protected]>', ccs)
del msg['cc']
self.assertMultiLineEqual(msg.as_string(), """\
From: [email protected]
To: [email protected]
Subject: A subject
X-Mailman-Version: X.Y
More things to say.
""")
def test_unfolding_with_quoted_comma(self):
# Ensure our unfolding doesn't break quoted commas.
msg = mfs("""\
From: [email protected]
To: [email protected]
Subject: A subject
Cc: "last, first" <[email protected]>, "real name\r
(dept)" <[email protected]>
X-Mailman-Version: X.Y
More things to say.
""")
msgdata = dict(recipients=set(['[email protected]']))
AvoidDuplicates().process(self._mlist, msg, msgdata)
# Don't test the whole message. The order in the Cc: varies with
# Python version.
ccs = msg.get('cc')
self.assertIn('"last, first" <[email protected]>', ccs)
self.assertIn('"real name (dept)" <[email protected]>', ccs)
del msg['cc']
self.assertMultiLineEqual(msg.as_string(), """\
From: [email protected]
To: [email protected]
Subject: A subject
X-Mailman-Version: X.Y
More things to say.
""")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment