diff options
| author | Barry Warsaw | 2012-03-23 19:25:27 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2012-03-23 19:25:27 -0400 |
| commit | 25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc (patch) | |
| tree | f51fe7931545e23058cdb65595c7caed9b43bb0e /src/mailman/pipeline/docs | |
| parent | aa2d0ad067adfd2515ed3c256cd0bca296058479 (diff) | |
| parent | e005e1b12fa0bd82d2e126df476b5505b440ce36 (diff) | |
| download | mailman-25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc.tar.gz mailman-25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc.tar.zst mailman-25a392bf5c1a8d4d2bc63d51697350fc7dbd48bc.zip | |
Merge the 'owners' branch. Posting to a list's -owner address now works as
expected.
Diffstat (limited to 'src/mailman/pipeline/docs')
19 files changed, 0 insertions, 3094 deletions
diff --git a/src/mailman/pipeline/docs/ack-headers.rst b/src/mailman/pipeline/docs/ack-headers.rst deleted file mode 100644 index dba2169e2..000000000 --- a/src/mailman/pipeline/docs/ack-headers.rst +++ /dev/null @@ -1,43 +0,0 @@ -====================== -Acknowledgment headers -====================== - -Messages that flow through the global pipeline get their headers `cooked`, -which basically means that their headers go through several mostly unrelated -transformations. Some headers get added, others get changed. Some of these -changes depend on mailing list settings and others depend on how the message -is getting sent through the system. We'll take things one-by-one. - - >>> mlist = create_list('_xtest@example.com') - >>> mlist.subject_prefix = '' - -When the message's metadata has a `noack` key set, an ``X-Ack: no`` header is -added. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... A message of great import. - ... """) - - >>> from mailman.pipeline.cook_headers import process - >>> process(mlist, msg, dict(noack=True)) - >>> print msg.as_string() - From: aperson@example.com - X-Ack: no - ... - -Any existing ``X-Ack`` header in the original message is removed. - - >>> msg = message_from_string("""\ - ... X-Ack: yes - ... From: aperson@example.com - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, dict(noack=True)) - >>> print msg.as_string() - From: aperson@example.com - X-Ack: no - ... diff --git a/src/mailman/pipeline/docs/acknowledge.rst b/src/mailman/pipeline/docs/acknowledge.rst deleted file mode 100644 index 479aa4ea6..000000000 --- a/src/mailman/pipeline/docs/acknowledge.rst +++ /dev/null @@ -1,174 +0,0 @@ -====================== -Message acknowledgment -====================== - -When a user posts a message to a mailing list, and that user has chosen to -receive acknowledgments of their postings, Mailman will sent them such an -acknowledgment. -:: - - >>> mlist = create_list('test@example.com') - >>> mlist.display_name = 'Test' - >>> mlist.preferred_language = 'en' - >>> # XXX This will almost certainly change once we've worked out the web - >>> # space layout for mailing lists now. - - >>> # Ensure that the virgin queue is empty, since we'll be checking this - >>> # for new auto-response messages. - >>> from mailman.testing.helpers import get_queue_messages - >>> get_queue_messages('virgin') - [] - -Subscribe a user to the mailing list. -:: - - >>> from mailman.interfaces.usermanager import IUserManager - >>> from zope.component import getUtility - >>> user_manager = getUtility(IUserManager) - - >>> from mailman.interfaces.member import MemberRole - >>> user_1 = user_manager.create_user('aperson@example.com') - >>> address_1 = list(user_1.addresses)[0] - >>> mlist.subscribe(address_1, MemberRole.member) - <Member: aperson@example.com on test@example.com as MemberRole.member> - - -Non-member posts -================ - -Non-members can't get acknowledgments of their posts to the mailing list. -:: - - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... - ... """) - - >>> handler = config.handlers['acknowledge'] - >>> handler.process(mlist, msg, {}) - >>> get_queue_messages('virgin') - [] - -We can also specify the original sender in the message's metadata. If that -person is also not a member, no acknowledgment will be sent either. - - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... - ... """) - >>> handler.process(mlist, msg, - ... dict(original_sender='cperson@example.com')) - >>> get_queue_messages('virgin') - [] - - -No acknowledgment requested -=========================== - -Unless the user has requested acknowledgments, they will not get one. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> handler.process(mlist, msg, {}) - >>> get_queue_messages('virgin') - [] - -Similarly if the original sender is specified in the message metadata, and -that sender is a member but not one who has requested acknowledgments, none -will be sent. -:: - - >>> user_2 = user_manager.create_user('dperson@example.com') - >>> address_2 = list(user_2.addresses)[0] - >>> mlist.subscribe(address_2, MemberRole.member) - <Member: dperson@example.com on test@example.com as MemberRole.member> - - >>> handler.process(mlist, msg, - ... dict(original_sender='dperson@example.com')) - >>> get_queue_messages('virgin') - [] - - -Requested acknowledgments -========================= - -If the member requests acknowledgments, Mailman will send them one when they -post to the mailing list. - - >>> user_1.preferences.acknowledge_posts = True - -The receipt will include the original message's subject in the response body, - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: Something witty and insightful - ... - ... """) - >>> handler.process(mlist, msg, {}) - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - >>> dump_msgdata(messages[0].msgdata) - _parsemsg : False - listname : test@example.com - nodecorate : True - recipients : set([u'aperson@example.com']) - reduced_list_headers: True - ... - >>> print messages[0].msg.as_string() - ... - MIME-Version: 1.0 - ... - Subject: Test post acknowledgment - From: test-bounces@example.com - To: aperson@example.com - ... - Precedence: bulk - <BLANKLINE> - Your message entitled - <BLANKLINE> - Something witty and insightful - <BLANKLINE> - was successfully received by the Test mailing list. - <BLANKLINE> - List info page: http://lists.example.com/listinfo/test@example.com - Your preferences: http://example.com/aperson@example.com - <BLANKLINE> - -If there is no subject, then the receipt will use a generic message. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> handler.process(mlist, msg, {}) - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - >>> dump_msgdata(messages[0].msgdata) - _parsemsg : False - listname : test@example.com - nodecorate : True - recipients : set([u'aperson@example.com']) - reduced_list_headers: True - ... - >>> print messages[0].msg.as_string() - MIME-Version: 1.0 - ... - Subject: Test post acknowledgment - From: test-bounces@example.com - To: aperson@example.com - ... - Precedence: bulk - <BLANKLINE> - Your message entitled - <BLANKLINE> - (no subject) - <BLANKLINE> - was successfully received by the Test mailing list. - <BLANKLINE> - List info page: http://lists.example.com/listinfo/test@example.com - Your preferences: http://example.com/aperson@example.com - <BLANKLINE> diff --git a/src/mailman/pipeline/docs/after-delivery.rst b/src/mailman/pipeline/docs/after-delivery.rst deleted file mode 100644 index c3e393cf2..000000000 --- a/src/mailman/pipeline/docs/after-delivery.rst +++ /dev/null @@ -1,30 +0,0 @@ -============== -After delivery -============== - -After a message is delivered, or more correctly, after it has been processed -by the rest of the handlers in the incoming queue pipeline, a couple of -bookkeeping pieces of information are updated. - - >>> import datetime - >>> mlist = create_list('_xtest@example.com') - >>> post_time = datetime.datetime.now() - datetime.timedelta(minutes=10) - >>> mlist.last_post_time = post_time - >>> mlist.post_id = 10 - -Processing a message with this handler updates the last_post_time and post_id -attributes. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... Something interesting. - ... """) - - >>> handler = config.handlers['after-delivery'] - >>> handler.process(mlist, msg, {}) - >>> mlist.last_post_time > post_time - True - >>> mlist.post_id - 11 diff --git a/src/mailman/pipeline/docs/archives.rst b/src/mailman/pipeline/docs/archives.rst deleted file mode 100644 index 323d121e8..000000000 --- a/src/mailman/pipeline/docs/archives.rst +++ /dev/null @@ -1,133 +0,0 @@ -======== -Archives -======== - -Updating the archives with posted messages is handled by a separate queue, -which allows for better memory management and prevents blocking the main -delivery processes while messages are archived. This also allows external -archivers to work in a separate process from the main Mailman delivery -processes. - - >>> handler = config.handlers['to-archive'] - >>> mlist = create_list('_xtest@example.com') - >>> switchboard = config.switchboards['archive'] - -A helper function. - - >>> def clear(): - ... for filebase in switchboard.files: - ... msg, msgdata = switchboard.dequeue(filebase) - ... switchboard.finish(filebase) - -The purpose of this handler is to make a simple decision as to whether the -message should get archived and if so, to drop the message in the archiving -queue. Really the most important things are to determine when a message -should *not* get archived. - -For example, no digests should ever get archived. - - >>> mlist.archive = True - >>> msg = message_from_string("""\ - ... Subject: A sample message - ... - ... A message of great import. - ... """) - >>> handler.process(mlist, msg, dict(isdigest=True)) - >>> switchboard.files - [] - -If the mailing list is not configured to archive, then even regular deliveries -won't be archived. - - >>> mlist.archive = False - >>> handler.process(mlist, msg, {}) - >>> switchboard.files - [] - -There are two de-facto standards for a message to indicate that it does not -want to be archived. We've seen both in the wild so both are supported. The -``X-No-Archive:`` header can be used to indicate that the message should not -be archived. Confusingly, this header's value is actually ignored. - - >>> mlist.archive = True - >>> msg = message_from_string("""\ - ... Subject: A sample message - ... X-No-Archive: YES - ... - ... A message of great import. - ... """) - >>> handler.process(mlist, msg, dict(isdigest=True)) - >>> switchboard.files - [] - -Even a ``no`` value will stop the archiving of the message. - - >>> msg = message_from_string("""\ - ... Subject: A sample message - ... X-No-Archive: No - ... - ... A message of great import. - ... """) - >>> handler.process(mlist, msg, dict(isdigest=True)) - >>> switchboard.files - [] - -Another header that's been observed is the ``X-Archive:`` header. Here, the -header's case folded value must be ``no`` in order to prevent archiving. - - >>> msg = message_from_string("""\ - ... Subject: A sample message - ... X-Archive: No - ... - ... A message of great import. - ... """) - >>> handler.process(mlist, msg, dict(isdigest=True)) - >>> switchboard.files - [] - -But if the value is ``yes``, then the message will be archived. - - >>> msg = message_from_string("""\ - ... Subject: A sample message - ... X-Archive: Yes - ... - ... A message of great import. - ... """) - >>> handler.process(mlist, msg, {}) - >>> len(switchboard.files) - 1 - >>> filebase = switchboard.files[0] - >>> qmsg, qdata = switchboard.dequeue(filebase) - >>> switchboard.finish(filebase) - >>> print qmsg.as_string() - Subject: A sample message - X-Archive: Yes - <BLANKLINE> - A message of great import. - <BLANKLINE> - >>> dump_msgdata(qdata) - _parsemsg: False - version : 3 - -Without either archiving header, and all other things being the same, the -message will get archived. - - >>> msg = message_from_string("""\ - ... Subject: A sample message - ... - ... A message of great import. - ... """) - >>> handler.process(mlist, msg, {}) - >>> len(switchboard.files) - 1 - >>> filebase = switchboard.files[0] - >>> qmsg, qdata = switchboard.dequeue(filebase) - >>> switchboard.finish(filebase) - >>> print qmsg.as_string() - Subject: A sample message - <BLANKLINE> - A message of great import. - <BLANKLINE> - >>> dump_msgdata(qdata) - _parsemsg: False - version : 3 diff --git a/src/mailman/pipeline/docs/avoid-duplicates.rst b/src/mailman/pipeline/docs/avoid-duplicates.rst deleted file mode 100644 index 1e46793c2..000000000 --- a/src/mailman/pipeline/docs/avoid-duplicates.rst +++ /dev/null @@ -1,175 +0,0 @@ -================ -Avoid duplicates -================ - -This handler implements several strategies to reduce the reception of -duplicate messages. It does this by removing certain recipients from the list -of recipients calculated earlier. - - >>> mlist = create_list('_xtest@example.com') - -Create some members we're going to use. -:: - - >>> from mailman.interfaces.usermanager import IUserManager - >>> from zope.component import getUtility - >>> user_manager = getUtility(IUserManager) - - >>> address_a = user_manager.create_address('aperson@example.com') - >>> address_b = user_manager.create_address('bperson@example.com') - - >>> from mailman.interfaces.member import MemberRole - >>> member_a = mlist.subscribe(address_a, MemberRole.member) - >>> member_b = mlist.subscribe(address_b, MemberRole.member) - >>> # This is the message metadata dictionary as it would be produced by - >>> # the CalcRecips handler. - >>> recips = dict( - ... recipients=['aperson@example.com', 'bperson@example.com']) - - -Short circuiting -================ - -The module short-circuits if there are no recipients. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: A message of great import - ... - ... Something - ... """) - >>> msgdata = {} - - >>> handler = config.handlers['avoid-duplicates'] - >>> handler.process(mlist, msg, msgdata) - >>> msgdata - {} - >>> print msg.as_string() - From: aperson@example.com - Subject: A message of great import - <BLANKLINE> - Something - <BLANKLINE> - - -Suppressing the list copy -========================= - -Members can elect not to receive a list copy of any message on which they are -explicitly named as a recipient. This is done by setting their -``receive_list_copy`` preference to ``False``. However, if they aren't -mentioned in one of the recipient headers (i.e. ``To``, ``CC``, ``Resent-To``, -or ``Resent-CC``), then they will get a list copy. - - >>> member_a.preferences.receive_list_copy = False - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... - ... Something of great import. - ... """) - >>> msgdata = recips.copy() - >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recipients']) - [u'aperson@example.com', u'bperson@example.com'] - >>> print msg.as_string() - From: Claire Person <cperson@example.com> - <BLANKLINE> - Something of great import. - <BLANKLINE> - -If they're mentioned on the ``CC`` line, they won't get a list copy. - - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... CC: aperson@example.com - ... - ... Something of great import. - ... """) - >>> msgdata = recips.copy() - >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recipients']) - [u'bperson@example.com'] - >>> print msg.as_string() - From: Claire Person <cperson@example.com> - CC: aperson@example.com - <BLANKLINE> - Something of great import. - <BLANKLINE> - -But if they're mentioned on the ``CC`` line and have ``receive_list_copy`` set -to ``True`` (the default), then they still get a list copy. - - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... CC: bperson@example.com - ... - ... Something of great import. - ... """) - >>> msgdata = recips.copy() - >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recipients']) - [u'aperson@example.com', u'bperson@example.com'] - >>> print msg.as_string() - From: Claire Person <cperson@example.com> - CC: bperson@example.com - <BLANKLINE> - Something of great import. - <BLANKLINE> - -Other headers checked for recipients include the ``To``... - - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... To: aperson@example.com - ... - ... Something of great import. - ... """) - >>> msgdata = recips.copy() - >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recipients']) - [u'bperson@example.com'] - >>> print msg.as_string() - From: Claire Person <cperson@example.com> - To: aperson@example.com - <BLANKLINE> - Something of great import. - <BLANKLINE> - -... ``Resent-To`` ... - - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... Resent-To: aperson@example.com - ... - ... Something of great import. - ... """) - >>> msgdata = recips.copy() - >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recipients']) - [u'bperson@example.com'] - >>> print msg.as_string() - From: Claire Person <cperson@example.com> - Resent-To: aperson@example.com - <BLANKLINE> - Something of great import. - <BLANKLINE> - -...and ``Resent-CC`` headers. - - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... Resent-Cc: aperson@example.com - ... - ... Something of great import. - ... """) - >>> msgdata = recips.copy() - >>> handler.process(mlist, msg, msgdata) - >>> sorted(msgdata['recipients']) - [u'bperson@example.com'] - >>> print msg.as_string() - From: Claire Person <cperson@example.com> - Resent-Cc: aperson@example.com - <BLANKLINE> - Something of great import. - <BLANKLINE> diff --git a/src/mailman/pipeline/docs/calc-recips.rst b/src/mailman/pipeline/docs/calc-recips.rst deleted file mode 100644 index 6dca85816..000000000 --- a/src/mailman/pipeline/docs/calc-recips.rst +++ /dev/null @@ -1,114 +0,0 @@ -====================== -Calculating recipients -====================== - -Every message that makes it through to the list membership gets sent to a set -of recipient addresses. These addresses are calculated by one of the handler -modules and depends on a host of factors. - - >>> mlist = create_list('_xtest@example.com') - -Recipients are calculate from the list members, so add a bunch of members to -start out with. First, create a bunch of addresses... -:: - - >>> from mailman.interfaces.usermanager import IUserManager - >>> from zope.component import getUtility - >>> user_manager = getUtility(IUserManager) - - >>> address_a = user_manager.create_address('aperson@example.com') - >>> address_b = user_manager.create_address('bperson@example.com') - >>> address_c = user_manager.create_address('cperson@example.com') - >>> address_d = user_manager.create_address('dperson@example.com') - >>> address_e = user_manager.create_address('eperson@example.com') - >>> address_f = user_manager.create_address('fperson@example.com') - -...then subscribe these addresses to the mailing list as members... - - >>> from mailman.interfaces.member import MemberRole - >>> member_a = mlist.subscribe(address_a, MemberRole.member) - >>> member_b = mlist.subscribe(address_b, MemberRole.member) - >>> member_c = mlist.subscribe(address_c, MemberRole.member) - >>> member_d = mlist.subscribe(address_d, MemberRole.member) - >>> member_e = mlist.subscribe(address_e, MemberRole.member) - >>> member_f = mlist.subscribe(address_f, MemberRole.member) - -...then make some of the members digest members. - - >>> from mailman.core.constants import DeliveryMode - >>> member_d.preferences.delivery_mode = DeliveryMode.plaintext_digests - >>> member_e.preferences.delivery_mode = DeliveryMode.mime_digests - >>> member_f.preferences.delivery_mode = DeliveryMode.summary_digests - - -Short-circuiting -================ - -Sometimes, the list of recipients already exists in the message metadata. -This can happen for example, when a message was previously delivered to some -but not all of the recipients. -:: - - >>> msg = message_from_string("""\ - ... From: Xavier Person <xperson@example.com> - ... - ... Something of great import. - ... """) - >>> recipients = set(('qperson@example.com', 'zperson@example.com')) - >>> msgdata = dict(recipients=recipients) - - >>> handler = config.handlers['calculate-recipients'] - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - qperson@example.com - zperson@example.com - - -Regular delivery recipients -=========================== - -Regular delivery recipients are those people who get messages from the list as -soon as they are posted. In other words, these folks are not digest members. - - >>> msgdata = {} - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - aperson@example.com - bperson@example.com - cperson@example.com - -Members can elect not to receive a list copy of their own postings. - - >>> member_c.preferences.receive_own_postings = False - >>> msg = message_from_string("""\ - ... From: Claire Person <cperson@example.com> - ... - ... Something of great import. - ... """) - >>> msgdata = {} - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - aperson@example.com - bperson@example.com - -Members can also elect not to receive a list copy of any message on which they -are explicitly named as a recipient. However, see the `avoid duplicates`_ -handler for details. - - -Digest recipients -================= - -XXX Test various digest deliveries. - - -Urgent messages -=============== - -XXX Test various urgent deliveries: - * test_urgent_moderator() - * test_urgent_admin() - * test_urgent_reject() - - -.. _`avoid duplicates`: avoid-duplicates.html diff --git a/src/mailman/pipeline/docs/cleanse.rst b/src/mailman/pipeline/docs/cleanse.rst deleted file mode 100644 index 61dfa8f52..000000000 --- a/src/mailman/pipeline/docs/cleanse.rst +++ /dev/null @@ -1,100 +0,0 @@ -================= -Cleansing headers -================= - -All messages posted to a list get their headers cleansed. Some headers are -related to additional permissions that can be granted to the message and other -headers can be used to fish for membership. - - >>> mlist = create_list('_xtest@example.com') - -Headers such as ``Approved``, ``Approve``, (as well as their ``X-`` variants) -and ``Urgent`` are used to grant special permissions to individual messages. -All may contain a password; the first two headers are used by list -administrators to pre-approve a message normal held for approval. The latter -header is used to send a regular message to all members, regardless of whether -they get digests or not. Because all three headers contain passwords, they -must be removed from any posted message. :: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Approved: foobar - ... Approve: barfoo - ... X-Approved: bazbar - ... X-Approve: barbaz - ... Urgent: notreally - ... Subject: A message of great import - ... - ... Blah blah blah - ... """) - - >>> handler = config.handlers['cleanse'] - >>> handler.process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - Subject: A message of great import - <BLANKLINE> - Blah blah blah - <BLANKLINE> - -Other headers can be used by list members to fish the list for membership, so -we don't let them go through. These are a mix of standard headers and custom -headers supported by some mail readers. For example, ``X-PMRC`` is supported -by Pegasus mail. I don't remember what program uses ``X-Confirm-Reading-To`` -though (Some Microsoft product perhaps?). - - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... Reply-To: bperson@example.org - ... Sender: asystem@example.net - ... Return-Receipt-To: another@example.com - ... Disposition-Notification-To: athird@example.com - ... X-Confirm-Reading-To: afourth@example.com - ... X-PMRQC: afifth@example.com - ... Subject: a message to you - ... - ... How are you doing? - ... """) - >>> handler.process(mlist, msg, {}) - >>> print msg.as_string() - From: bperson@example.com - Reply-To: bperson@example.org - Sender: asystem@example.net - Subject: a message to you - <BLANKLINE> - How are you doing? - <BLANKLINE> - - -Anonymous lists -=============== - -Anonymous mailing lists also try to cleanse certain identifying headers from -the original posting, so that it is at least a bit more difficult to determine -who sent the message. This isn't perfect though, for example, the body of the -messages are never scrubbed (though that might not be a bad idea). The -``From`` and ``Reply-To`` headers in the posted message are taken from list -attributes. - -Hotmail apparently sets ``X-Originating-Email``. - - >>> mlist.anonymous_list = True - >>> mlist.description = 'A Test Mailing List' - >>> mlist.preferred_language = 'en' - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... Reply-To: bperson@example.org - ... Sender: asystem@example.net - ... X-Originating-Email: cperson@example.com - ... Subject: a message to you - ... - ... How are you doing? - ... """) - >>> handler.process(mlist, msg, {}) - >>> print msg.as_string() - Subject: a message to you - From: A Test Mailing List <_xtest@example.com> - Reply-To: _xtest@example.com - <BLANKLINE> - How are you doing? - <BLANKLINE> diff --git a/src/mailman/pipeline/docs/cook-headers.rst b/src/mailman/pipeline/docs/cook-headers.rst deleted file mode 100644 index e0313f53a..000000000 --- a/src/mailman/pipeline/docs/cook-headers.rst +++ /dev/null @@ -1,129 +0,0 @@ -=============== -Cooking headers -=============== - -Messages that flow through the global pipeline get their headers 'cooked', -which basically means that their headers go through several mostly unrelated -transformations. Some headers get added, others get changed. Some of these -changes depend on mailing list settings and others depend on how the message -is getting sent through the system. We'll take things one-by-one. - - >>> mlist = create_list('test@example.com') - >>> mlist.subject_prefix = '' - - -Saving the original sender -========================== - -Because the original sender headers may get deleted or changed, this handler -will place the sender in the message metadata for safe keeping. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... A message of great import. - ... """) - >>> msgdata = {} - - >>> from mailman.pipeline.cook_headers import process - >>> process(mlist, msg, msgdata) - >>> print msgdata['original_sender'] - aperson@example.com - -But if there was no original sender, then the empty string will be saved. - - >>> msg = message_from_string("""\ - ... Subject: No original sender - ... - ... A message of great import. - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msgdata['original_sender'] - <BLANKLINE> - - -Mailman version header -====================== - -Mailman will also insert an ``X-Mailman-Version`` header... - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, {}) - >>> from mailman.version import VERSION - >>> msg['x-mailman-version'] == VERSION - True - -...but only if one doesn't already exist. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... X-Mailman-Version: 3000 - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, {}) - >>> print msg['x-mailman-version'] - 3000 - - -Precedence header -================= - -Mailman will insert a ``Precedence`` header, which is a de-facto standard for -telling automatic reply software (e.g. ``vacation(1)``) not to respond to this -message. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, {}) - >>> print msg['precedence'] - list - -But Mailman will only add that header if the original message doesn't already -have one of them. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Precedence: junk - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, {}) - >>> print msg['precedence'] - junk - - -Personalization -=============== - -The ``To`` field normally contains the list posting address. However when -messages are fully personalized, that header will get overwritten with the -address of the recipient. The list's posting address will be added to one of -the recipient headers so that users will be able to reply back to the list. - - >>> from mailman.interfaces.mailinglist import ( - ... Personalization, ReplyToMunging) - >>> mlist.personalize = Personalization.full - >>> mlist.reply_goes_to_list = ReplyToMunging.no_munging - >>> mlist.description = 'My test mailing list' - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - X-Mailman-Version: ... - Precedence: list - Cc: My test mailing list <test@example.com> - <BLANKLINE> - <BLANKLINE> diff --git a/src/mailman/pipeline/docs/decorate.rst b/src/mailman/pipeline/docs/decorate.rst deleted file mode 100644 index 6fa8212ac..000000000 --- a/src/mailman/pipeline/docs/decorate.rst +++ /dev/null @@ -1,348 +0,0 @@ -================== -Message decoration -================== - -Message decoration is the process of adding headers and footers to the -original message. A handler module takes care of this based on the settings -of the mailing list and the type of message being processed. - - >>> mlist = create_list('_xtest@example.com') - >>> msg_text = """\ - ... From: aperson@example.org - ... - ... Here is a message. - ... """ - >>> msg = message_from_string(msg_text) - - -Short circuiting -================ - -Digest messages get decorated during the digest creation phase so no extra -decorations are added for digest messages. -:: - - >>> from mailman.pipeline.decorate import process - >>> process(mlist, msg, dict(isdigest=True)) - >>> print msg.as_string() - From: aperson@example.org - <BLANKLINE> - Here is a message. - - >>> process(mlist, msg, dict(nodecorate=True)) - >>> print msg.as_string() - From: aperson@example.org - <BLANKLINE> - Here is a message. - - -Simple decorations -================== - -Message decorations are specified by URI and can be specialized by the mailing -list and language. Internal Mailman decorations can be referenced by using -the ``mailman://`` URL scheme. Here we create a simple English header and -footer for all mailing lists in our site. -:: - - >>> import os, tempfile - >>> template_dir = tempfile.mkdtemp() - >>> site_dir = os.path.join(template_dir, 'site', 'en') - >>> os.makedirs(site_dir) - >>> config.push('templates', """ - ... [paths.testing] - ... template_dir: {0} - ... """.format(template_dir)) - - >>> myheader_path = os.path.join(site_dir, 'myheader.txt') - >>> with open(myheader_path, 'w') as fp: - ... print >> fp, 'header' - >>> myfooter_path = os.path.join(site_dir, 'myfooter.txt') - >>> with open(myfooter_path, 'w') as fp: - ... print >> fp, 'footer' - -Setting these attributes on the mailing list causes it to use these -templates. Since these are site-global templates, we can use a shorter path. - - >>> mlist.header_uri = 'mailman:///myheader.txt' - >>> mlist.footer_uri = 'mailman:///myfooter.txt' - -Text messages that have no declared content type are, by default encoded in -ASCII. When the mailing list's preferred language is ``en`` (i.e. English), -the character set of the mailing list and of the message will match, allowing -Mailman to simply prepend the header and append the footer verbatim. - - >>> mlist.preferred_language = 'en' - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.org - ... - <BLANKLINE> - header - Here is a message. - footer - -Mailman supports a number of interpolation variables, placeholders in the -header and footer for information to be filled in with mailing list specific -data. An example of such information is the mailing list's `real name` (a -short descriptive name for the mailing list). -:: - - >>> with open(myheader_path, 'w') as fp: - ... print >> fp, '$display_name header' - >>> with open(myfooter_path, 'w') as fp: - ... print >> fp, '$display_name footer' - - >>> msg = message_from_string(msg_text) - >>> mlist.display_name = 'XTest' - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.org - ... - XTest header - Here is a message. - XTest footer - -You can't just pick any interpolation variable though; if you do, the variable -will remain in the header or footer unchanged. -:: - - >>> with open(myheader_path, 'w') as fp: - ... print >> fp, '$dummy header' - >>> with open(myfooter_path, 'w') as fp: - ... print >> fp, '$dummy footer' - - >>> msg = message_from_string(msg_text) - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.org - ... - $dummy header - Here is a message. - $dummy footer - - -Handling RFC 3676 'format=flowed' parameters -============================================ - -RFC 3676 describes a standard by which text/plain messages can marked by -generating MUAs for better readability in compatible receiving MUAs. The -``format`` parameter on the text/plain ``Content-Type`` header gives hints as -to how the receiving MUA may flow and delete trailing whitespace for better -display in a proportional font. - -When Mailman sees text/plain messages with such RFC 3676 parameters, it -preserves these parameters when it concatenates headers and footers to the -message payload. -:: - - >>> with open(myheader_path, 'w') as fp: - ... print >> fp, 'header' - >>> with open(myfooter_path, 'w') as fp: - ... print >> fp, 'footer' - - >>> mlist.preferred_language = 'en' - >>> msg = message_from_string("""\ - ... From: aperson@example.org - ... Content-Type: text/plain; format=flowed; delsp=no - ... - ... Here is a message\x20 - ... with soft line breaks. - ... """) - >>> process(mlist, msg, {}) - >>> # Don't use 'print' here as above because it won't be obvious from the - >>> # output that the soft-line break space at the end of the 'Here is a - >>> # message' line will be retained in the output. - >>> print msg['content-type'] - text/plain; format="flowed"; delsp="no"; charset="us-ascii" - >>> for line in msg.get_payload().splitlines(): - ... print '>{0}<'.format(line) - >header< - >Here is a message < - >with soft line breaks.< - >footer< - - -Decorating mixed-charset messages -================================= - -When a message has no explicit character set, it is assumed to be ASCII. -However, if the mailing list's preferred language has a different character -set, Mailman will still try to concatenate the header and footer, but it will -convert the text to utf-8 and base-64 encode the message payload. -:: - - # 'ja' = Japanese; charset = 'euc-jp' - >>> mlist.preferred_language = 'ja' - - >>> with open(myheader_path, 'w') as fp: - ... print >> fp, '$description header' - >>> with open(myfooter_path, 'w') as fp: - ... print >> fp, '$description footer' - >>> mlist.description = '\u65e5\u672c\u8a9e' - - >>> from email.message import Message - >>> msg = Message() - >>> msg.set_payload('Fran\xe7aise', 'iso-8859-1') - >>> print msg.as_string() - MIME-Version: 1.0 - Content-Type: text/plain; charset="iso-8859-1" - Content-Transfer-Encoding: quoted-printable - <BLANKLINE> - Fran=E7aise - >>> process(mlist, msg, {}) - >>> print msg.as_string() - MIME-Version: 1.0 - Content-Type: text/plain; charset="utf-8" - Content-Transfer-Encoding: base64 - <BLANKLINE> - 5pel5pys6KqeIGhlYWRlcgpGcmFuw6dhaXNlCuaXpeacrOiqniBmb290ZXIK - -Sometimes the message even has an unknown character set. In this case, -Mailman has no choice but to decorate the original message with MIME -attachments. -:: - - >>> mlist.preferred_language = 'en' - >>> with open(myheader_path, 'w') as fp: - ... print >> fp, 'header' - >>> with open(myfooter_path, 'w') as fp: - ... print >> fp, 'footer' - - >>> msg = message_from_string("""\ - ... From: aperson@example.org - ... Content-Type: text/plain; charset=unknown - ... Content-Transfer-Encoding: 7bit - ... - ... Here is a message. - ... """) - - >>> process(mlist, msg, {}) - >>> msg.set_boundary('BOUNDARY') - >>> print msg.as_string() - From: aperson@example.org - Content-Type: multipart/mixed; boundary="BOUNDARY" - <BLANKLINE> - --BOUNDARY - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: inline - <BLANKLINE> - header - --BOUNDARY - Content-Type: text/plain; charset=unknown - Content-Transfer-Encoding: 7bit - <BLANKLINE> - Here is a message. - <BLANKLINE> - --BOUNDARY - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: inline - <BLANKLINE> - footer - --BOUNDARY-- - - -Decorating multipart messages -============================= - -Multipart messages have to be decorated differently. The header and footer -cannot be simply concatenated into the payload because that will break the -MIME structure of the message. Instead, the header and footer are attached as -separate MIME subparts. - -When the outer part is ``multipart/mixed``, the header and footer can have a -``Content-Disposition`` of ``inline`` so that MUAs can display these headers -as if they were simply concatenated. - - >>> part_1 = message_from_string("""\ - ... From: aperson@example.org - ... - ... Here is the first message. - ... """) - >>> part_2 = message_from_string("""\ - ... From: bperson@example.com - ... - ... Here is the second message. - ... """) - >>> from email.mime.multipart import MIMEMultipart - >>> msg = MIMEMultipart('mixed', boundary='BOUNDARY', - ... _subparts=(part_1, part_2)) - >>> process(mlist, msg, {}) - >>> print msg.as_string() - Content-Type: multipart/mixed; boundary="BOUNDARY" - MIME-Version: 1.0 - <BLANKLINE> - --BOUNDARY - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: inline - <BLANKLINE> - header - --BOUNDARY - From: aperson@example.org - <BLANKLINE> - Here is the first message. - <BLANKLINE> - --BOUNDARY - From: bperson@example.com - <BLANKLINE> - Here is the second message. - <BLANKLINE> - --BOUNDARY - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: inline - <BLANKLINE> - footer - --BOUNDARY-- - - -Decorating other content types -============================== - -Non-multipart non-text content types will get wrapped in a ``multipart/mixed`` -so that the header and footer can be added as attachments. - - >>> msg = message_from_string("""\ - ... From: aperson@example.org - ... Content-Type: image/x-beautiful - ... - ... IMAGEDATAIMAGEDATAIMAGEDATA - ... """) - >>> process(mlist, msg, {}) - >>> msg.set_boundary('BOUNDARY') - >>> print msg.as_string() - From: aperson@example.org - ... - --BOUNDARY - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: inline - <BLANKLINE> - header - --BOUNDARY - Content-Type: image/x-beautiful - <BLANKLINE> - IMAGEDATAIMAGEDATAIMAGEDATA - <BLANKLINE> - --BOUNDARY - Content-Type: text/plain; charset="us-ascii" - MIME-Version: 1.0 - Content-Transfer-Encoding: 7bit - Content-Disposition: inline - <BLANKLINE> - footer - --BOUNDARY-- - -.. Clean up - - >>> config.pop('templates') - >>> import shutil - >>> shutil.rmtree(template_dir) diff --git a/src/mailman/pipeline/docs/digests.rst b/src/mailman/pipeline/docs/digests.rst deleted file mode 100644 index d4d563180..000000000 --- a/src/mailman/pipeline/docs/digests.rst +++ /dev/null @@ -1,113 +0,0 @@ -======= -Digests -======= - -Digests are a way for a user to receive list traffic in collections instead of -as individual messages when immediately posted. There are several forms of -digests, although only two are currently supported: MIME digests and RFC 1153 -(a.k.a. plain text) digests. - - >>> mlist = create_list('xtest@example.com') - -This is a helper function used to iterate through all the accumulated digest -messages, in the order in which they were posted. This makes it easier to -update the tests when we switch to a different mailbox format. -:: - - >>> from mailman.testing.helpers import digest_mbox - >>> from itertools import count - >>> from string import Template - - >>> def message_factory(): - ... for i in count(1): - ... text = Template("""\ - ... From: aperson@example.com - ... To: xtest@example.com - ... Subject: Test message $i - ... - ... Here is message $i - ... """).substitute(i=i) - ... yield message_from_string(text) - >>> message_factory = message_factory() - - -Short circuiting -================ - -When a message is posted to the mailing list, it is generally added to a -mailbox, unless the mailing list does not allow digests. - - >>> mlist.digestable = False - >>> msg = next(message_factory) - >>> process = config.handlers['to-digest'].process - >>> process(mlist, msg, {}) - >>> sum(1 for msg in digest_mbox(mlist)) - 0 - >>> digest_queue = config.switchboards['digest'] - >>> digest_queue.files - [] - -...or they may allow digests but the message is already a digest. - - >>> mlist.digestable = True - >>> process(mlist, msg, dict(isdigest=True)) - >>> sum(1 for msg in digest_mbox(mlist)) - 0 - >>> digest_queue.files - [] - - -Sending a digest -================ - -For messages which are not digests, but which are posted to a digesting -mailing list, the messages will be stored until they reach a criteria -triggering the sending of the digest. If none of those criteria are met, then -the message will just sit in the mailbox for a while. - - >>> mlist.digest_size_threshold = 10000 - >>> process(mlist, msg, {}) - >>> digest_queue.files - [] - >>> digest = digest_mbox(mlist) - >>> sum(1 for msg in digest) - 1 - >>> import os - >>> os.remove(digest._path) - -When the size of the digest mailbox reaches the maximum size threshold, a -marker message is placed into the digest runner's queue. The digest is not -actually crafted by the handler. - - >>> mlist.digest_size_threshold = 1 - >>> mlist.volume = 2 - >>> mlist.next_digest_number = 10 - >>> size = 0 - >>> for msg in message_factory: - ... process(mlist, msg, {}) - ... size += len(str(msg)) - ... if size >= mlist.digest_size_threshold * 1024: - ... break - - >>> sum(1 for msg in digest_mbox(mlist)) - 0 - >>> len(digest_queue.files) - 1 - -The digest has been moved to a unique file. - - >>> from mailman.utilities.mailbox import Mailbox - >>> from mailman.testing.helpers import get_queue_messages - >>> item = get_queue_messages('digest')[0] - >>> for msg in Mailbox(item.msgdata['digest_path']): - ... print msg['subject'] - Test message 2 - Test message 3 - Test message 4 - Test message 5 - Test message 6 - Test message 7 - Test message 8 - Test message 9 - -Digests are actually crafted and sent by a separate digest runner. diff --git a/src/mailman/pipeline/docs/file-recips.rst b/src/mailman/pipeline/docs/file-recips.rst deleted file mode 100644 index 7d157ccc5..000000000 --- a/src/mailman/pipeline/docs/file-recips.rst +++ /dev/null @@ -1,111 +0,0 @@ -=============== -File recipients -=============== - -Mailman can calculate the recipients for a message from a Sendmail-style -include file. This file must be called ``members.txt`` and it must live in -the list's data directory. - - >>> mlist = create_list('_xtest@example.com') - - -Short circuiting -================ - -If the message's metadata already has recipients, this handler immediately -returns. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... A message. - ... """) - >>> msgdata = {'recipients': 7} - - >>> handler = config.handlers['file-recipients'] - >>> handler.process(mlist, msg, msgdata) - >>> print msg.as_string() - From: aperson@example.com - <BLANKLINE> - A message. - <BLANKLINE> - >>> dump_msgdata(msgdata) - recipients: 7 - - -Missing file -============ - -The include file must live inside the list's data directory, under the name -``members.txt``. If the file doesn't exist, the list of recipients will be -empty. - - >>> import os - >>> file_path = os.path.join(mlist.data_path, 'members.txt') - >>> open(file_path) - Traceback (most recent call last): - ... - IOError: [Errno ...] - No such file or directory: u'.../_xtest@example.com/members.txt' - >>> msgdata = {} - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - *Empty* - - -Existing file -============= - -If the file exists, it contains a list of addresses, one per line. These -addresses are returned as the set of recipients. -:: - - >>> fp = open(file_path, 'w') - >>> try: - ... print >> fp, 'bperson@example.com' - ... print >> fp, 'cperson@example.com' - ... print >> fp, 'dperson@example.com' - ... print >> fp, 'eperson@example.com' - ... print >> fp, 'fperson@example.com' - ... print >> fp, 'gperson@example.com' - ... finally: - ... fp.close() - - >>> msgdata = {} - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - bperson@example.com - cperson@example.com - dperson@example.com - eperson@example.com - fperson@example.com - gperson@example.com - -However, if the sender of the original message is a member of the list and -their address is in the include file, the sender's address is *not* included -in the recipients list. -:: - - >>> from mailman.interfaces.usermanager import IUserManager - >>> from zope.component import getUtility - >>> address_1 = getUtility(IUserManager).create_address( - ... 'cperson@example.com') - - >>> from mailman.interfaces.member import MemberRole - >>> mlist.subscribe(address_1, MemberRole.member) - <Member: cperson@example.com on _xtest@example.com as MemberRole.member> - - >>> msg = message_from_string("""\ - ... From: cperson@example.com - ... - ... A message. - ... """) - >>> msgdata = {} - >>> handler.process(mlist, msg, msgdata) - >>> dump_list(msgdata['recipients']) - bperson@example.com - dperson@example.com - eperson@example.com - fperson@example.com - gperson@example.com diff --git a/src/mailman/pipeline/docs/filtering.rst b/src/mailman/pipeline/docs/filtering.rst deleted file mode 100644 index fd0b33d3b..000000000 --- a/src/mailman/pipeline/docs/filtering.rst +++ /dev/null @@ -1,347 +0,0 @@ -================= -Content filtering -================= - -Mailman can filter the content of messages posted to a mailing list by -stripping MIME subparts, and possibly reorganizing the MIME structure of a -message. - - >>> mlist = create_list('test@example.com') - -Several mailing list options control content filtering. First, the feature -must be enabled, then there are two options that control which MIME types get -filtered and which get passed. Finally, there is an option to control whether -``text/html`` parts will get converted to plain text. Let's set up some -defaults for these variables, then we'll explain them in more detail below. - - >>> mlist.filter_content = True - >>> mlist.filter_types = [] - >>> mlist.pass_types = [] - >>> mlist.convert_html_to_plaintext = False - - -Filtering the outer content type -================================ - -A simple filtering setting will just search the content types of the messages -parts, discarding all parts with a matching MIME type. If the message's outer -content type matches the filter, the entire message will be discarded. -:: - - >>> from mailman.interfaces.mime import FilterAction - - >>> mlist.filter_types = ['image/jpeg'] - >>> mlist.filter_action = FilterAction.discard - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Content-Type: image/jpeg - ... MIME-Version: 1.0 - ... - ... xxxxx - ... """) - - >>> process = config.handlers['mime-delete'].process - >>> process(mlist, msg, {}) - Traceback (most recent call last): - ... - DiscardMessage: The message's content type was explicitly disallowed - -However, if we turn off content filtering altogether, then the handler -short-circuits. - - >>> mlist.filter_content = False - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - From: aperson@example.com - Content-Type: image/jpeg - MIME-Version: 1.0 - <BLANKLINE> - xxxxx - >>> msgdata - {} - -Similarly, no content filtering is performed on digest messages, which are -crafted internally by Mailman. - - >>> mlist.filter_content = True - >>> msgdata = {'isdigest': True} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - From: aperson@example.com - Content-Type: image/jpeg - MIME-Version: 1.0 - <BLANKLINE> - xxxxx - >>> msgdata - {u'isdigest': True} - - -Simple multipart filtering -========================== - -If one of the subparts in a multipart message matches the filter type, then -just that subpart will be stripped. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Content-Type: multipart/mixed; boundary=BOUNDARY - ... MIME-Version: 1.0 - ... - ... --BOUNDARY - ... Content-Type: image/jpeg - ... MIME-Version: 1.0 - ... - ... xxx - ... - ... --BOUNDARY - ... Content-Type: image/gif - ... MIME-Version: 1.0 - ... - ... yyy - ... --BOUNDARY-- - ... """) - - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - Content-Type: multipart/mixed; boundary=BOUNDARY - MIME-Version: 1.0 - X-Content-Filtered-By: Mailman/MimeDel ... - <BLANKLINE> - --BOUNDARY - Content-Type: image/gif - MIME-Version: 1.0 - <BLANKLINE> - yyy - --BOUNDARY-- - <BLANKLINE> - - -Collapsing multipart/alternative messages -========================================= - -When content filtering encounters a ``multipart/alternative`` part, and the -results of filtering leave only one of the subparts, then the -``multipart/alternative`` may be collapsed. For example, in the following -message, the outer content type is a ``multipart/mixed``. Inside this part is -just a single subpart that has a content type of ``multipart/alternative``. -This inner multipart has two subparts, a jpeg and a gif. - -Content filtering will remove the jpeg part, leaving the -``multipart/alternative`` with only a single gif subpart. Because there's -only one subpart left, the MIME structure of the message will be reorganized, -removing the inner ``multipart/alternative`` so that the outer -``multipart/mixed`` has just a single gif subpart. - - >>> mlist.collapse_alternatives = True - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Content-Type: multipart/mixed; boundary=BOUNDARY - ... MIME-Version: 1.0 - ... - ... --BOUNDARY - ... Content-Type: multipart/alternative; boundary=BOUND2 - ... MIME-Version: 1.0 - ... - ... --BOUND2 - ... Content-Type: image/jpeg - ... MIME-Version: 1.0 - ... - ... xxx - ... - ... --BOUND2 - ... Content-Type: image/gif - ... MIME-Version: 1.0 - ... - ... yyy - ... --BOUND2-- - ... - ... --BOUNDARY-- - ... """) - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - Content-Type: multipart/mixed; boundary=BOUNDARY - MIME-Version: 1.0 - X-Content-Filtered-By: Mailman/MimeDel ... - <BLANKLINE> - --BOUNDARY - Content-Type: image/gif - MIME-Version: 1.0 - <BLANKLINE> - yyy - --BOUNDARY-- - <BLANKLINE> - -When the outer part is a ``multipart/alternative`` and filtering leaves this -outer part with just one subpart, the entire message is converted to the left -over part's content type. In other words, the left over inner part is -promoted to being the outer part. -:: - - >>> mlist.filter_types = ['image/jpeg', 'text/html'] - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Content-Type: multipart/alternative; boundary=AAA - ... - ... --AAA - ... Content-Type: text/html - ... - ... <b>This is some html</b> - ... --AAA - ... Content-Type: text/plain - ... - ... This is plain text - ... --AAA-- - ... """) - - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - Content-Type: text/plain - X-Content-Filtered-By: Mailman/MimeDel ... - <BLANKLINE> - This is plain text - -Clean up. - - >>> mlist.filter_types = ['image/jpeg'] - - -Conversion to plain text -======================== - -Many mailing lists prohibit HTML email, and in fact, such email can be a -phishing or spam vector. However, many mail readers will send HTML email by -default because users think it looks pretty. One approach to handling this -would be to filter out ``text/html`` parts and rely on -``multipart/alternative`` collapsing to leave just a plain text part. This -works because many mail readers that send HTML email actually send a plain -text part in the second subpart of such ``multipart/alternatives``. - -While this is a good suggestion for plain text-only mailing lists, often a -mail reader will send only a ``text/html`` part with no plain text -alternative. in this case, the site administer can enable ``text/html`` to -``text/plain`` conversion by defining a conversion command. A list -administrator still needs to enable such conversion for their list though. - - >>> mlist.convert_html_to_plaintext = True - -By default, Mailman sends the message through lynx, but since this program is -not guaranteed to exist, we'll craft a simple, but stupid script to simulate -the conversion process. The script expects a single argument, which is the -name of the file containing the message payload to filter. - - >>> import os, sys - >>> script_path = os.path.join(config.DATA_DIR, 'filter.py') - >>> fp = open(script_path, 'w') - >>> try: - ... print >> fp, """\ - ... import sys - ... print 'Converted text/html to text/plain' - ... print 'Filename:', sys.argv[1] - ... """ - ... finally: - ... fp.close() - >>> config.HTML_TO_PLAIN_TEXT_COMMAND = '%s %s %%(filename)s' % ( - ... sys.executable, script_path) - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Content-Type: text/html - ... MIME-Version: 1.0 - ... - ... <html><head></head> - ... <body></body></html> - ... """) - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - MIME-Version: 1.0 - Content-Type: text/plain - X-Content-Filtered-By: Mailman/MimeDel ... - <BLANKLINE> - Converted text/html to text/plain - Filename: ... - <BLANKLINE> - - -Discarding empty parts -====================== - -Similarly, if after filtering a multipart section ends up empty, then the -entire multipart is discarded. For example, here's a message where an inner -``multipart/mixed`` contains two jpeg subparts. Both jpegs are filtered out, -so the entire inner ``multipart/mixed`` is discarded. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Content-Type: multipart/mixed; boundary=AAA - ... - ... --AAA - ... Content-Type: multipart/mixed; boundary=BBB - ... - ... --BBB - ... Content-Type: image/jpeg - ... - ... xxx - ... --BBB - ... Content-Type: image/jpeg - ... - ... yyy - ... --BBB--- - ... --AAA - ... Content-Type: multipart/alternative; boundary=CCC - ... - ... --CCC - ... Content-Type: text/html - ... - ... <h2>This is a header</h2> - ... - ... --CCC - ... Content-Type: text/plain - ... - ... A different message - ... --CCC-- - ... --AAA - ... Content-Type: image/gif - ... - ... zzz - ... --AAA - ... Content-Type: image/gif - ... - ... aaa - ... --AAA-- - ... """) - >>> process(mlist, msg, {}) - >>> print msg.as_string() - From: aperson@example.com - Content-Type: multipart/mixed; boundary=AAA - X-Content-Filtered-By: Mailman/MimeDel ... - <BLANKLINE> - --AAA - MIME-Version: 1.0 - Content-Type: text/plain - <BLANKLINE> - Converted text/html to text/plain - Filename: ... - <BLANKLINE> - --AAA - Content-Type: image/gif - <BLANKLINE> - zzz - --AAA - Content-Type: image/gif - <BLANKLINE> - aaa - --AAA-- - <BLANKLINE> - - -Passing MIME types -================== - -XXX Describe the pass_mime_types setting and how it interacts with -``filter_mime_types``. diff --git a/src/mailman/pipeline/docs/nntp.rst b/src/mailman/pipeline/docs/nntp.rst deleted file mode 100644 index 874712397..000000000 --- a/src/mailman/pipeline/docs/nntp.rst +++ /dev/null @@ -1,68 +0,0 @@ -============ -NNTP Gateway -============ - -Mailman has an NNTP gateway, whereby messages posted to the mailing list can -be forwarded onto an NNTP newsgroup. Typically this means Usenet, but since -NNTP is to Usenet as IP is to the web, it's more general than that. - - >>> mlist = create_list('_xtest@example.com') - -Gatewaying from the mailing list to the newsgroup happens through a separate -``nntp`` queue and happen immediately when the message is posted through to -the list. Note that gatewaying from the newsgroup to the list happens via a -cronjob (currently not shown). - -There are several situations which prevent a message from being gatewayed to -the newsgroup. The feature could be disabled, as is the default. -:: - - >>> mlist.gateway_to_news = False - >>> msg = message_from_string("""\ - ... Subject: An important message - ... - ... Something of great import. - ... """) - - >>> handler = config.handlers['to-usenet'] - >>> handler.process(mlist, msg, {}) - - >>> switchboard = config.switchboards['news'] - >>> switchboard.files - [] - -Even if enabled, messages that came from the newsgroup are never gated back to -the newsgroup. - - >>> mlist.gateway_to_news = True - >>> handler.process(mlist, msg, {'fromusenet': True}) - >>> switchboard.files - [] - -Neither are digests ever gated to the newsgroup. - - >>> handler.process(mlist, msg, {'isdigest': True}) - >>> switchboard.files - [] - -However, other posted messages get gated to the newsgroup via the nntp queue. -The list owner can set the linked newsgroup and the nntp host that its -messages are gated to. - - >>> mlist.linked_newsgroup = 'comp.lang.thing' - >>> mlist.nntp_host = 'news.example.com' - >>> handler.process(mlist, msg, {}) - >>> len(switchboard.files) - 1 - >>> filebase = switchboard.files[0] - >>> msg, msgdata = switchboard.dequeue(filebase) - >>> switchboard.finish(filebase) - >>> print msg.as_string() - Subject: An important message - <BLANKLINE> - Something of great import. - <BLANKLINE> - >>> dump_msgdata(msgdata) - _parsemsg: False - listname : _xtest@example.com - version : 3 diff --git a/src/mailman/pipeline/docs/reply-to.rst b/src/mailman/pipeline/docs/reply-to.rst deleted file mode 100644 index e08fea81d..000000000 --- a/src/mailman/pipeline/docs/reply-to.rst +++ /dev/null @@ -1,131 +0,0 @@ -================ -Reply-to munging -================ - -Messages that flow through the global pipeline get their headers *cooked*, -which basically means that their headers go through several mostly unrelated -transformations. Some headers get added, others get changed. Some of these -changes depend on mailing list settings and others depend on how the message -is getting sent through the system. We'll take things one-by-one. - - >>> mlist = create_list('_xtest@example.com') - -*Reply-to munging* refers to the behavior where a mailing list can be -configured to change or augment an existing ``Reply-To`` header in a message -posted to the list. Reply-to munging is fairly controversial, with arguments -made either for or against munging. - -The Mailman developers, and I believe the majority consensus is to do no -reply-to munging, under several principles. Primarily, most reply-to munging -is requested by people who do not have both a `Reply` and `Reply All` button -on their mail reader. If you do not munge ``Reply-To``, then these buttons -will work properly, but if you munge the header, it is impossible for these -buttons to work right, because both will reply to the list. This leads to -unfortunate accidents where a private message is accidentally posted to the -entire list. - -However, Mailman gives list owners the option to do reply-To munging anyway, -mostly as a way to shut up the really vocal minority who seem to insist on -this mis-feature. - - -Reply to list -============= - -A list can be configured to add a ``Reply-To`` header pointing back to the -mailing list's posting address. If there's no ``Reply-To`` header in the -original message, the list's posting address simply gets inserted. -:: - - >>> from mailman.interfaces.mailinglist import ReplyToMunging - >>> mlist.reply_goes_to_list = ReplyToMunging.point_to_list - >>> mlist.preferred_language = 'en' - >>> mlist.description = '' - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - - >>> from mailman.pipeline.cook_headers import process - >>> process(mlist, msg, {}) - >>> len(msg.get_all('reply-to')) - 1 - >>> print msg['reply-to'] - _xtest@example.com - -It's also possible to strip any existing ``Reply-To`` header first, before -adding the list's posting address. - - >>> mlist.first_strip_reply_to = True - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Reply-To: bperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> len(msg.get_all('reply-to')) - 1 - >>> print msg['reply-to'] - _xtest@example.com - -If you don't first strip the header, then the list's posting address will just -get appended to whatever the original version was. - - >>> mlist.first_strip_reply_to = False - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Reply-To: bperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> len(msg.get_all('reply-to')) - 1 - >>> print msg['reply-to'] - bperson@example.com, _xtest@example.com - - -Explicit Reply-To -================= - -The list can also be configured to have an explicit ``Reply-To`` header. - - >>> mlist.reply_goes_to_list = ReplyToMunging.explicit_header - >>> mlist.reply_to_address = 'my-list@example.com' - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> len(msg.get_all('reply-to')) - 1 - >>> print msg['reply-to'] - my-list@example.com - -And as before, it's possible to either strip any existing ``Reply-To`` -header... - - >>> mlist.first_strip_reply_to = True - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Reply-To: bperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> len(msg.get_all('reply-to')) - 1 - >>> print msg['reply-to'] - my-list@example.com - -...or not. - - >>> mlist.first_strip_reply_to = False - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Reply-To: bperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> len(msg.get_all('reply-to')) - 1 - >>> print msg['reply-to'] - my-list@example.com, bperson@example.com diff --git a/src/mailman/pipeline/docs/replybot.rst b/src/mailman/pipeline/docs/replybot.rst deleted file mode 100644 index 7cdd7c928..000000000 --- a/src/mailman/pipeline/docs/replybot.rst +++ /dev/null @@ -1,343 +0,0 @@ -========================== -Automatic response handler -========================== - -Mailman has a autoreply handler that sends automatic responses to messages it -receives on its posting address, owner address, or robot address. Automatic -responses are subject to various conditions, such as headers in the original -message or the amount of time since the last auto-response. - - >>> mlist = create_list('_xtest@example.com') - >>> mlist.display_name = 'XTest' - - -Basic automatic responding -========================== - -Basic automatic responding occurs when the list is set up to respond to either -its ``-owner`` address, its ``-request`` address, or to the posting address, -and a message is sent to one of these addresses. A mailing list also has an -automatic response grace period which specifies how much time must pass before -a second response will be sent, with 0 meaning "there is no grace period". -:: - - >>> import datetime - >>> from mailman.interfaces.autorespond import ResponseAction - - >>> mlist.autorespond_owner = ResponseAction.respond_and_continue - >>> mlist.autoresponse_grace_period = datetime.timedelta() - >>> mlist.autoresponse_owner_text = 'owner autoresponse text' - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... To: _xtest-owner@example.com - ... - ... help - ... """) - -The preceding message to the mailing list's owner will trigger an automatic -response. -:: - - >>> from mailman.testing.helpers import get_queue_messages - - >>> handler = config.handlers['replybot'] - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - - >>> dump_msgdata(messages[0].msgdata) - _parsemsg : False - listname : _xtest@example.com - nodecorate : True - recipients : set([u'aperson@example.com']) - reduced_list_headers: True - version : 3 - - >>> print messages[0].msg.as_string() - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - Content-Transfer-Encoding: 7bit - Subject: Auto-response for your message to the "XTest" mailing list - From: _xtest-bounces@example.com - To: aperson@example.com - X-Mailer: The Mailman Replybot - X-Ack: No - Message-ID: <...> - Date: ... - Precedence: bulk - <BLANKLINE> - owner autoresponse text - - -Short circuiting -================ - -Several headers in the original message determine whether an automatic -response should even be sent. For example, if the message has an -``X-Ack: No`` header, no auto-response is sent. -:: - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... X-Ack: No - ... - ... help me - ... """) - - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> get_queue_messages('virgin') - [] - -Mailman itself can suppress automatic responses for certain types of -internally crafted messages, by setting the ``noack`` metadata key. -:: - - >>> msg = message_from_string("""\ - ... From: mailman@example.com - ... - ... help for you - ... """) - - >>> handler.process(mlist, msg, dict(noack=True, to_owner=True)) - >>> get_queue_messages('virgin') - [] - -If there is a ``Precedence:`` header with any of the values ``bulk``, -``junk``, or ``list``, then the automatic response is also suppressed. -:: - - >>> msg = message_from_string("""\ - ... From: asystem@example.com - ... Precedence: bulk - ... - ... hey! - ... """) - - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> get_queue_messages('virgin') - [] - - >>> msg.replace_header('precedence', 'junk') - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> get_queue_messages('virgin') - [] - - >>> msg.replace_header('precedence', 'list') - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> get_queue_messages('virgin') - [] - -Unless the ``X-Ack:`` header has a value of ``yes``, in which case, the -``Precedence`` header is ignored. -:: - - >>> msg['X-Ack'] = 'yes' - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - - >>> dump_msgdata(messages[0].msgdata) - _parsemsg : False - listname : _xtest@example.com - nodecorate : True - recipients : set([u'asystem@example.com']) - reduced_list_headers: True - version : 3 - - >>> print messages[0].msg.as_string() - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - Content-Transfer-Encoding: 7bit - Subject: Auto-response for your message to the "XTest" mailing list - From: _xtest-bounces@example.com - To: asystem@example.com - X-Mailer: The Mailman Replybot - X-Ack: No - Message-ID: <...> - Date: ... - Precedence: bulk - <BLANKLINE> - owner autoresponse text - - -Available auto-responses -======================== - -As shown above, a message sent to the ``-owner`` address will get an -auto-response with the text set for owner responses. Two other types of email -will get auto-responses: those sent to the ``-request`` address... -:: - - >>> mlist.autorespond_requests = ResponseAction.respond_and_continue - >>> mlist.autoresponse_request_text = 'robot autoresponse text' - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... To: _xtest-request@example.com - ... - ... help me - ... """) - - >>> handler.process(mlist, msg, dict(to_request=True)) - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - - >>> print messages[0].msg.as_string() - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - Content-Transfer-Encoding: 7bit - Subject: Auto-response for your message to the "XTest" mailing list - From: _xtest-bounces@example.com - To: aperson@example.com - X-Mailer: The Mailman Replybot - X-Ack: No - Message-ID: <...> - Date: ... - Precedence: bulk - <BLANKLINE> - robot autoresponse text - -...and those sent to the posting address. -:: - - >>> mlist.autorespond_postings = ResponseAction.respond_and_continue - >>> mlist.autoresponse_postings_text = 'postings autoresponse text' - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... To: _xtest@example.com - ... - ... help me - ... """) - - >>> handler.process(mlist, msg, dict(to_list=True)) - >>> messages = get_queue_messages('virgin') - >>> len(messages) - 1 - - >>> print messages[0].msg.as_string() - MIME-Version: 1.0 - Content-Type: text/plain; charset="us-ascii" - Content-Transfer-Encoding: 7bit - Subject: Auto-response for your message to the "XTest" mailing list - From: _xtest-bounces@example.com - To: aperson@example.com - X-Mailer: The Mailman Replybot - X-Ack: No - Message-ID: <...> - Date: ... - Precedence: bulk - <BLANKLINE> - postings autoresponse text - - -Grace periods -============= - -Automatic responses have a grace period, during which no additional responses -will be sent. This is so as not to bombard the sender with responses. The -grace period is measured in days. - - >>> mlist.autoresponse_grace_period = datetime.timedelta(days=10) - -When a response is sent to a person via any of the owner, request, or postings -addresses, the response date is recorded. The grace period is usually -measured in days. - - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... To: _xtest-owner@example.com - ... - ... help - ... """) - -This is the first response to bperson, so it gets sent. - - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> print len(get_queue_messages('virgin')) - 1 - -But with a grace period greater than zero, no subsequent response will be sent -right now. - - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> print len(get_queue_messages('virgin')) - 0 - -Fast forward 9 days and you still don't get a response. -:: - - >>> from mailman.utilities.datetime import factory - >>> factory.fast_forward(days=9) - - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> print len(get_queue_messages('virgin')) - 0 - -But tomorrow, the sender will get a new auto-response. - - >>> factory.fast_forward() - >>> handler.process(mlist, msg, dict(to_owner=True)) - >>> print len(get_queue_messages('virgin')) - 1 - -Of course, everything works the same way for messages to the request -address, even if the sender is the same person... -:: - - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... To: _xtest-request@example.com - ... - ... help - ... """) - - >>> handler.process(mlist, msg, dict(to_request=True)) - >>> print len(get_queue_messages('virgin')) - 1 - - >>> handler.process(mlist, msg, dict(to_request=True)) - >>> print len(get_queue_messages('virgin')) - 0 - - >>> factory.fast_forward(days=9) - >>> handler.process(mlist, msg, dict(to_request=True)) - >>> print len(get_queue_messages('virgin')) - 0 - - >>> factory.fast_forward() - >>> handler.process(mlist, msg, dict(to_request=True)) - >>> print len(get_queue_messages('virgin')) - 1 - -...and for messages to the posting address. -:: - - >>> msg = message_from_string("""\ - ... From: bperson@example.com - ... To: _xtest@example.com - ... - ... help - ... """) - - >>> handler.process(mlist, msg, dict(to_list=True)) - >>> print len(get_queue_messages('virgin')) - 1 - - >>> handler.process(mlist, msg, dict(to_list=True)) - >>> print len(get_queue_messages('virgin')) - 0 - - >>> factory.fast_forward(days=9) - >>> handler.process(mlist, msg, dict(to_list=True)) - >>> print len(get_queue_messages('virgin')) - 0 - - >>> factory.fast_forward() - >>> handler.process(mlist, msg, dict(to_list=True)) - >>> print len(get_queue_messages('virgin')) - 1 diff --git a/src/mailman/pipeline/docs/rfc-2369.rst b/src/mailman/pipeline/docs/rfc-2369.rst deleted file mode 100644 index 1b89f2354..000000000 --- a/src/mailman/pipeline/docs/rfc-2369.rst +++ /dev/null @@ -1,206 +0,0 @@ -========================= -RFC 2919 and 2369 headers -========================= - -`RFC 2919`_ and `RFC 2369`_ define headers for mailing list actions. These -headers generally start with the `List-` prefix. - - >>> mlist = create_list('test@example.com') - >>> mlist.preferred_language = 'en' - >>> mlist.archive = False - -.. - This is a helper function for the following section. - >>> def list_headers(msg, only=None): - ... if isinstance(only, basestring): - ... only = (only.lower(),) - ... elif only is None: - ... only = set(header.lower() for header in msg.keys() - ... if header.lower().startswith('list-')) - ... only.add('archived-at') - ... else: - ... only = set(header.lower() for header in only) - ... print '---start---' - ... for header in sorted(only): - ... for value in sorted(msg.get_all(header, ())): - ... print '%s: %s' % (header, value) - ... print '---end---' - -The `rfc-2369` handler adds the `List-` headers. `List-Id` is always added. - - >>> from mailman.pipeline.rfc_2369 import process - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> list_headers(msg, 'list-id') - ---start--- - list-id: <test.example.com> - ---end--- - - -Fewer headers -============= - -Some people don't like these headers because their mail readers aren't good -about hiding them. A list owner can turn these headers off. - - >>> mlist.include_rfc2369_headers = False - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> process(mlist, msg, {}) - >>> list_headers(msg) - ---start--- - ---end--- - -Messages which Mailman generates itself, such as user or owner notifications, -have a reduced set of `List-` headers. Specifically, there is no `List-Post`, -`List-Archive` or `Archived-At` header. - - >>> mlist.include_rfc2369_headers = True - >>> mlist.include_list_post_header = False - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... """) - >>> process(mlist, msg, dict(reduced_list_headers=True)) - >>> list_headers(msg) - ---start--- - list-help: <mailto:test-request@example.com?subject=help> - list-id: <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--- - - -List-Post header -================ - -Discussion lists, to which any subscriber can post, also have a `List-Post` -header which contains the `mailto:` URL used to send messages to the list. - - >>> mlist.include_list_post_header = True - >>> 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: <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--- - - -List-Id header -============== - -If the mailing list has a description, then it is included in the ``List-Id`` -header. - - >>> mlist.description = 'My test mailing list' - >>> 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--- - -Any existing ``List-Id`` headers are removed from the original message. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... List-ID: <123.456.789> - ... - ... """) - - >>> process(mlist, msg, {}) - >>> list_headers(msg, only='list-id') - ---start--- - list-id: My test mailing list <test.example.com> - ---end--- - - -Archive headers -=============== - -When the mailing list is configured to enable archiving, a `List-Archive` -header will be added. - - >>> mlist.archive = True - -`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 = False - >>> 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 -.. _`RFC 5064`: http://www.faqs.org/rfcs/rfc5064.html diff --git a/src/mailman/pipeline/docs/subject-munging.rst b/src/mailman/pipeline/docs/subject-munging.rst deleted file mode 100644 index e7a6553ce..000000000 --- a/src/mailman/pipeline/docs/subject-munging.rst +++ /dev/null @@ -1,249 +0,0 @@ -=============== -Subject munging -=============== - -Messages that flow through the global pipeline get their headers *cooked*, -which basically means that their headers go through several mostly unrelated -transformations. Some headers get added, others get changed. Some of these -changes depend on mailing list settings and others depend on how the message -is getting sent through the system. We'll take things one-by-one. - - >>> mlist = create_list('_xtest@example.com') - - -Inserting a prefix -================== - -Another thing header cooking does is *munge* the ``Subject`` header by -inserting the subject prefix for the list at the front. If there's no subject -header in the original message, Mailman uses a canned default. In order to do -subject munging, a mailing list must have a preferred language. -:: - - >>> mlist.subject_prefix = '[XTest] ' - >>> mlist.preferred_language = 'en' - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... - ... A message of great import. - ... """) - >>> msgdata = {} - - >>> from mailman.pipeline.cook_headers import process - >>> process(mlist, msg, msgdata) - -The original subject header is stored in the message metadata. We must print -the new ``Subject`` header because it gets converted from a string to an -``email.header.Header`` instance which has an unhelpful ``repr``. - - >>> msgdata['origsubj'] - u'' - >>> print msg['subject'] - [XTest] (no subject) - -If the original message had a ``Subject`` header, then the prefix is inserted -at the beginning of the header's value. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: Something important - ... - ... A message of great import. - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msgdata['origsubj'] - Something important - >>> print msg['subject'] - [XTest] Something important - -``Subject`` headers are not munged for digest messages. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: Something important - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, dict(isdigest=True)) - >>> print msg['subject'] - Something important - -Nor are they munged for *fast tracked* messages, which are generally defined -as messages that Mailman crafts internally. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: Something important - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, dict(_fasttrack=True)) - >>> print msg['subject'] - Something important - -If a ``Subject`` header already has a prefix, usually following a ``Re:`` -marker, another one will not be added but the prefix will be moved to the -front of the header text. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: Re: [XTest] Something important - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest] Re: Something important - -If the ``Subject`` header has a prefix at the front of the header text, that's -where it will stay. This is called *new style* prefixing and is the only -option available in Mailman 3. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: [XTest] Re: Something important - ... - ... A message of great import. - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest] Re: Something important - - -Internationalized headers -========================= - -Internationalization adds some interesting twists to the handling of subject -prefixes. Part of what makes this interesting is the encoding of i18n headers -using RFC 2047, and lists whose preferred language is in a different character -set than the encoded header. - - >>> msg = message_from_string("""\ - ... Subject: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - >>> unicode(msg['subject']) - u'[XTest] \u30e1\u30fc\u30eb\u30de\u30f3' - - -Prefix numbers -============== - -Subject prefixes support a placeholder for the numeric post id. Every time a -message is posted to the mailing list, a *post id* gets incremented. This is -a purely sequential integer that increases monotonically. By added a ``%d`` -placeholder to the subject prefix, this post id can be included in the prefix. - - >>> mlist.subject_prefix = '[XTest %d] ' - >>> mlist.post_id = 456 - >>> msg = message_from_string("""\ - ... Subject: Something important - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest 456] Something important - -This works even when the message is a reply, except that in this case, the -numeric post id in the generated subject prefix is updated with the new post -id. - - >>> msg = message_from_string("""\ - ... Subject: [XTest 123] Re: Something important - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest 456] Re: Something important - -If the ``Subject`` header had old style prefixing, the prefix is moved to the -front of the header text. - - >>> msg = message_from_string("""\ - ... Subject: Re: [XTest 123] Something important - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest 456] Re: Something important - - -And of course, the proper thing is done when posting id numbers are included -in the subject prefix, and the subject is encoded non-ASCII. - - >>> msg = message_from_string("""\ - ... Subject: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest 456] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - >>> unicode(msg['subject']) - u'[XTest 456] \u30e1\u30fc\u30eb\u30de\u30f3' - -Even more fun is when the internationalized ``Subject`` header already has a -prefix, possibly with a different posting number. - - >>> msg = message_from_string("""\ - ... Subject: [XTest 123] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest 456] Re: =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - -.. - # XXX This requires Python email patch #1681333 to succeed. - # >>> unicode(msg['subject']) - # u'[XTest 456] Re: \u30e1\u30fc\u30eb\u30de\u30f3' - -As before, old style subject prefixes are re-ordered. - - >>> msg = message_from_string("""\ - ... Subject: Re: [XTest 123] =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest 456] Re: - =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - -.. - # XXX This requires Python email patch #1681333 to succeed. - # >>> unicode(msg['subject']) - # u'[XTest 456] Re: \u30e1\u30fc\u30eb\u30de\u30f3' - - -In this test case, we get an extra space between the prefix and the original -subject. It's because the original is *crooked*. Note that a ``Subject`` -starting with '\n ' is generated by some version of Eudora Japanese edition. - - >>> mlist.subject_prefix = '[XTest] ' - >>> msg = message_from_string("""\ - ... Subject: - ... Important message - ... - ... """) - >>> process(mlist, msg, {}) - >>> print msg['subject'] - [XTest] Important message - -And again, with an RFC 2047 encoded header. - - >>> msg = message_from_string("""\ - ... Subject: - ... =?iso-2022-jp?b?GyRCJWEhPCVrJV4lcxsoQg==?= - ... - ... """) - >>> process(mlist, msg, {}) - -.. - # XXX This one does not appear to work the same way as - # test_subject_munging_prefix_crooked() in the old Python-based tests. I need - # to get Tokio to look at this. - # >>> print msg['subject'] - # [XTest] =?iso-2022-jp?b?IBskQiVhITwlayVeJXMbKEI=?= diff --git a/src/mailman/pipeline/docs/tagger.rst b/src/mailman/pipeline/docs/tagger.rst deleted file mode 100644 index 80e682119..000000000 --- a/src/mailman/pipeline/docs/tagger.rst +++ /dev/null @@ -1,238 +0,0 @@ -============== -Message tagger -============== - -Mailman has a topics system which works like this: a mailing list -administrator sets up one or more topics, which is essentially a named regular -expression. The topic name can be any arbitrary string, and the name serves -double duty as the *topic tag*. Each message that flows the mailing list has -its ``Subject:`` and ``Keywords:`` headers compared against these regular -expressions. The message then gets tagged with the topic names of each hit. - - >>> mlist = create_list('_xtest@example.com') - -Topics must be enabled for Mailman to do any topic matching, even if topics -are defined. -:: - - >>> mlist.topics = [('bar fight', '.*bar.*', 'catch any bars', False)] - >>> mlist.topics_enabled = False - >>> mlist.topics_bodylines_limit = 0 - - >>> msg = message_from_string("""\ - ... Subject: foobar - ... Keywords: barbaz - ... - ... """) - >>> msgdata = {} - - >>> from mailman.pipeline.tagger import process - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - Subject: foobar - Keywords: barbaz - <BLANKLINE> - <BLANKLINE> - >>> msgdata - {} - -However, once topics are enabled, message will be tagged. There are two -artifacts of tagging; an ``X-Topics:`` header is added with the topic name, -and the message metadata gets a key with a list of matching topic names. - - >>> mlist.topics_enabled = True - >>> msg = message_from_string("""\ - ... Subject: foobar - ... Keywords: barbaz - ... - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - Subject: foobar - Keywords: barbaz - X-Topics: bar fight - <BLANKLINE> - <BLANKLINE> - >>> msgdata['topichits'] - [u'bar fight'] - - -Scanning body lines -=================== - -The tagger can also look at a certain number of body lines, but only for -``Subject:`` and ``Keyword:`` header-like lines. When set to zero, no body -lines are scanned. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: nothing - ... Keywords: at all - ... - ... X-Ignore: something else - ... Subject: foobar - ... Keywords: barbaz - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - From: aperson@example.com - Subject: nothing - Keywords: at all - <BLANKLINE> - X-Ignore: something else - Subject: foobar - Keywords: barbaz - <BLANKLINE> - >>> msgdata - {} - -But let the tagger scan a few body lines and the matching headers will be -found. - - >>> mlist.topics_bodylines_limit = 5 - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: nothing - ... Keywords: at all - ... - ... X-Ignore: something else - ... Subject: foobar - ... Keywords: barbaz - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - From: aperson@example.com - Subject: nothing - Keywords: at all - X-Topics: bar fight - <BLANKLINE> - X-Ignore: something else - Subject: foobar - Keywords: barbaz - <BLANKLINE> - >>> msgdata['topichits'] - [u'bar fight'] - -However, scanning stops at the first body line that doesn't look like a -header. - - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: nothing - ... Keywords: at all - ... - ... This is not a header - ... Subject: foobar - ... Keywords: barbaz - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - From: aperson@example.com - Subject: nothing - Keywords: at all - <BLANKLINE> - This is not a header - Subject: foobar - Keywords: barbaz - >>> msgdata - {} - -When set to a negative number, all body lines will be scanned. - - >>> mlist.topics_bodylines_limit = -1 - >>> lots_of_headers = '\n'.join(['X-Ignore: zip'] * 100) - >>> msg = message_from_string("""\ - ... From: aperson@example.com - ... Subject: nothing - ... Keywords: at all - ... - ... %s - ... Subject: foobar - ... Keywords: barbaz - ... """ % lots_of_headers) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> # Rather than print out 100 X-Ignore: headers, let's just prove that - >>> # the X-Topics: header exists, meaning that the tagger did its job. - >>> print msg['x-topics'] - bar fight - >>> msgdata['topichits'] - [u'bar fight'] - - -Scanning sub-parts -================== - -The tagger will also scan the body lines of text subparts in a multipart -message, using the same rules as if all those body lines lived in a single -text payload. - - >>> msg = message_from_string("""\ - ... Subject: Was - ... Keywords: Raw - ... Content-Type: multipart/alternative; boundary="BOUNDARY" - ... - ... --BOUNDARY - ... From: sabo - ... To: obas - ... - ... Subject: farbaw - ... Keywords: barbaz - ... - ... --BOUNDARY-- - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg.as_string() - Subject: Was - Keywords: Raw - Content-Type: multipart/alternative; boundary="BOUNDARY" - X-Topics: bar fight - <BLANKLINE> - --BOUNDARY - From: sabo - To: obas - <BLANKLINE> - Subject: farbaw - Keywords: barbaz - <BLANKLINE> - --BOUNDARY-- - <BLANKLINE> - >>> msgdata['topichits'] - [u'bar fight'] - -But the tagger will not descend into non-text parts. - - >>> msg = message_from_string("""\ - ... Subject: Was - ... Keywords: Raw - ... Content-Type: multipart/alternative; boundary=BOUNDARY - ... - ... --BOUNDARY - ... From: sabo - ... To: obas - ... Content-Type: message/rfc822 - ... - ... Subject: farbaw - ... Keywords: barbaz - ... - ... --BOUNDARY - ... From: sabo - ... To: obas - ... Content-Type: message/rfc822 - ... - ... Subject: farbaw - ... Keywords: barbaz - ... - ... --BOUNDARY-- - ... """) - >>> msgdata = {} - >>> process(mlist, msg, msgdata) - >>> print msg['x-topics'] - None - >>> msgdata - {} diff --git a/src/mailman/pipeline/docs/to-outgoing.rst b/src/mailman/pipeline/docs/to-outgoing.rst deleted file mode 100644 index 816aa4ca6..000000000 --- a/src/mailman/pipeline/docs/to-outgoing.rst +++ /dev/null @@ -1,42 +0,0 @@ -==================== -The outgoing handler -==================== - -Mailman's outgoing queue is used as the wrapper around SMTP delivery to the -upstream mail server. The to-outgoing handler does little more than drop the -message into the outgoing queue. - - >>> mlist = create_list('test@example.com') - -Craft a message destined for the outgoing queue. Include some random metadata -as if this message had passed through some other handlers. -:: - - >>> msg = message_from_string("""\ - ... Subject: Here is a message - ... - ... Something of great import. - ... """) - - >>> msgdata = dict(foo=1, bar=2, verp=True) - >>> handler = config.handlers['to-outgoing'] - >>> handler.process(mlist, msg, msgdata) - -While the queued message will not be changed, the queued metadata will have an -additional key set: the mailing list name. - - >>> from mailman.testing.helpers import get_queue_messages - >>> messages = get_queue_messages('out') - >>> len(messages) - 1 - >>> print messages[0].msg.as_string() - Subject: Here is a message - <BLANKLINE> - Something of great import. - >>> dump_msgdata(messages[0].msgdata) - _parsemsg: False - bar : 2 - foo : 1 - listname : test@example.com - verp : True - version : 3 |
