summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mailman/docs/NEWS.rst2
-rw-r--r--src/mailman/handlers/decorate.py11
-rw-r--r--src/mailman/handlers/rfc_2369.py19
-rw-r--r--src/mailman/handlers/tests/test_decorate.py57
-rw-r--r--src/mailman/handlers/tests/test_rfc_2369.py41
-rw-r--r--src/mailman/model/mailinglist.py4
-rw-r--r--src/mailman/runners/archive.py5
-rw-r--r--src/mailman/runners/tests/test_archiver.py44
8 files changed, 169 insertions, 14 deletions
diff --git a/src/mailman/docs/NEWS.rst b/src/mailman/docs/NEWS.rst
index ed5b09ab8..baf34413b 100644
--- a/src/mailman/docs/NEWS.rst
+++ b/src/mailman/docs/NEWS.rst
@@ -71,6 +71,8 @@ Bugs
* Cross-posting messages held on both lists no longer fails. (Closes #176)
* Don't let unknown charsets crash the "approved" rule. Given by Aurélien
Bompard. (Closes #203)
+ * Don't let crashes in IArchiver plugins break handlers or runners.
+ (Closes #208)
Configuration
-------------
diff --git a/src/mailman/handlers/decorate.py b/src/mailman/handlers/decorate.py
index 8493a0b08..b8cdcb9ae 100644
--- a/src/mailman/handlers/decorate.py
+++ b/src/mailman/handlers/decorate.py
@@ -40,6 +40,7 @@ from zope.interface import implementer
log = logging.getLogger('mailman.error')
+alog = logging.getLogger('mailman.archiver')
@@ -64,8 +65,14 @@ def process(mlist, msg, msgdata):
# the $<archive-name>_url placeholder for every enabled archiver.
for archiver in IListArchiverSet(mlist).archivers:
if archiver.is_enabled:
- # Get the permalink of the message from the archiver.
- archive_url = archiver.system_archiver.permalink(mlist, msg)
+ # Get the permalink of the message from the archiver. Watch out
+ # for exceptions in the archiver plugin.
+ try:
+ archive_url = archiver.system_archiver.permalink(mlist, msg)
+ except Exception:
+ alog.exception('Exception in "{}" archiver'.format(
+ archiver.system_archiver.name))
+ archive_url = None
if archive_url is not None:
placeholder = '{}_url'.format(archiver.system_archiver.name)
d[placeholder] = archive_url
diff --git a/src/mailman/handlers/rfc_2369.py b/src/mailman/handlers/rfc_2369.py
index 58037a735..4f172bde1 100644
--- a/src/mailman/handlers/rfc_2369.py
+++ b/src/mailman/handlers/rfc_2369.py
@@ -22,6 +22,8 @@ __all__ = [
]
+import logging
+
from email.utils import formataddr
from mailman.core.i18n import _
from mailman.handlers.cook_headers import uheader
@@ -33,6 +35,8 @@ from zope.interface import implementer
CONTINUATION = ',\n\t'
+log = logging.getLogger('mailman.archiver')
+
def process(mlist, msg, msgdata):
@@ -85,11 +89,22 @@ def process(mlist, msg, msgdata):
for archiver in archiver_set.archivers:
if not archiver.is_enabled:
continue
- archiver_url = archiver.system_archiver.list_url(mlist)
+ # Watch out for exceptions in the archiver plugin.
+ try:
+ archiver_url = archiver.system_archiver.list_url(mlist)
+ except Exception:
+ log.exception('Exception in "{}" archiver'.format(
+ archiver.system_archiver.name))
+ archiver_url = None
if archiver_url is not None:
headers.append(('List-Archive',
'<{}>'.format(archiver_url)))
- permalink = archiver.system_archiver.permalink(mlist, msg)
+ try:
+ permalink = archiver.system_archiver.permalink(mlist, msg)
+ except Exception:
+ log.exception('Exception in "{}" archiver'.format(
+ archiver.system_archiver.name))
+ permalink = None
if permalink is not None:
headers.append(('Archived-At', '<{}>'.format(permalink)))
# XXX RFC 2369 also defines a List-Owner header which we are not currently
diff --git a/src/mailman/handlers/tests/test_decorate.py b/src/mailman/handlers/tests/test_decorate.py
index 84adceb9a..2a44d4186 100644
--- a/src/mailman/handlers/tests/test_decorate.py
+++ b/src/mailman/handlers/tests/test_decorate.py
@@ -18,6 +18,7 @@
"""Test the decorate handler."""
__all__ = [
+ 'TestBrokenPermalink',
'TestDecorate',
]
@@ -29,7 +30,8 @@ from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.handlers import decorate
from mailman.interfaces.archiver import IArchiver
-from mailman.testing.helpers import specialized_message_from_string as mfs
+from mailman.testing.helpers import (
+ LogFileMark, specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
from tempfile import TemporaryDirectory
from zope.interface import implementer
@@ -48,6 +50,16 @@ class TestArchiver:
return 'http://example.com/link_to_message'
+@implementer(IArchiver)
+class BrokenArchiver:
+ name = 'broken'
+ is_enabled = True
+
+ @staticmethod
+ def permalink(mlist, msg):
+ raise RuntimeError('Cannot get permalink')
+
+
class TestDecorate(unittest.TestCase):
"""Test the cook_headers handler."""
@@ -116,3 +128,46 @@ This is a test message.
decorate.process(self._mlist, self._msg, {})
self.assertIn('http://example.com/link_to_message',
self._msg.as_string())
+
+
+class TestBrokenPermalink(unittest.TestCase):
+ layer = ConfigLayer
+
+ def setUp(self):
+ self._mlist = create_list('ant@example.com')
+ self._msg = mfs("""\
+To: ant@example.com
+From: aperson@example.com
+Message-ID: <alpha>
+Content-Type: text/plain;
+
+This is a test message.
+""")
+ temporary_dir = TemporaryDirectory()
+ self.addCleanup(temporary_dir.cleanup)
+ template_dir = temporary_dir.name
+ config.push('archiver', """\
+ [paths.testing]
+ template_dir: {}
+ [archiver.testarchiver]
+ class: mailman.handlers.tests.test_decorate.BrokenArchiver
+ enable: yes
+ """.format(template_dir))
+ self.addCleanup(config.pop, 'archiver')
+
+ def test_broken_permalink(self):
+ # GL issue #208 - IArchive messages raise exceptions, breaking the
+ # rfc-2369 handler and shunting messages.
+ site_dir = os.path.join(config.TEMPLATE_DIR, 'site', 'en')
+ os.makedirs(site_dir)
+ footer_path = os.path.join(site_dir, 'myfooter.txt')
+ with open(footer_path, 'w', encoding='utf-8') as fp:
+ print('${broken_url}', file=fp)
+ self._mlist.footer_uri = 'mailman:///myfooter.txt'
+ self._mlist.preferred_language = 'en'
+ mark = LogFileMark('mailman.archiver')
+ decorate.process(self._mlist, self._msg, {})
+ log_messages = mark.read()
+ self.assertNotIn('http:', self._msg.as_string())
+ self.assertIn('Exception in "broken" archiver', log_messages)
+ self.assertIn('RuntimeError: Cannot get permalink', log_messages)
diff --git a/src/mailman/handlers/tests/test_rfc_2369.py b/src/mailman/handlers/tests/test_rfc_2369.py
index 110024c16..cbbaf2153 100644
--- a/src/mailman/handlers/tests/test_rfc_2369.py
+++ b/src/mailman/handlers/tests/test_rfc_2369.py
@@ -28,7 +28,8 @@ from mailman.app.lifecycle import create_list
from mailman.config import config
from mailman.handlers import rfc_2369
from mailman.interfaces.archiver import ArchivePolicy, IArchiver
-from mailman.testing.helpers import specialized_message_from_string as mfs
+from mailman.testing.helpers import (
+ LogFileMark, specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
from urllib.parse import urljoin
from zope.interface import implementer
@@ -56,6 +57,23 @@ class DummyArchiver:
return None
+@implementer(IArchiver)
+class BrokenArchiver:
+ """An archiver that has some broken methods."""
+
+ name = 'broken'
+
+ def list_url(self, mlist):
+ raise RuntimeError('Cannot get list URL')
+
+ def permalink(self, mlist, msg):
+ raise RuntimeError('Cannot get permalink')
+
+ @staticmethod
+ def archive_message(mlist, message):
+ raise RuntimeError('Cannot archive message')
+
+
class TestRFC2369(unittest.TestCase):
"""Test the rfc_2369 handler."""
@@ -123,3 +141,24 @@ Dummy text
rfc_2369.process(self._mlist, self._msg, {})
self.assertNotIn('List-Archive', self._msg)
self.assertNotIn('Archived-At', self._msg)
+
+ def test_broken_archiver(self):
+ # GL issue #208 - IArchive messages raise exceptions, breaking the
+ # rfc-2369 handler and shunting messages.
+ config.push('archiver', """
+ [archiver.broken]
+ class: {}.BrokenArchiver
+ enable: yes
+ """.format(BrokenArchiver.__module__))
+ self.addCleanup(config.pop, 'archiver')
+ mark = LogFileMark('mailman.archiver')
+ rfc_2369.process(self._mlist, self._msg, {})
+ log_messages = mark.read()
+ # Because .list_url() was broken, there will be no List-Archive header.
+ self.assertIsNone(self._msg.get('list-archive'))
+ self.assertIn('Exception in "broken" archiver', log_messages)
+ self.assertIn('RuntimeError: Cannot get list URL', log_messages)
+ # Because .permalink() was broken, there will be no Archived-At header.
+ self.assertIsNone(self._msg.get('archived-at'))
+ self.assertIn('Exception in "broken" archiver', log_messages)
+ self.assertIn('RuntimeError: Cannot get permalink', log_messages)
diff --git a/src/mailman/model/mailinglist.py b/src/mailman/model/mailinglist.py
index 07b5b4fed..5b015e5b5 100644
--- a/src/mailman/model/mailinglist.py
+++ b/src/mailman/model/mailinglist.py
@@ -605,7 +605,7 @@ class ListArchiverSet:
for archiver_name in system_archivers:
exists = store.query(ListArchiver).filter(
ListArchiver.mailing_list == mailing_list,
- ListArchiver.name == archiver_name).first()
+ ListArchiver.name == archiver_name).one_or_none()
if exists is None:
store.add(ListArchiver(mailing_list, archiver_name,
system_archivers[archiver_name]))
@@ -621,7 +621,7 @@ class ListArchiverSet:
def get(self, store, archiver_name):
return store.query(ListArchiver).filter(
ListArchiver.mailing_list == self._mailing_list,
- ListArchiver.name == archiver_name).first()
+ ListArchiver.name == archiver_name).one_or_none()
@implementer(IHeaderMatch)
diff --git a/src/mailman/runners/archive.py b/src/mailman/runners/archive.py
index 13232b93f..240d991f6 100644
--- a/src/mailman/runners/archive.py
+++ b/src/mailman/runners/archive.py
@@ -35,7 +35,7 @@ from mailman.utilities.datetime import RFC822_DATE_FMT, now
from mailman.interfaces.mailinglist import IListArchiverSet
-log = logging.getLogger('mailman.error')
+log = logging.getLogger('mailman.archiver')
@@ -106,4 +106,5 @@ class ArchiveRunner(Runner):
try:
archiver.system_archiver.archive_message(mlist, msg_copy)
except Exception:
- log.exception('Broken archiver: %s' % archiver.name)
+ log.exception('Exception in "{}" archiver'.format(
+ archiver.name))
diff --git a/src/mailman/runners/tests/test_archiver.py b/src/mailman/runners/tests/test_archiver.py
index da0adf439..8aaaec86e 100644
--- a/src/mailman/runners/tests/test_archiver.py
+++ b/src/mailman/runners/tests/test_archiver.py
@@ -32,7 +32,7 @@ from mailman.interfaces.archiver import IArchiver
from mailman.interfaces.mailinglist import IListArchiverSet
from mailman.runners.archive import ArchiveRunner
from mailman.testing.helpers import (
- configuration, make_testable_runner,
+ LogFileMark, configuration, get_queue_messages, make_testable_runner,
specialized_message_from_string as mfs)
from mailman.testing.layers import ConfigLayer
from mailman.utilities.datetime import RFC822_DATE_FMT, factory, now
@@ -63,6 +63,23 @@ class DummyArchiver:
return path
+@implementer(IArchiver)
+class BrokenArchiver:
+ """An archiver that has some broken methods."""
+
+ name = 'broken'
+
+ def list_url(self, mlist):
+ raise RuntimeError('Cannot get list URL')
+
+ def permalink(self, mlist, msg):
+ raise RuntimeError('Cannot get permalink')
+
+ @staticmethod
+ def archive_message(mlist, message):
+ raise RuntimeError('Cannot archive message')
+
+
class TestArchiveRunner(unittest.TestCase):
"""Test the archive runner."""
@@ -77,6 +94,9 @@ class TestArchiveRunner(unittest.TestCase):
[archiver.dummy]
class: mailman.runners.tests.test_archiver.DummyArchiver
enable: no
+ [archiver.broken]
+ class: mailman.runners.tests.test_archiver.BrokenArchiver
+ enable: no
[archiver.prototype]
enable: no
[archiver.mhonarc]
@@ -84,6 +104,7 @@ class TestArchiveRunner(unittest.TestCase):
[archiver.mail_archive]
enable: no
""")
+ self.addCleanup(config.pop, 'dummy')
self._archiveq = config.switchboards['archive']
self._msg = mfs("""\
From: aperson@example.com
@@ -97,9 +118,6 @@ First post!
self._runner = make_testable_runner(ArchiveRunner)
IListArchiverSet(self._mlist).get('dummy').is_enabled = True
- def tearDown(self):
- config.pop('dummy')
-
@configuration('archiver.dummy', enable='yes')
def test_archive_runner(self):
# Ensure that the archive runner ends up archiving the message.
@@ -247,3 +265,21 @@ First post!
listid=self._mlist.list_id)
self._runner.run()
self.assertEqual(os.listdir(config.MESSAGES_DIR), [])
+
+ @configuration('archiver.broken', enable='yes')
+ def test_broken_archiver(self):
+ # GL issue #208 - IArchive messages raise exceptions, breaking the
+ # rfc-2369 handler and shunting messages.
+ mark = LogFileMark('mailman.archiver')
+ self._archiveq.enqueue(
+ self._msg, {},
+ listid=self._mlist.list_id,
+ received_time=now())
+ IListArchiverSet(self._mlist).get('broken').is_enabled = True
+ self._runner.run()
+ # The archiver is broken, so there are no messages on the file system,
+ # but there is a log message and the message was not shunted.
+ log_messages = mark.read()
+ self.assertIn('Exception in "broken" archiver', log_messages)
+ self.assertIn('RuntimeError: Cannot archive message', log_messages)
+ self.assertEqual(len(get_queue_messages('shunt')), 0)