diff options
| -rw-r--r-- | src/mailman/archiving/docs/common.rst | 7 | ||||
| -rw-r--r-- | src/mailman/archiving/prototype.py | 19 | ||||
| -rw-r--r-- | src/mailman/handlers/docs/rfc-2369.rst | 46 | ||||
| -rw-r--r-- | src/mailman/handlers/rfc_2369.py | 7 | ||||
| -rw-r--r-- | src/mailman/handlers/tests/test_rfc_2369.py | 124 |
5 files changed, 137 insertions, 66 deletions
diff --git a/src/mailman/archiving/docs/common.rst b/src/mailman/archiving/docs/common.rst index 30a15e2b3..3fbc72d43 100644 --- a/src/mailman/archiving/docs/common.rst +++ b/src/mailman/archiving/docs/common.rst @@ -21,6 +21,9 @@ header, and one that provides a *permalink* to the specific message object in the archive. This latter is appropriate for the message footer or for the RFC 5064 ``Archived-At:`` header. +If the archiver is not network-accessible, it can return ``None`` and the +headers will not be added. + Mailman defines a draft spec for how list servers and archivers can interoperate. @@ -38,8 +41,8 @@ interoperate. http://lists.example.com/.../test@example.com http://lists.example.com/.../RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE prototype - http://lists.example.com - http://lists.example.com/RSZCG7IGPHFIRW3EMTVMMDNJMNCVCOLE + None + None Sending the message to the archiver diff --git a/src/mailman/archiving/prototype.py b/src/mailman/archiving/prototype.py index 73c296b5d..c17d6b504 100644 --- a/src/mailman/archiving/prototype.py +++ b/src/mailman/archiving/prototype.py @@ -31,7 +31,6 @@ from flufl.lock import Lock, TimeOutError from mailbox import Maildir from mailman.config import config from mailman.interfaces.archiver import IArchiver -from urllib.parse import urljoin from zope.interface import implementer @@ -53,23 +52,14 @@ class Prototype: @staticmethod def list_url(mlist): """See `IArchiver`.""" - return mlist.domain.base_url + # This archiver is not web-accessible, therefore no URL is returned. + return None @staticmethod def permalink(mlist, msg): """See `IArchiver`.""" - # It is the LMTP server's responsibility to ensure that the message has - # a Message-ID-Hash header. For backward compatibility, fall back to - # X-Message-ID-Hash. If the message has neither, then there's no - # permalink. - message_id_hash = msg.get('message-id-hash') - if message_id_hash is None: - message_id_hash = msg.get('x-message-id-hash') - if message_id_hash is None: - return None - if isinstance(message_id_hash, bytes): - message_id_hash = message_id_hash.decode('ascii') - return urljoin(Prototype.list_url(mlist), message_id_hash) + # This archiver is not web-accessible, therefore no URL is returned. + return None @staticmethod def archive_message(mlist, message): @@ -118,5 +108,4 @@ class Prototype: message.get('message-id', 'n/a'))) finally: lock.unlock(unconditionally=True) - # Can we get return the URL of the archived message? return None diff --git a/src/mailman/handlers/docs/rfc-2369.rst b/src/mailman/handlers/docs/rfc-2369.rst index b5a783edc..052aa7156 100644 --- a/src/mailman/handlers/docs/rfc-2369.rst +++ b/src/mailman/handlers/docs/rfc-2369.rst @@ -172,60 +172,14 @@ Archive headers When the mailing list is configured to enable archiving, a `List-Archive` header will be added. - >>> mlist.archive_policy = ArchivePolicy.public - `RFC 5064`_ defines the `Archived-At` header which contains the url to the individual message in the archives. Archivers which don't support pre-calculation of the archive url cannot add the `Archived-At` header. However, other archivers can calculate the url, and do add this header. - >>> config.push('prototype', """ - ... [archiver.prototype] - ... enable: yes - ... [archiver.mail_archive] - ... enable: no - ... [archiver.mhonarc] - ... enable: no - ... [archiver.pipermail] - ... enable: No - ... """) - -The *prototype* archiver can calculate this archive url given a `Message-ID`. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Message-ID: <first> - ... X-Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB - ... - ... """) - >>> process(mlist, msg, {}) - >>> list_headers(msg, only=('list-archive', 'archived-at')) - ---start--- - archived-at: http://lists.example.com/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB - list-archive: <http://lists.example.com> - ---end--- - If the mailing list isn't being archived, neither the `List-Archive` nor `Archived-At` headers will be added. - >>> config.pop('prototype') - >>> mlist.archive_policy = ArchivePolicy.never - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> list_headers(msg) - ---start--- - list-help: <mailto:test-request@example.com?subject=help> - list-id: My test mailing list <test.example.com> - list-post: <mailto:test@example.com> - list-subscribe: <http://lists.example.com/listinfo/test@example.com>, - <mailto:test-join@example.com> - list-unsubscribe: <http://lists.example.com/listinfo/test@example.com>, - <mailto:test-leave@example.com> - ---end--- - .. _`RFC 2919`: http://www.faqs.org/rfcs/rfc2919.html .. _`RFC 2369`: http://www.faqs.org/rfcs/rfc2369.html diff --git a/src/mailman/handlers/rfc_2369.py b/src/mailman/handlers/rfc_2369.py index 8c908128b..8cb586abf 100644 --- a/src/mailman/handlers/rfc_2369.py +++ b/src/mailman/handlers/rfc_2369.py @@ -85,9 +85,10 @@ def process(mlist, msg, msgdata): for archiver in archiver_set.archivers: if not archiver.is_enabled: continue - archiver_url = '<{}>'.format( - archiver.system_archiver.list_url(mlist)) - headers.append(('List-Archive', archiver_url)) + archiver_url = archiver.system_archiver.list_url(mlist) + if archiver_url is not None: + headers.append(('List-Archive', + '<{}>'.format(archiver_url))) permalink = archiver.system_archiver.permalink(mlist, msg) if permalink is not None: headers.append(('Archived-At', permalink)) diff --git a/src/mailman/handlers/tests/test_rfc_2369.py b/src/mailman/handlers/tests/test_rfc_2369.py new file mode 100644 index 000000000..3ee3a7ca2 --- /dev/null +++ b/src/mailman/handlers/tests/test_rfc_2369.py @@ -0,0 +1,124 @@ +# Copyright (C) 2014-2015 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 <http://www.gnu.org/licenses/>. + +"""Test the rfc_2369 handler.""" + +__all__ = [ + 'TestRFC2369', + ] + + +import unittest +from urllib.parse import urljoin + +from zope.interface import implementer + +from mailman.app.lifecycle import create_list +from mailman.config import config +from mailman.interfaces.archiver import ArchivePolicy, IArchiver +from mailman.handlers import rfc_2369 +from mailman.testing.helpers import specialized_message_from_string as mfs +from mailman.testing.layers import ConfigLayer + + +@implementer(IArchiver) +class DummyArchiver: + """An example archiver which does nothing but return URLs.""" + name = 'dummy' + + def list_url(self, mlist): + """See `IArchiver`.""" + return mlist.domain.base_url + + def permalink(self, mlist, msg): + """See `IArchiver`.""" + message_id_hash = msg.get('message-id-hash') + if message_id_hash is None: + return None + return urljoin(self.list_url(mlist), message_id_hash) + + @staticmethod + def archive_message(mlist, message): + return None + + +class TestRFC2369(unittest.TestCase): + """Test the rfc_2369 handler.""" + + layer = ConfigLayer + + def setUp(self): + config.push('no_archivers', """ + [archiver.prototype] + enable: no + [archiver.mail_archive] + enable: no + [archiver.mhonarc] + enable: no + [archiver.pipermail] + enable: no + """) + self.addCleanup(config.pop, 'no_archivers') + self._mlist = create_list('test@example.com') + self._mlist.archive_policy = ArchivePolicy.public + self._msg = mfs("""\ +From: aperson@example.com +Message-ID: <first> +Message-ID-Hash: 4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB + +Dummy text +""") + + def test_add_headers(self): + # Test the addition of the Archived-At and List-Archive headers. + config.push('archiver', """ + [archiver.dummy] + class: {}.DummyArchiver + enable: yes + """.format(DummyArchiver.__module__)) + self.addCleanup(config.pop, 'archiver') + rfc_2369.process(self._mlist, self._msg, {}) + self.assertEqual( + self._msg.get_all('List-Archive'), + ['<http://lists.example.com>']) + self.assertEqual( + self._msg.get_all('Archived-At'), + ['http://lists.example.com/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB']) + + def test_prototype_no_url(self): + # The prototype archiver is not web-based, it must not return URLs + config.push('archiver', """ + [archiver.prototype] + enable: yes + """) + self.addCleanup(config.pop, 'archiver') + rfc_2369.process(self._mlist, self._msg, {}) + self.assertFalse('Archived-At' in self._msg) + self.assertFalse('List-Archive' in self._msg) + + def test_not_archived(self): + # Messages sent to non-archived lists must not get the added headers. + self._mlist.archive_policy = ArchivePolicy.never + config.push('archiver', """ + [archiver.dummy] + class: {}.DummyArchiver + enable: yes + """.format(DummyArchiver.__module__)) + self.addCleanup(config.pop, 'archiver') + rfc_2369.process(self._mlist, self._msg, {}) + self.assertFalse('List-Archive' in self._msg) + self.assertFalse('Archived-At' in self._msg) |
