summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--buildout.cfg3
-rw-r--r--mailman/Archiver/HyperArch.py21
-rw-r--r--mailman/Mailbox.py5
-rw-r--r--mailman/app/notifications.py2
-rw-r--r--mailman/app/replybot.py1
-rw-r--r--mailman/archiving/mhonarc.py3
-rw-r--r--mailman/bin/make_instance.py171
-rw-r--r--mailman/config/config.py3
-rw-r--r--mailman/config/schema.cfg84
-rw-r--r--mailman/database/pending.py2
-rw-r--r--mailman/docs/lifecycle.txt3
-rw-r--r--mailman/docs/mlist-addresses.txt9
-rw-r--r--mailman/docs/pipelines.txt20
-rw-r--r--mailman/docs/styles.txt23
-rw-r--r--mailman/interfaces/styles.py7
-rw-r--r--mailman/mta/postfix.py3
-rw-r--r--mailman/mta/smtp_direct.py (renamed from mailman/pipeline/smtp_direct.py)0
-rw-r--r--mailman/pipeline/cleanse_dkim.py1
-rw-r--r--mailman/pipeline/docs/cook-headers.txt6
-rw-r--r--mailman/pipeline/docs/scrubber.txt21
-rw-r--r--mailman/pipeline/docs/to-outgoing.txt42
-rw-r--r--mailman/queue/archive.py12
-rw-r--r--mailman/queue/bounce.py22
-rw-r--r--mailman/queue/command.py8
-rw-r--r--mailman/queue/lmtp.py9
-rw-r--r--mailman/queue/news.py17
-rw-r--r--mailman/queue/outgoing.py13
-rw-r--r--mailman/rules/docs/header-matching.txt12
-rw-r--r--mailman/styles/manager.py92
-rw-r--r--mailman/testing/layers.py10
30 files changed, 330 insertions, 295 deletions
diff --git a/buildout.cfg b/buildout.cfg
index f502d24e9..c02c4c332 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -4,8 +4,7 @@ parts =
tags
test
unzip = true
-# bzr branch lp:~barry/lazr.config/megamerge
-develop = . /Users/barry/projects/lazr/megamerge
+develop = .
[interpreter]
recipe = zc.recipe.egg
diff --git a/mailman/Archiver/HyperArch.py b/mailman/Archiver/HyperArch.py
index 7b65d2f82..d9477cc3f 100644
--- a/mailman/Archiver/HyperArch.py
+++ b/mailman/Archiver/HyperArch.py
@@ -40,10 +40,10 @@ import binascii
from email.Charset import Charset
from email.Errors import HeaderParseError
from email.Header import decode_header, make_header
+from lazr.config import as_boolean
from locknix.lockfile import Lock
from string import Template
-from mailman import Defaults
from mailman import Utils
from mailman import i18n
from mailman.Archiver import HyperDatabase
@@ -176,7 +176,7 @@ def quick_maketext(templatefile, dict=None, lang=None, mlist=None):
listname = mlist.fqdn_listname
if lang is None:
if mlist is None:
- lang = Defaults.DEFAULT_SERVER_LANGUAGE
+ lang = config.mailman.default_language
else:
lang = mlist.preferred_language
cachekey = (templatefile, lang, listname)
@@ -252,7 +252,7 @@ class Article(pipermail.Article):
self._lang = lang
self._mlist = mlist
- if Defaults.ARCHIVER_OBSCURES_EMAILADDRS:
+ if as_boolean(config.archiver.pipermail.obscure_email_addresses):
# Avoid i18n side-effects. Note that the language for this
# article (for this list) could be different from the site-wide
# preferred language, so we need to ensure no side-effects will
@@ -327,7 +327,7 @@ class Article(pipermail.Article):
if hasattr(self, '_mlist'):
self._lang = self._mlist.preferred_language
else:
- self._lang = Defaults.DEFAULT_SERVER_LANGUAGE
+ self._lang = config.mailman.default_language
if not d.has_key('cenc'):
self.cenc = None
if not d.has_key('decoded'):
@@ -359,7 +359,7 @@ class Article(pipermail.Article):
if email:
self.decoded['email'] = email
if subject:
- if Defaults.ARCHIVER_OBSCURES_EMAILADDRS:
+ if as_boolean(config.archiver.pipermail.obscure_email_addresses):
with i18n.using_language(self._lang):
atmark = _(' at ')
subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)',
@@ -407,7 +407,7 @@ class Article(pipermail.Article):
d["subject_html"] = self.quote(self.subject)
d["subject_url"] = url_quote(self.subject)
d["in_reply_to_url"] = url_quote(self.in_reply_to)
- if Defaults.ARCHIVER_OBSCURES_EMAILADDRS:
+ if as_boolean(config.archiver.pipermail.obscure_email_addresses):
# Point the mailto url back to the list
author = re.sub('@', _(' at '), self.author)
emailurl = self._mlist.posting_address
@@ -511,7 +511,7 @@ class Article(pipermail.Article):
# Coerce the body to Unicode and replace any invalid characters.
if not isinstance(body, unicode):
body = unicode(body, cset, 'replace')
- if Defaults.ARCHIVER_OBSCURES_EMAILADDRS:
+ if as_boolean(config.archiver.pipermail.obscure_email_addresses):
with i18n.using_language(self._lang):
atmark = _(' at ')
body = re.sub(r'([-+,.\w]+)@([-+.\w]+)',
@@ -707,7 +707,7 @@ class HyperArchive(pipermail.T):
# The TOC is always in the charset of the list's preferred language
d['meta'] += html_charset % Utils.GetCharSet(mlist.preferred_language)
# The site can disable public access to the mbox file.
- if Defaults.PUBLIC_MBOX:
+ if as_boolean(config.archiver.pipermail.public_mbox):
template = 'archtoc.html'
else:
template = 'archtocnombox.html'
@@ -964,7 +964,7 @@ class HyperArchive(pipermail.T):
def write_index_entry(self, article):
subject = self.get_header("subject", article)
author = self.get_header("author", article)
- if Defaults.ARCHIVER_OBSCURES_EMAILADDRS:
+ if as_boolean(config.archiver.pipermail.obscure_email_addresses):
try:
author = re.sub('@', _(' at '), author)
except UnicodeError:
@@ -1137,7 +1137,8 @@ class HyperArchive(pipermail.T):
if j != -1 and (j < k or k == -1):
text = jr.group(1)
length = len(text)
- if Defaults.ARCHIVER_OBSCURES_EMAILADDRS:
+ if as_boolean(
+ config.archiver.pipermail.obscure_email_addresses):
text = re.sub('@', atmark, text)
URL = self.maillist.script_url('listinfo')
else:
diff --git a/mailman/Mailbox.py b/mailman/Mailbox.py
index d5902023a..3a2f079c4 100644
--- a/mailman/Mailbox.py
+++ b/mailman/Mailbox.py
@@ -22,10 +22,11 @@ import sys
import email
import mailbox
-from email.Errors import MessageParseError
-from email.Generator import Generator
+from email.errors import MessageParseError
+from email.generator import Generator
from mailman.Message import Message
+from mailman.config import config
diff --git a/mailman/app/notifications.py b/mailman/app/notifications.py
index 5d4b4572d..d60fe5fcf 100644
--- a/mailman/app/notifications.py
+++ b/mailman/app/notifications.py
@@ -31,8 +31,10 @@ from lazr.config import as_boolean
from mailman import Message
from mailman import Utils
from mailman import i18n
+from mailman.config import config
from mailman.interfaces.member import DeliveryMode
+
_ = i18n._
diff --git a/mailman/app/replybot.py b/mailman/app/replybot.py
index ba357e18b..e9c303418 100644
--- a/mailman/app/replybot.py
+++ b/mailman/app/replybot.py
@@ -31,6 +31,7 @@ import datetime
from mailman import Utils
from mailman import i18n
+from mailman.config import config
log = logging.getLogger('mailman.vette')
diff --git a/mailman/archiving/mhonarc.py b/mailman/archiving/mhonarc.py
index cc2e0d7e9..76d022c92 100644
--- a/mailman/archiving/mhonarc.py
+++ b/mailman/archiving/mhonarc.py
@@ -32,7 +32,6 @@ from string import Template
from urlparse import urljoin
from zope.interface import implements
-from mailman import Defaults
from mailman.config import config
from mailman.interfaces.archiver import IArchiver
@@ -53,7 +52,7 @@ class MHonArc:
"""See `IArchiver`."""
# XXX What about private MHonArc archives?
web_host = config.domains[mlist.host_name].url_host
- return Template(Defaults.PUBLIC_ARCHIVE_URL).safe_substitute(
+ return Template(config.archiver.mhonarc.base_url).safe_substitute(
listname=mlist.fqdn_listname,
hostname=web_host,
fqdn_listname=mlist.fqdn_listname,
diff --git a/mailman/bin/make_instance.py b/mailman/bin/make_instance.py
deleted file mode 100644
index cf20902ba..000000000
--- a/mailman/bin/make_instance.py
+++ /dev/null
@@ -1,171 +0,0 @@
-# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
-#
-# This file is part of GNU Mailman.
-#
-# GNU Mailman is free software: you can redistribute it and/or modify it under
-# the terms of the GNU General Public License as published by the Free
-# Software Foundation, either version 3 of the License, or (at your option)
-# any later version.
-#
-# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
-# more details.
-#
-# You should have received a copy of the GNU General Public License along with
-# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-
-import os
-import grp
-import pwd
-import sys
-import errno
-import shutil
-import optparse
-import setuptools
-
-from pkg_resources import resource_string
-from string import Template
-
-from mailman import Defaults
-from mailman.version import MAILMAN_VERSION
-from mailman.i18n import _
-
-
-SPACE = ' '
-
-
-
-def parseargs():
- parser = optparse.OptionParser(version=MAILMAN_VERSION,
- usage=_("""\
-%prog [options]
-
-Create a Mailman instance by generating all the necessary basic configuration
-support and intervening directories.
-"""))
- parser.add_option('-d', '--var-dir',
- type='string', default=Defaults.DEFAULT_VAR_DIRECTORY,
- help=_("""\
-The top-level runtime data directory. All supporting runtime data will be
-placed in subdirectories of this directory. It will be created if necessary,
-although this might require superuser privileges."""))
- parser.add_option('-f', '--force',
- default=False, action='store_true', help=_("""\
-Force overwriting of mailman.cfg file with new values. Ordinarily, Mailman
-will never overwrite this file because it would cause you to lose your
-configuration data."""))
- parser.add_option('-p', '--permchecks',
- default=False, action='store_true', help=_("""\
-Perform permission checks on the runtime directory."""))
- parser.add_option('-u', '--user',
- type='string', default=None, help=_("""\
-The user id or name to use for the runtime environment. If not specified, the
-current user is used."""))
- parser.add_option('-g', '--group',
- type='string', default=None, help=_("""\
-The group id or name to use for the runtime environment. If not specified, the
-current group is used."""))
- parser.add_option('-l', '--languages',
- default=Defaults.DEFAULT_SERVER_LANGUAGE, type='string',
- help=_("""\
-Space separated list of language codes to enable. Use -L to print all
-available language codes and the name of the associated native language.
-Default is to enable just English. Use the special code 'all' to enable all
-available languages."""))
- opts, args = parser.parse_args()
- if args:
- unexpected = SPACE.join(args)
- parser.error(_('Unexpected arguments: $unexpected'))
- return parser, opts, args
-
-
-
-def instantiate(var_dir, user, group, languages, force):
- # XXX This needs to be converted to use package resources.
- etc_dir = os.path.join(var_dir, 'etc')
- # Figure out which user name and group name to use.
- if user is None:
- uid = os.getuid()
- else:
- try:
- uid = int(user)
- except ValueError:
- try:
- uid = pwd.getpwnam(user).pw_uid
- except KeyError:
- parser.print_error(_('Unknown user: $user'))
- try:
- user_name = pwd.getpwuid(uid).pw_name
- except KeyError:
- parser.print_error(_('Unknown user: $user'))
- if group is None:
- gid = os.getgid()
- else:
- try:
- gid = int(group)
- except ValueError:
- try:
- gid = grp.getgrnam(group).gr_gid
- except KeyError:
- parser.print_error(_('Unknown group: $group'))
- try:
- group_name = grp.getgrgid(gid).gr_name
- except KeyError:
- parser.print_error(_('Unknown group: $group'))
- # Make the runtime dir if it doesn't yet exist.
- try:
- omask = os.umask(0)
- try:
- os.makedirs(etc_dir, 02775)
- finally:
- os.umask(omask)
- except OSError, e:
- # Ignore the exceptions if the directory already exists
- if e.errno <> errno.EEXIST:
- raise
- os.chown(etc_dir, uid, gid)
- # Create an etc/mailman.cfg file which contains just a few configuration
- # variables about the run-time environment that can't be calculated.
- # Don't overwrite mailman.cfg unless the -f flag was given.
- out_file_path = os.path.join(etc_dir, 'mailman.cfg')
- if os.path.exists(out_file_path) and not force:
- # The logging subsystem isn't up yet, so just print this to stderr.
- print >> sys.stderr, 'File exists:', out_file_path
- print >> sys.stderr, 'Use --force to override.'
- else:
- raw = Template(resource_string('mailman.extras', 'mailman.cfg.in'))
- processed = raw.safe_substitute(var_dir=var_dir,
- user_id=uid,
- user_name=user_name,
- group_name=group_name,
- group_id=gid,
- languages=SPACE.join(languages),
- )
- with open(out_file_path, 'w') as fp:
- fp.write(processed)
- # XXX Do --permchecks
-
-
-
-def main():
- parser, opts, args = parseargs()
- available_languages = set(Defaults._DEFAULT_LANGUAGE_DATA)
- enable_languages = set(opts.languages.split())
- if 'all' in enable_languages:
- languages = available_languages
- else:
- unknown_language = enable_languages - available_languages
- if unknown_language:
- print >> sys.stderr, 'Ignoring unknown language codes:', \
- SPACE.join(unknown_language)
- languages = available_languages & enable_languages
- # We need an absolute path for var_dir.
- var_dir = os.path.abspath(opts.var_dir)
- instantiate(var_dir, opts.user, opts.group, languages, opts.force)
-
-
-
-if __name__ == '__main__':
- main()
-
diff --git a/mailman/config/config.py b/mailman/config/config.py
index 349202338..44bb6d491 100644
--- a/mailman/config/config.py
+++ b/mailman/config/config.py
@@ -50,6 +50,7 @@ class Configuration(object):
self.domains = {} # email host -> IDomain
self.switchboards = {}
self.languages = LanguageManager()
+ self.style_manager = StyleManager()
self.QFILE_SCHEMA_VERSION = version.QFILE_SCHEMA_VERSION
self._config = None
self.filename = None
@@ -149,7 +150,7 @@ class Configuration(object):
# Always enable the server default language, which must be defined.
self.languages.enable_language(self._config.mailman.default_language)
self.ensure_directories_exist()
- self.style_manager = StyleManager()
+ self.style_manager.populate()
@property
def logger_configs(self):
diff --git a/mailman/config/schema.cfg b/mailman/config/schema.cfg
index 654bbaabc..df20a7370 100644
--- a/mailman/config/schema.cfg
+++ b/mailman/config/schema.cfg
@@ -266,7 +266,7 @@ chain: hold
incoming: mailman.mta.postfix.LMTP
# The class defining the interface to the outgoing mail transport agent.
-outgoing: mailman.mta.direct.SMTPDirect
+outgoing: mailman.mta.smtp_direct.process
# How to connect to the outgoing MTA.
smtp_host: localhost
@@ -296,6 +296,11 @@ max_sessions_per_connection: 0
# to 0.
max_delivery_threads: 0
+# How long should messages which have delivery failures continue to be
+# retried? After this period of time, a message that has failed recipients
+# will be dequeued and those recipients will never receive the message.
+delivery_retry_period: 5d
+
# These variables control the format and frequency of VERP-like delivery for
# better bounce detection. VERP is Variable Envelope Return Path, defined
# here:
@@ -370,6 +375,12 @@ verp_personalized_deliveries: no
# 1 to VERP every delivery, or to some number > 1 for only occasional VERPs.
verp_delivery_interval: 0
+# VERP format and regexp for probe messages.
+verp_probe_format: %(bounces)s+%(token)s
+verp_probe_regexp: ^(?P<bounces>[^+]+?)\+(?P<token>[^@]+)@.*$
+# Set this 'yes' to activate VERP probe for disabling by bounce.
+verp_probes: no
+
# This is the maximum number of automatic responses sent to an address because
# of -request messages or posting hold messages. This limit prevents response
# loops between Mailman and misconfigured remote email robots. Mailman
@@ -388,6 +399,17 @@ max_autoresponses_per_day: 10
# may wish to remove these headers by setting this to 'yes'.
remove_dkim_headers: no
+# This variable describe the program to use for regenerating the transport map
+# db file, from the associated plain text files. The file being updated will
+# be appended to this string (with a separating space), so it must be
+# appropriate for os.system().
+postfix_map_cmd: /usr/sbin/postmap
+
+
+[bounces]
+# How often should the bounce qrunner process queued detected bounces?
+register_bounces_every: 15m
+
[archiver.master]
# To add new archivers, define a new section based on this one, overriding the
@@ -416,6 +438,9 @@ command: /bin/echo
# This is the stock MHonArc archiver.
class: mailman.archiving.mhonarc.MHonArc
+base_url: http://$hostname/archives/$fqdn_listname
+
+
[archiver.mail_archive]
# This is the stock mail-archive.com archiver.
class: mailman.archiving.mailarchive.MailArchive
@@ -424,6 +449,29 @@ class: mailman.archiving.mailarchive.MailArchive
# This is the stock Pipermail archiver.
class: mailman.archiving.pipermail.Pipermail
+# This sets the default `clobber date' policy for the archiver. When a
+# message is to be archived either by Pipermail or an external archiver,
+# Mailman can modify the Date: header to be the date the message was received
+# instead of the Date: in the original message. This is useful if you
+# typically receive messages with outrageous dates. Set this to 0 to retain
+# the date of the original message, or to 1 to always clobber the date. Set
+# it to 2 to perform `smart overrides' on the date; when the date is outside
+# allowable_sane_date_skew (either too early or too late), then the received
+# date is substituted instead.
+clobber_date_policy: 2
+allowable_sane_date_skew: 15d
+
+# Pipermail archives contain the raw email addresses of the posting authors.
+# Some view this as a goldmine for spam harvesters. Set this to 'yes' to
+# moderately obscure email addresses, but note that this breaks mailto: URLs
+# in the archives too.
+obscure_email_addresses: yes
+
+# When the archive is public, should Pipermail also make the raw Unix mbox
+# file publically available?
+public_mbox: no
+
+
[archiver.prototype]
# This is a prototypical sample archiver.
class: mailman.archiving.prototype.Prototype
@@ -505,3 +553,37 @@ plain_digest_keep_headers:
Subject To Cc
Message-ID Keywords
Content-Type
+
+
+[nntp]
+# Set these variables if you need to authenticate to your NNTP server for
+# Usenet posting or reading. If no authentication is necessary, specify None
+# for both variables.
+username:
+password:
+
+# Set this if you have an NNTP server you prefer gatewayed lists to use.
+host:
+
+# This controls how headers must be cleansed in order to be accepted by your
+# NNTP server. Some servers like INN reject messages containing prohibited
+# headers, or duplicate headers. The NNTP server may reject the message for
+# other reasons, but there's little that can be programmatically done about
+# that.
+#
+# These headers (case ignored) are removed from the original message. This is
+# a whitespace separate list of headers.
+remove_headers:
+ nntp-posting-host nntp-posting-date x-trace
+ x-complaints-to xref date-received posted
+ posting-version relay-version received
+
+# These headers are left alone, unless there are duplicates in the original
+# message. Any second and subsequent headers are rewritten to the second
+# named header (case preserved). This is a list of header pairs, one pair per
+# line.
+rewrite_duplicate_headers:
+ To X-Original-To
+ CC X-Original-CC
+ Content-Transfer-Encoding X-Original-Content-Transfer-Encoding
+ MIME-Version X-MIME-Version
diff --git a/mailman/database/pending.py b/mailman/database/pending.py
index 6fe66f5fd..dc82085a2 100644
--- a/mailman/database/pending.py
+++ b/mailman/database/pending.py
@@ -87,7 +87,7 @@ class Pendings:
verifyObject(IPendable, pendable)
# Calculate the token and the lifetime.
if lifetime is None:
- lifetime = as_timedelta(config.pending_request_life)
+ lifetime = as_timedelta(config.mailman.pending_request_life)
# Calculate a unique token. Algorithm vetted by the Timbot. time()
# has high resolution on Linux, clock() on Windows. random gives us
# about 45 bits in Python 2.2, 53 bits on Python 2.3. The time and
diff --git a/mailman/docs/lifecycle.txt b/mailman/docs/lifecycle.txt
index 1e28f03ce..8a98bf0f5 100644
--- a/mailman/docs/lifecycle.txt
+++ b/mailman/docs/lifecycle.txt
@@ -58,8 +58,7 @@ Start by registering a test style.
... if 'test' in mailing_list.fqdn_listname:
... styles.append(self)
- >>> from mailman.core.styles import style_manager
- >>> style_manager.register(TestStyle())
+ >>> config.style_manager.register(TestStyle())
Using the higher level interface for creating a list, applies all matching
list styles.
diff --git a/mailman/docs/mlist-addresses.txt b/mailman/docs/mlist-addresses.txt
index fe76fb4d5..75ec3df37 100644
--- a/mailman/docs/mlist-addresses.txt
+++ b/mailman/docs/mlist-addresses.txt
@@ -67,9 +67,10 @@ dependent on the VERP_CONFIRM_FORMAT configuration variable.
>>> mlist.confirm_address('wookie')
u'_xtest-confirm+wookie@example.com'
- >>> from mailman import Defaults
- >>> old_format = Defaults.VERP_CONFIRM_FORMAT
- >>> Defaults.VERP_CONFIRM_FORMAT = '$address---$cookie'
+ >>> config.push('test config', """
+ ... [mta]
+ ... verp_confirm_format: $address---$cookie
+ ... """)
>>> mlist.confirm_address('cookie')
u'_xtest-confirm---cookie@example.com'
- >>> config.VERP_CONFIRM_FORMAT = old_format
+ >>> config.pop('test config')
diff --git a/mailman/docs/pipelines.txt b/mailman/docs/pipelines.txt
index 29888ee0b..205d210b1 100644
--- a/mailman/docs/pipelines.txt
+++ b/mailman/docs/pipelines.txt
@@ -10,8 +10,8 @@ message once it's started.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'xtest@example.com')
- >>> mlist.pipeline
- u'built-in'
+ >>> print mlist.pipeline
+ built-in
>>> from mailman.core.pipelines import process
@@ -49,11 +49,11 @@ etc.
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
Archived-At:
- http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ http://lists.example.com/archives/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Unsubscribe:
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-leave@example.com>
- List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/xtest@example.com>
List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
@@ -89,11 +89,11 @@ And the message is now sitting in various other processing queues.
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
Archived-At:
- http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ http://lists.example.com/archives/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Unsubscribe:
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-leave@example.com>
- List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/xtest@example.com>
List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
@@ -132,11 +132,11 @@ This is the message that will actually get delivered to end recipients.
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
Archived-At:
- http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ http://lists.example.com/archives/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Unsubscribe:
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-leave@example.com>
- List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/xtest@example.com>
List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
@@ -170,11 +170,11 @@ There's now one message in the digest mailbox, getting ready to be sent.
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-join@example.com>
Archived-At:
- http://lists.example.com/pipermail/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
+ http://lists.example.com/archives/4CMWUN6BHVCMHMDAOSJZ2Q72G5M32MWB
List-Unsubscribe:
<http://lists.example.com/listinfo/xtest@example.com>,
<mailto:xtest-leave@example.com>
- List-Archive: <http://lists.example.com/pipermail/xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/xtest@example.com>
List-Help: <mailto:xtest-request@example.com?subject=help>
<BLANKLINE>
First post!
diff --git a/mailman/docs/styles.txt b/mailman/docs/styles.txt
index 49f337e15..d12e57b08 100644
--- a/mailman/docs/styles.txt
+++ b/mailman/docs/styles.txt
@@ -14,17 +14,21 @@ modify the mailing list any way it wants.
Let's start with a vanilla mailing list and a default style manager.
>>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> from mailman.core.styles import StyleManager
+ >>> from mailman.styles.manager import StyleManager
>>> style_manager = StyleManager()
+ >>> style_manager.populate()
+ >>> sorted(style.name for style in style_manager.styles)
+ ['default']
The default style
-----------------
There is a default style which implements the legacy application of list
-defaults from Defaults.py. This style only matching a mailing list when no
-other styles match, and it has the lowest priority. The low priority means
-that it is matched last and if it matches, it is applied last.
+defaults from previous versions of Mailman. This style only matching a
+mailing list when no other styles match, and it has the lowest priority. The
+low priority means that it is matched last and if it matches, it is applied
+last.
>>> default_style = style_manager.get('default')
>>> default_style.name
@@ -43,17 +47,6 @@ manager's `lookup()` method.
>>> [style.name for style in style_manager.lookup(mlist)]
['default']
-If the site administrator modified their mailman.cfg file, the default style
-would pick this up and apply it to the mailing list.
-
- >>> print mlist.msg_footer
- None
- >>> from mailman import Defaults
- >>> Defaults.DEFAULT_MSG_FOOTER = u'default footer'
- >>> default_style.apply(mlist)
- >>> mlist.msg_footer
- u'default footer'
-
Registering styles
------------------
diff --git a/mailman/interfaces/styles.py b/mailman/interfaces/styles.py
index b2b52db4e..7cc0e50e8 100644
--- a/mailman/interfaces/styles.py
+++ b/mailman/interfaces/styles.py
@@ -109,3 +109,10 @@ class IStyleManager(Interface):
:param style: an IStyle.
:raises KeyError: If the style's name is not currently registered.
"""
+
+ def populate():
+ """Populate the styles from the configuration files.
+
+ This clears the current set of styles and resets them from those
+ defined in the configuration files.
+ """
diff --git a/mailman/mta/postfix.py b/mailman/mta/postfix.py
index 4b92d5789..be7dc9b9b 100644
--- a/mailman/mta/postfix.py
+++ b/mailman/mta/postfix.py
@@ -34,7 +34,6 @@ import datetime
from locknix.lockfile import Lock
from zope.interface import implements
-from mailman import Defaults
from mailman import Utils
from mailman.config import config
from mailman.interfaces.mta import IMailTransportAgent
@@ -113,7 +112,7 @@ class LMTP:
os.rename(path + '.new', path)
# Now that the new aliases file has been written, we must tell Postfix
# to generate a new .db file.
- command = Defaults.POSTFIX_MAP_CMD + ' ' + path
+ command = config.mta.postfix_map_cmd + ' ' + path
status = (os.system(command) >> 8) & 0xff
if status:
msg = 'command failure: %s, %s, %s'
diff --git a/mailman/pipeline/smtp_direct.py b/mailman/mta/smtp_direct.py
index b82fb9227..b82fb9227 100644
--- a/mailman/pipeline/smtp_direct.py
+++ b/mailman/mta/smtp_direct.py
diff --git a/mailman/pipeline/cleanse_dkim.py b/mailman/pipeline/cleanse_dkim.py
index d4d8f38ae..774fa78cf 100644
--- a/mailman/pipeline/cleanse_dkim.py
+++ b/mailman/pipeline/cleanse_dkim.py
@@ -34,6 +34,7 @@ __all__ = [
from lazr.config import as_boolean
from zope.interface import implements
+from mailman.config import config
from mailman.i18n import _
from mailman.interfaces.handler import IHandler
diff --git a/mailman/pipeline/docs/cook-headers.txt b/mailman/pipeline/docs/cook-headers.txt
index 732231e1f..ce13a45b6 100644
--- a/mailman/pipeline/docs/cook-headers.txt
+++ b/mailman/pipeline/docs/cook-headers.txt
@@ -188,7 +188,7 @@ But normally, a list will include these headers.
>>> process(mlist, msg, {})
>>> list_headers(msg)
---start---
- List-Archive: <http://lists.example.com/pipermail/_xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/_xtest@example.com>
List-Help: <mailto:_xtest-request@example.com?subject=help>
List-Id: <_xtest.example.com>
List-Post: <mailto:_xtest@example.com>
@@ -209,7 +209,7 @@ header.
>>> process(mlist, msg, {})
>>> list_headers(msg)
---start---
- List-Archive: <http://lists.example.com/pipermail/_xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/_xtest@example.com>
List-Help: <mailto:_xtest-request@example.com?subject=help>
List-Id: My test mailing list <_xtest.example.com>
List-Post: <mailto:_xtest@example.com>
@@ -248,7 +248,7 @@ List-Post header, which is reasonable for an announce only mailing list.
>>> process(mlist, msg, {})
>>> list_headers(msg)
---start---
- List-Archive: <http://lists.example.com/pipermail/_xtest@example.com>
+ List-Archive: <http://lists.example.com/archives/_xtest@example.com>
List-Help: <mailto:_xtest-request@example.com?subject=help>
List-Id: My test mailing list <_xtest.example.com>
List-Subscribe: <http://lists.example.com/listinfo/_xtest@example.com>,
diff --git a/mailman/pipeline/docs/scrubber.txt b/mailman/pipeline/docs/scrubber.txt
index eddd1939d..dec1c1f64 100644
--- a/mailman/pipeline/docs/scrubber.txt
+++ b/mailman/pipeline/docs/scrubber.txt
@@ -50,8 +50,10 @@ filename suggested in the message's Content-Disposition: header or not. If
enabled, the filename will be used when this header attribute is present (yes,
this is an unfortunate double negative).
- >>> from mailman import Defaults
- >>> Defaults.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = False
+ >>> config.push('test config', """
+ ... [scrubber]
+ ... use_attachment_filename: yes
+ ... """)
>>> msg = message_from_string("""\
... Content-Type: image/gif; name="xtest.gif"
... Content-Transfer-Encoding: base64
@@ -77,10 +79,13 @@ Saving the attachment does not alter the original message.
R0lGODdhAQABAIAAAAAAAAAAACwAAAAAAQABAAACAQUAOw==
The site administrator can also configure Mailman to ignore the
-Content-Disposition: filename. This is the default for reasons described in
-the Defaults.py.in file.
+Content-Disposition: filename. This is the default.
- >>> Defaults.SCRUBBER_DONT_USE_ATTACHMENT_FILENAME = True
+ >>> config.pop('test config')
+ >>> config.push('test config', """
+ ... [scrubber]
+ ... use_attachment_filename: no
+ ... """)
>>> msg = message_from_string("""\
... Content-Type: image/gif; name="xtest.gif"
... Content-Transfer-Encoding: base64
@@ -212,3 +217,9 @@ placeholder will be slightly different.
<BLANKLINE>
>>> read_url_from_message(msg)
'This is a text attachment.'
+
+
+Clean up
+--------
+
+ >>> config.pop('test config')
diff --git a/mailman/pipeline/docs/to-outgoing.txt b/mailman/pipeline/docs/to-outgoing.txt
index e7f412690..6142d86c7 100644
--- a/mailman/pipeline/docs/to-outgoing.txt
+++ b/mailman/pipeline/docs/to-outgoing.txt
@@ -64,10 +64,11 @@ If the list is set to personalize deliveries, and the global configuration
option to VERP personalized deliveries is set, then the message will be
VERP'd.
- # Save the original value for clean up.
- >>> from mailman import Defaults
- >>> verp_personalized_delivieries = Defaults.VERP_PERSONALIZED_DELIVERIES
- >>> Defaults.VERP_PERSONALIZED_DELIVERIES = True
+ >>> config.push('test config', """
+ ... [mta]
+ ... verp_personalized_deliveries: yes
+ ... """)
+
>>> from mailman.interfaces.mailinglist import Personalization
>>> mlist.personalize = Personalization.individual
>>> msgdata = dict(foo=1, bar=2)
@@ -80,7 +81,12 @@ VERP'd.
However, if the global configuration variable prohibits VERP'ing, even
personalized lists will not VERP.
- >>> Defaults.VERP_PERSONALIZED_DELIVERIES = False
+ >>> config.pop('test config')
+ >>> config.push('test config', """
+ ... [mta]
+ ... verp_personalized_deliveries: no
+ ... """)
+
>>> msgdata = dict(foo=1, bar=2)
>>> handler.process(mlist, msg, msgdata)
>>> print msgdata.get('verp')
@@ -93,9 +99,12 @@ the global configuration variable VERP_DELIVERY_INTERVAL. This variable tells
Mailman how often to VERP even non-personalized mailing lists. It can be set
to zero, which means non-personalized messages will never be VERP'd.
- # Save the original value for clean up.
- >>> verp_delivery_interval = Defaults.VERP_DELIVERY_INTERVAL
- >>> Defaults.VERP_DELIVERY_INTERVAL = 0
+ >>> config.pop('test config')
+ >>> config.push('test config', """
+ ... [mta]
+ ... verp_delivery_interval: 0
+ ... """)
+
>>> mlist.personalize = Personalization.none
>>> msgdata = dict(foo=1, bar=2)
>>> handler.process(mlist, msg, msgdata)
@@ -106,7 +115,12 @@ to zero, which means non-personalized messages will never be VERP'd.
If the interval is set to 1, then every message will be VERP'd.
- >>> Defaults.VERP_DELIVERY_INTERVAL = 1
+ >>> config.pop('test config')
+ >>> config.push('test config', """
+ ... [mta]
+ ... verp_delivery_interval: 1
+ ... """)
+
>>> for i in range(10):
... msgdata = dict(foo=1, bar=2)
... handler.process(mlist, msg, msgdata)
@@ -127,7 +141,12 @@ If the interval is set to 1, then every message will be VERP'd.
If the interval is set to some other number, then one out of that many posts
will be VERP'd.
- >>> Defaults.VERP_DELIVERY_INTERVAL = 3
+ >>> config.pop('test config')
+ >>> config.push('test config', """
+ ... [mta]
+ ... verp_delivery_interval: 3
+ ... """)
+
>>> for i in range(10):
... mlist.post_id = i
... msgdata = dict(foo=1, bar=2)
@@ -150,5 +169,4 @@ will be VERP'd.
Clean up
========
- >>> Defaults.VERP_PERSONALIZED_DELIVERIES = verp_personalized_delivieries
- >>> Defaults.VERP_DELIVERY_INTERVAL = verp_delivery_interval
+ >>> config.pop('test config')
diff --git a/mailman/queue/archive.py b/mailman/queue/archive.py
index c97bd86fb..75e8569e0 100644
--- a/mailman/queue/archive.py
+++ b/mailman/queue/archive.py
@@ -30,10 +30,9 @@ import logging
from datetime import datetime
from email.Utils import parsedate_tz, mktime_tz, formatdate
-from lazr.config import as_boolean
+from lazr.config import as_boolean, as_timedelta
from locknix.lockfile import Lock
-from mailman import Defaults
from mailman.config import config
from mailman.queue import Runner
@@ -53,9 +52,9 @@ class ArchiveRunner(Runner):
received_time = formatdate(msgdata['received_time'])
if not original_date:
clobber = True
- elif Defaults.ARCHIVER_CLOBBER_DATE_POLICY == 1:
+ elif int(config.archiver.pipermail.clobber_date_policy) == 1:
clobber = True
- elif Defaults.ARCHIVER_CLOBBER_DATE_POLICY == 2:
+ elif int(config.archiver.pipermail.clobber_date_policy) == 2:
# What's the timestamp on the original message?
timetup = parsedate_tz(original_date)
now = datetime.now()
@@ -64,8 +63,9 @@ class ArchiveRunner(Runner):
clobber = True
else:
utc_timestamp = datetime.fromtimestamp(mktime_tz(timetup))
- clobber = (abs(now - utc_timestamp) >
- Defaults.ARCHIVER_ALLOWABLE_SANE_DATE_SKEW)
+ date_skew = as_timedelta(
+ config.archiver.pipermail.allowable_sane_date_skew)
+ clobber = (abs(now - utc_timestamp) > date_skew)
except (ValueError, OverflowError):
# The likely cause of this is that the year in the Date: field
# is horribly incorrect, e.g. (from SF bug # 571634):
diff --git a/mailman/queue/bounce.py b/mailman/queue/bounce.py
index ce5040982..ced731d6d 100644
--- a/mailman/queue/bounce.py
+++ b/mailman/queue/bounce.py
@@ -24,8 +24,8 @@ import logging
import datetime
from email.Utils import parseaddr
+from lazr.config import as_timedelta
-from mailman import Defaults
from mailman import Utils
from mailman.Bouncers import BouncerAPI
from mailman.config import config
@@ -77,8 +77,9 @@ class BounceMixin:
config.DATA_DIR, 'bounce-events-%05d.pck' % os.getpid())
self._bounce_events_fp = None
self._bouncecnt = 0
- self._nextaction = (datetime.datetime.now() +
- Defaults.REGISTER_BOUNCES_EVERY)
+ self._nextaction = (
+ datetime.datetime.now() +
+ as_timedelta(config.bounces.register_bounces_every))
def _queue_bounces(self, listname, addrs, msg):
today = datetime.date.today()
@@ -130,7 +131,8 @@ class BounceMixin:
if self._nextaction > now or self._bouncecnt == 0:
return
# Let's go ahead and register the bounces we've got stored up
- self._nextaction = now + Defaults.REGISTER_BOUNCES_EVERY
+ self._nextaction = now + as_timedelta(
+ config.bounces.register_bounces_every)
self._register_bounces()
def _probe_bounce(self, mlist, token):
@@ -239,7 +241,7 @@ def verp_bounce(mlist, msg):
to = parseaddr(field)[1]
if not to:
continue # empty header
- mo = re.search(Defaults.VERP_REGEXP, to)
+ mo = re.search(config.mta.verp_regexp, to)
if not mo:
continue # no match of regexp
try:
@@ -248,8 +250,8 @@ def verp_bounce(mlist, msg):
# All is good
addr = '%s@%s' % mo.group('mailbox', 'host')
except IndexError:
- elog.error("VERP_REGEXP doesn't yield the right match groups: %s",
- Defaults.VERP_REGEXP)
+ elog.error("verp_regexp doesn't yield the right match groups: %s",
+ config.mta.verp_regexp)
return []
return [addr]
@@ -270,7 +272,7 @@ def verp_probe(mlist, msg):
to = parseaddr(field)[1]
if not to:
continue # empty header
- mo = re.search(Defaults.VERP_PROBE_REGEXP, to)
+ mo = re.search(config.mta.verp_probe_regexp, to)
if not mo:
continue # no match of regexp
try:
@@ -283,8 +285,8 @@ def verp_probe(mlist, msg):
return token
except IndexError:
elog.error(
- "VERP_PROBE_REGEXP doesn't yield the right match groups: %s",
- Defaults.VERP_PROBE_REGEXP)
+ "verp_probe_regexp doesn't yield the right match groups: %s",
+ config.mta.verp_probe_regexp)
return None
diff --git a/mailman/queue/command.py b/mailman/queue/command.py
index 45c9693b5..d2be7c9fd 100644
--- a/mailman/queue/command.py
+++ b/mailman/queue/command.py
@@ -37,7 +37,6 @@ from email.Header import decode_header, make_header
from email.Iterators import typed_subpart_iterator
from zope.interface import implements
-from mailman import Defaults
from mailman import Message
from mailman.config import config
from mailman.i18n import _
@@ -66,7 +65,7 @@ class CommandFinder:
elif msgdata.get('toleave'):
self.command_lines.append('leave')
elif msgdata.get('toconfirm'):
- mo = re.match(Defaults.VERP_CONFIRM_REGEXP, msg.get('to', ''))
+ mo = re.match(config.mta.verp_confirm_regexp, msg.get('to', ''))
if mo:
self.command_lines.append('confirm ' + mo.group('cookie'))
# Extract the subject header and do RFC 2047 decoding.
@@ -95,8 +94,9 @@ class CommandFinder:
assert isinstance(body, basestring), 'Non-string decoded payload'
lines = body.splitlines()
# Use no more lines than specified
- self.command_lines.extend(lines[:Defaults.EMAIL_COMMANDS_MAX_LINES])
- self.ignored_lines.extend(lines[Defaults.EMAIL_COMMANDS_MAX_LINES:])
+ max_lines = int(config.mailman.email_commands_max_lines)
+ self.command_lines.extend(lines[:max_lines])
+ self.ignored_lines.extend(lines[max_lines:])
def __iter__(self):
"""Return each command line, split into commands and arguments.
diff --git a/mailman/queue/lmtp.py b/mailman/queue/lmtp.py
index f52c99fae..3ac8796ca 100644
--- a/mailman/queue/lmtp.py
+++ b/mailman/queue/lmtp.py
@@ -29,9 +29,6 @@ so that the peer mail server can provide better diagnostics.
[1] RFC 2033 Local Mail Transport Protocol
http://www.faqs.org/rfcs/rfc2033.html
-
-See the variable USE_LMTP in Defaults.py.in for enabling this delivery
-mechanism.
"""
import email
@@ -41,7 +38,6 @@ import asyncore
from email.utils import parseaddr
-from mailman import Defaults
from mailman.Message import Message
from mailman.config import config
from mailman.database.transaction import txn
@@ -63,7 +59,7 @@ CRLF = '\r\n'
ERR_451 = '451 Requested action aborted: error in processing'
ERR_501 = '501 Message has defects'
ERR_502 = '502 Error: command HELO not implemented'
-ERR_550 = Defaults.LMTP_ERR_550
+ERR_550 = '550 Requested action not taken: mailbox unavailable'
# XXX Blech
smtpd.__version__ = 'Python LMTP queue runner 1.0'
@@ -86,7 +82,7 @@ def split_recipient(address):
subaddress may be None if this is the list's posting address.
"""
localpart, domain = address.split('@', 1)
- localpart = localpart.split(Defaults.VERP_DELIMITER, 1)[0]
+ localpart = localpart.split(config.mta.verp_delimiter, 1)[0]
parts = localpart.split(DASH)
if parts[-1] in SUBADDRESS_NAMES:
listname = DASH.join(parts[:-1])
@@ -186,7 +182,6 @@ class LMTPRunner(Runner, smtpd.SMTPServer):
msgdata.update(dict(
toowner=True,
envsender=config.mailman.site_owner,
- pipeline=Defaults.OWNER_PIPELINE,
))
queue = 'in'
elif subaddress is None:
diff --git a/mailman/queue/news.py b/mailman/queue/news.py
index ec744208d..ed408c72e 100644
--- a/mailman/queue/news.py
+++ b/mailman/queue/news.py
@@ -25,15 +25,15 @@ import nntplib
from cStringIO import StringIO
-COMMASPACE = ', '
-
-from mailman import Defaults
from mailman import Utils
+from mailman.config import config
from mailman.interfaces import NewsModeration
from mailman.queue import Runner
+COMMASPACE = ', '
log = logging.getLogger('mailman.error')
+
# Matches our Mailman crafted Message-IDs. See Utils.unique_message_id()
# XXX The move to email.utils.make_msgid() breaks this.
mcre = re.compile(r"""
@@ -64,8 +64,8 @@ class NewsRunner(Runner):
nntp_host, nntp_port = Utils.nntpsplit(mlist.nntp_host)
conn = nntplib.NNTP(nntp_host, nntp_port,
readermode=True,
- user=Defaults.NNTP_USERNAME,
- password=Defaults.NNTP_PASSWORD)
+ user=config.nntp.username,
+ password=config.nntp.password)
conn.post(fp)
except nntplib.error_temp, e:
log.error('(NNTPDirect) NNTP error for list "%s": %s',
@@ -147,9 +147,12 @@ def prepare_message(mlist, msg, msgdata):
# woon't completely sanitize the message, but it will eliminate the bulk
# of the rejections based on message headers. The NNTP server may still
# reject the message because of other problems.
- for header in Defaults.NNTP_REMOVE_HEADERS:
+ for header in config.nntp.remove_headers.split():
del msg[header]
- for header, rewrite in Defaults.NNTP_REWRITE_DUPLICATE_HEADERS:
+ for rewrite_pairs in config.nntp.rewrite_duplicate_headers.splitlines():
+ if len(rewrite_pairs.strip()) == 0:
+ continue
+ header, rewrite = rewrite_pairs.split()
values = msg.get_all(header, [])
if len(values) < 2:
# We only care about duplicates
diff --git a/mailman/queue/outgoing.py b/mailman/queue/outgoing.py
index ed648cca0..fdb1289fd 100644
--- a/mailman/queue/outgoing.py
+++ b/mailman/queue/outgoing.py
@@ -18,12 +18,13 @@
"""Outgoing queue runner."""
import os
+import sys
import socket
import logging
from datetime import datetime
+from lazr.config import as_timedelta
-from mailman import Defaults
from mailman.config import config
from mailman.core import errors
from mailman.queue import Runner
@@ -43,9 +44,10 @@ class OutgoingRunner(Runner, BounceMixin):
def __init__(self, slice=None, numslices=1):
Runner.__init__(self, slice, numslices)
BounceMixin.__init__(self)
- # We look this function up only at startup time
- handler = config.handlers[Defaults.DELIVERY_MODULE]
- self._func = handler.process
+ # We look this function up only at startup time.
+ module_name, callable_name = config.mta.outgoing.rsplit('.', 1)
+ __import__(module_name)
+ self._func = getattr(sys.modules[module_name], callable_name)
# This prevents smtp server connection problems from filling up the
# error log. It gets reset if the message was successfully sent, and
# set if there was a socket.error.
@@ -112,7 +114,8 @@ class OutgoingRunner(Runner, BounceMixin):
return False
else:
# Keep trying to delivery this message for a while
- deliver_until = now + Defaults.DELIVERY_RETRY_PERIOD
+ deliver_until = now + as_timedelta(
+ config.mta.delivery_retry_period)
msgdata['last_recip_count'] = len(recips)
msgdata['deliver_until'] = deliver_until
msgdata['recips'] = recips
diff --git a/mailman/rules/docs/header-matching.txt b/mailman/rules/docs/header-matching.txt
index 5e8d01ead..417000d67 100644
--- a/mailman/rules/docs/header-matching.txt
+++ b/mailman/rules/docs/header-matching.txt
@@ -2,15 +2,15 @@ Header matching
===============
Mailman can do pattern based header matching during its normal rule
-processing. There is a set of site-wide default header matchines specified in
-the configuaration file under the HEADER_MATCHES variable.
+processing. There is a set of site-wide default header matches specified in
+the configuration file under the [spam.headers] section.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'_xtest@example.com')
-Because the default HEADER_MATCHES variable is empty when the configuration
-file is read, we'll just extend the current header matching chain with a
-pattern that matches 4 or more stars, discarding the message if it hits.
+Because the default [spam.headers] section is empty, we'll just extend the
+current header matching chain with a pattern that matches 4 or more stars,
+discarding the message if it hits.
>>> chain = config.chains['header-match']
>>> chain.extend('x-spam-score', '[*]{4,}', 'discard')
@@ -98,7 +98,7 @@ List-specific header matching
Each mailing list can also be configured with a set of header matching regular
expression rules. These are used to impose list-specific header filtering
-with the same semantics as the global `HEADER_MATCHES` variable.
+with the same semantics as the global [spam.headers] section.
The list administrator wants to match not on four stars, but on three plus
signs, but only for the current mailing list.
diff --git a/mailman/styles/manager.py b/mailman/styles/manager.py
new file mode 100644
index 000000000..0b84f20e0
--- /dev/null
+++ b/mailman/styles/manager.py
@@ -0,0 +1,92 @@
+# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Style manager."""
+
+__metaclass__ = type
+__all__ = [
+ 'StyleManager',
+ ]
+
+
+import sys
+
+from operator import attrgetter
+from zope.interface import implements
+from zope.interface.verify import verifyObject
+
+from mailman.interfaces.styles import (
+ DuplicateStyleError, IStyle, IStyleManager)
+
+
+
+class StyleManager:
+ """The built-in style manager."""
+
+ implements(IStyleManager)
+
+ def __init__(self):
+ """Install all styles from the configuration files."""
+ self._styles = {}
+
+ def populate(self):
+ self._styles.clear()
+ # Avoid circular imports.
+ from mailman.config import config
+ # Install all the styles described by the configuration files.
+ for section in config.style_configs:
+ class_path = section['class']
+ module_name, class_name = class_path.rsplit('.', 1)
+ __import__(module_name)
+ style = getattr(sys.modules[module_name], class_name)()
+ assert section.name.startswith('style'), (
+ 'Bad style section name: %s' % section.name)
+ style.name = section.name[6:]
+ style.priority = int(section.priority)
+ self.register(style)
+
+ def get(self, name):
+ """See `IStyleManager`."""
+ return self._styles.get(name)
+
+ def lookup(self, mailing_list):
+ """See `IStyleManager`."""
+ matched_styles = []
+ for style in self.styles:
+ style.match(mailing_list, matched_styles)
+ for style in matched_styles:
+ yield style
+
+ @property
+ def styles(self):
+ """See `IStyleManager`."""
+ for style in sorted(self._styles.values(),
+ key=attrgetter('priority'),
+ reverse=True):
+ yield style
+
+ def register(self, style):
+ """See `IStyleManager`."""
+ verifyObject(IStyle, style)
+ if style.name in self._styles:
+ raise DuplicateStyleError(style.name)
+ self._styles[style.name] = style
+
+ def unregister(self, style):
+ """See `IStyleManager`."""
+ # Let KeyErrors percolate up.
+ del self._styles[style.name]
diff --git a/mailman/testing/layers.py b/mailman/testing/layers.py
index c9b99cab9..0bac91c5c 100644
--- a/mailman/testing/layers.py
+++ b/mailman/testing/layers.py
@@ -49,6 +49,7 @@ class ConfigLayer:
"""Layer for pushing and popping test configurations."""
var_dir = None
+ styles = None
@classmethod
def setUp(cls):
@@ -136,9 +137,7 @@ class ConfigLayer:
@classmethod
def testSetUp(cls):
- # Record the current (default) set of styles so that we can reset them
- # easily in the tear down.
- cls.styles = set(config.style_manager.styles)
+ pass
@classmethod
def testTearDown(cls):
@@ -153,10 +152,7 @@ class ConfigLayer:
config.db.message_store.delete_message(message['message-id'])
config.db.commit()
# Reset the global style manager.
- new_styles = set(config.style_manager.styles) - cls.styles
- for style in new_styles:
- config.style_manager.unregister(style)
- cls.styles = None
+ config.style_manager.populate()
# Flag to indicate that loggers should propagate to the console.
stderr = False