diff options
| -rw-r--r-- | src/mailman/handlers/cook_headers.py | 8 | ||||
| -rw-r--r-- | src/mailman/handlers/tests/test_cook_headers.py | 13 | ||||
| -rw-r--r-- | src/mailman/rest/listconf.py | 10 | ||||
| -rw-r--r-- | src/mailman/rest/tests/test_listconf.py | 12 |
4 files changed, 41 insertions, 2 deletions
diff --git a/src/mailman/handlers/cook_headers.py b/src/mailman/handlers/cook_headers.py index 297155d90..f43e3ad5a 100644 --- a/src/mailman/handlers/cook_headers.py +++ b/src/mailman/handlers/cook_headers.py @@ -18,6 +18,7 @@ """Cook a message's headers.""" import re +import logging from email.header import Header from email.utils import formataddr, getaddresses, parseaddr @@ -28,6 +29,7 @@ from mailman.interfaces.mailinglist import Personalization, ReplyToMunging from mailman.version import VERSION from zope.interface import implementer +log = logging.getLogger('mailman.error') COMMASPACE = ', ' MAXLINELEN = 78 @@ -42,6 +44,8 @@ def uheader(mlist, s, header_name=None, continuation_ws='\t', maxlinelen=None): there is and the charset is us-ascii then we use iso-8859-1 instead. If the string is ascii only we use 'us-ascii' if another charset is specified. + + If the header contains a newline, truncate it (see GL#273). """ charset = mlist.preferred_language.charset if NONASCII.search(s): @@ -51,6 +55,10 @@ def uheader(mlist, s, header_name=None, continuation_ws='\t', maxlinelen=None): else: # there is no non-ascii so ... charset = 'us-ascii' + if '\n' in s: + s = '{} [...]'.format(s.split('\n')[0]) + log.warning('Header {} contains a newline, truncating it.'.format( + header_name, s)) return Header(s, charset, maxlinelen, header_name, continuation_ws) diff --git a/src/mailman/handlers/tests/test_cook_headers.py b/src/mailman/handlers/tests/test_cook_headers.py index fad3f49d9..f9def6ca8 100644 --- a/src/mailman/handlers/tests/test_cook_headers.py +++ b/src/mailman/handlers/tests/test_cook_headers.py @@ -23,7 +23,7 @@ from mailman.app.lifecycle import create_list from mailman.handlers import cook_headers from mailman.interfaces.member import DeliveryMode from mailman.testing.helpers import ( - get_queue_messages, make_digest_messages, subscribe) + get_queue_messages, LogFileMark, make_digest_messages, subscribe) from mailman.testing.layers import ConfigLayer @@ -50,3 +50,14 @@ class TestCookHeaders(unittest.TestCase): except AttributeError as error: # LP: #1130696 would raise an AttributeError on .sender self.fail(error) + + def test_uheader_multiline(self): + # Multiline headers should be truncated (GL#273). + mark = LogFileMark('mailman.error') + header = cook_headers.uheader( + self._mlist, 'A multiline\ndescription', 'X-Header') + self.assertEqual( + header.encode(), 'A multiline [...]') + log_messages = mark.read() + self.assertIn( + 'Header X-Header contains a newline, truncating it', log_messages) diff --git a/src/mailman/rest/listconf.py b/src/mailman/rest/listconf.py index 0982e7970..c49228c39 100644 --- a/src/mailman/rest/listconf.py +++ b/src/mailman/rest/listconf.py @@ -101,6 +101,14 @@ def password_bytes_validator(value): return config.password_context.encrypt(value).encode('utf-8') +def no_newlines_validator(value): + value = str(value) + if '\n' in value: + raise ValueError( + 'This value must be on a single line: {}'.format(value)) + return value + + # This is the list of IMailingList attributes that are exposed through the # REST API. The values of the keys are the GetterSetter instance holding the # decoder used to convert the web request string to an internally valid value. @@ -136,7 +144,7 @@ ATTRIBUTES = dict( created_at=GetterSetter(None), default_member_action=GetterSetter(enum_validator(Action)), default_nonmember_action=GetterSetter(enum_validator(Action)), - description=GetterSetter(str), + description=GetterSetter(no_newlines_validator), digest_last_sent_at=GetterSetter(None), digest_send_periodic=GetterSetter(as_boolean), digest_size_threshold=GetterSetter(float), diff --git a/src/mailman/rest/tests/test_listconf.py b/src/mailman/rest/tests/test_listconf.py index 9bc3486be..54918282f 100644 --- a/src/mailman/rest/tests/test_listconf.py +++ b/src/mailman/rest/tests/test_listconf.py @@ -437,3 +437,15 @@ class TestConfiguration(unittest.TestCase): dict(advertised=True), 'PATCH') self.assertTrue(self._mlist.advertised) + + def test_patch_bad_description_value(self): + # GL issue #273 + with self.assertRaises(HTTPError) as cm: + call_api( + 'http://localhost:9001/3.0/lists/ant.example.com/config' + '/description', + dict(description='This\ncontains\nnewlines.'), + 'PATCH') + self.assertEqual(cm.exception.code, 400) + self.assertEqual(cm.exception.reason, + b'Cannot convert parameters: description') |
