summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/handlers/cook_headers.py8
-rw-r--r--src/mailman/handlers/tests/test_cook_headers.py13
-rw-r--r--src/mailman/rest/listconf.py10
-rw-r--r--src/mailman/rest/tests/test_listconf.py12
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')