summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/Utils.py14
-rw-r--r--src/mailman/app/bounces.py15
-rw-r--r--src/mailman/app/membership.py4
-rw-r--r--src/mailman/app/moderator.py16
-rw-r--r--src/mailman/app/notifications.py8
-rw-r--r--src/mailman/app/registrar.py2
-rw-r--r--src/mailman/bin/inject.py2
-rw-r--r--src/mailman/chains/hold.py16
-rw-r--r--src/mailman/commands/cmd_help.py2
-rw-r--r--src/mailman/commands/docs/echo.txt2
-rw-r--r--src/mailman/commands/docs/end.txt2
-rw-r--r--src/mailman/commands/docs/join.txt2
-rw-r--r--src/mailman/commands/join.py2
-rw-r--r--src/mailman/config/schema.cfg14
-rw-r--r--src/mailman/docs/message.txt5
-rw-r--r--src/mailman/docs/registration.txt24
-rw-r--r--src/mailman/email/__init__.py0
-rw-r--r--src/mailman/email/message.py (renamed from src/mailman/Message.py)188
-rw-r--r--src/mailman/inject.py4
-rw-r--r--src/mailman/mta/smtp_direct.py2
-rw-r--r--src/mailman/pipeline/acknowledge.py8
-rw-r--r--src/mailman/pipeline/calculate_recipients.py3
-rw-r--r--src/mailman/pipeline/cook_headers.py2
-rw-r--r--src/mailman/pipeline/decorate.py2
-rw-r--r--src/mailman/pipeline/docs/cook-headers.txt4
-rw-r--r--src/mailman/pipeline/file_recipients.py3
-rw-r--r--src/mailman/pipeline/moderate.py7
-rw-r--r--src/mailman/pipeline/replybot.py19
-rw-r--r--src/mailman/pipeline/to_digest.py2
-rw-r--r--src/mailman/queue/__init__.py9
-rw-r--r--src/mailman/queue/command.py11
-rw-r--r--src/mailman/queue/docs/outgoing.txt1
-rw-r--r--src/mailman/queue/lmtp.py2
-rw-r--r--src/mailman/queue/maildir.py2
-rw-r--r--src/mailman/rules/moderation.py4
-rw-r--r--src/mailman/tests/test_documentation.py2
36 files changed, 172 insertions, 233 deletions
diff --git a/src/mailman/Utils.py b/src/mailman/Utils.py
index 9946273c9..b6715616c 100644
--- a/src/mailman/Utils.py
+++ b/src/mailman/Utils.py
@@ -22,6 +22,13 @@ message and address munging, a handy-dandy routine to map a function on all
the mailing lists, and whatever else doesn't belong elsewhere.
"""
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ ]
+
+
import os
import re
import cgi
@@ -31,10 +38,9 @@ import base64
import random
import logging
import htmlentitydefs
-import email.Header
-import email.Iterators
-from email.Errors import HeaderParseError
+from email.errors import HeaderParseError
+from email.header import decode_header, make_header
from lazr.config import as_boolean
from string import ascii_letters, digits, whitespace
@@ -609,7 +615,7 @@ def uquote(s):
def oneline(s, cset='us-ascii', in_unicode=False):
# Decode header string in one line and convert into specified charset
try:
- h = email.Header.make_header(email.Header.decode_header(s))
+ h = make_header(decode_header(s))
ustr = h.__unicode__()
line = UEMPTYSTRING.join(ustr.splitlines())
if in_unicode:
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index 875f615a5..aa7f51c77 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -17,7 +17,7 @@
"""Application level bounce handling."""
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
@@ -29,8 +29,8 @@ import logging
from email.mime.message import MIMEMessage
from email.mime.text import MIMEText
-from mailman import Message
from mailman import Utils
+from mailman.email.message import Message, UserNotification
from mailman.i18n import _
log = logging.getLogger('mailman.config')
@@ -40,7 +40,10 @@ log = logging.getLogger('mailman.config')
def bounce_message(mlist, msg, e=None):
# Bounce a message back to the sender, with an error message if provided
# in the exception argument.
- sender = msg.get_sender()
+ if msg.sender is None:
+ # We can't bounce the message if we don't know who it's supposed to go
+ # to.
+ return
subject = msg.get('subject', _('(no subject)'))
subject = Utils.oneline(subject,
Utils.GetCharSet(mlist.preferred_language))
@@ -49,10 +52,8 @@ def bounce_message(mlist, msg, e=None):
else:
notice = _(e.notice)
# Currently we always craft bounces as MIME messages.
- bmsg = Message.UserNotification(msg.get_sender(),
- mlist.owner_address,
- subject,
- lang=mlist.preferred_language)
+ bmsg = UserNotification(msg.sender, mlist.owner_address, subject,
+ lang=mlist.preferred_language)
# BAW: Be sure you set the type before trying to attach, or you'll get
# a MultipartConversionError.
bmsg.set_type('multipart/mixed')
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py
index 4b9609469..79e2501bd 100644
--- a/src/mailman/app/membership.py
+++ b/src/mailman/app/membership.py
@@ -28,12 +28,12 @@ __all__ = [
from email.utils import formataddr
-from mailman import Message
from mailman import Utils
from mailman import i18n
from mailman.app.notifications import send_goodbye_message
from mailman.config import config
from mailman.core import errors
+from mailman.email.message import Message, OwnerNotification
from mailman.interfaces.member import AlreadySubscribedError, MemberRole
_ = i18n._
@@ -133,5 +133,5 @@ def delete_member(mlist, address, admin_notif=None, userack=None):
{'listname': mlist.real_name,
'member' : formataddr((realname, address)),
}, mlist=mlist)
- msg = Message.OwnerNotification(mlist, subject, text)
+ msg = OwnerNotification(mlist, subject, text)
msg.send(mlist)
diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py
index b40a34344..1f66e00ac 100644
--- a/src/mailman/app/moderator.py
+++ b/src/mailman/app/moderator.py
@@ -17,7 +17,7 @@
"""Application support for moderators."""
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
@@ -34,7 +34,6 @@ import logging
from datetime import datetime
from email.utils import formataddr, formatdate, getaddresses, make_msgid
-from mailman import Message
from mailman import Utils
from mailman import i18n
from mailman.app.membership import add_member, delete_member
@@ -42,10 +41,12 @@ from mailman.app.notifications import (
send_admin_subscription_notice, send_welcome_message)
from mailman.config import config
from mailman.core import errors
+from mailman.email.message import Message, UserNotification
from mailman.interfaces import Action
from mailman.interfaces.member import AlreadySubscribedError, DeliveryMode
from mailman.interfaces.requests import RequestType
+
_ = i18n._
vlog = logging.getLogger('mailman.vette')
@@ -87,7 +88,7 @@ def hold_message(mlist, msg, msgdata=None, reason=None):
# the moderation interface.
msgdata['_mod_message_id'] = message_id
msgdata['_mod_fqdn_listname'] = mlist.fqdn_listname
- msgdata['_mod_sender'] = msg.get_sender()
+ msgdata['_mod_sender'] = msg.sender
msgdata['_mod_subject'] = msg.get('subject', _('(no subject)'))
msgdata['_mod_reason'] = reason
msgdata['_mod_hold_date'] = datetime.now().isoformat()
@@ -165,7 +166,7 @@ def handle_message(mlist, id, action,
if member:
language = member.preferred_language
with i18n.using_language(language):
- fmsg = Message.UserNotification(
+ fmsg = UserNotification(
addresses, mlist.bounces_address,
_('Forward of moderated message'),
lang=language)
@@ -212,7 +213,7 @@ def hold_subscription(mlist, address, realname, password, mode, language):
}, mlist=mlist)
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- msg = Message.UserNotification(
+ msg = UserNotification(
mlist.owner_address, mlist.owner_address,
subject, text, mlist.preferred_language)
msg.send(mlist, tomoderators=True)
@@ -284,7 +285,7 @@ def hold_unsubscription(mlist, address):
}, mlist=mlist)
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- msg = Message.UserNotification(
+ msg = UserNotification(
mlist.owner_address, mlist.owner_address,
subject, text, mlist.preferred_language)
msg.send(mlist, tomoderators=True)
@@ -346,6 +347,5 @@ def _refuse(mlist, request, recip, comment, origmsg=None, lang=None):
str(origmsg)
])
subject = _('Request to mailing list "$realname" rejected')
- msg = Message.UserNotification(recip, mlist.bounces_address,
- subject, text, lang)
+ msg = UserNotification(recip, mlist.bounces_address, subject, text, lang)
msg.send(mlist)
diff --git a/src/mailman/app/notifications.py b/src/mailman/app/notifications.py
index 9bef9998b..b1d77dc6f 100644
--- a/src/mailman/app/notifications.py
+++ b/src/mailman/app/notifications.py
@@ -30,10 +30,10 @@ __all__ = [
from email.utils import formataddr
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.email.message import Message, OwnerNotification, UserNotification
from mailman.interfaces.member import DeliveryMode
@@ -78,7 +78,7 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''):
digmode = _(' (Digest mode)')
else:
digmode = ''
- msg = Message.UserNotification(
+ msg = UserNotification(
address, mlist.request_address,
_('Welcome to the "$mlist.real_name" mailing list${digmode}'),
text, language)
@@ -104,7 +104,7 @@ def send_goodbye_message(mlist, address, language):
goodbye = Utils.wrap(mlist.goodbye_msg) + '\n'
else:
goodbye = ''
- msg = Message.UserNotification(
+ msg = UserNotification(
address, mlist.bounces_address,
_('You have been unsubscribed from the $mlist.real_name mailing list'),
goodbye, language)
@@ -132,5 +132,5 @@ def send_admin_subscription_notice(mlist, address, full_name, language):
{'listname' : mlist.real_name,
'member' : formataddr((full_name, address)),
}, mlist=mlist)
- msg = Message.OwnerNotification(mlist, subject, text)
+ msg = OwnerNotification(mlist, subject, text)
msg.send(mlist)
diff --git a/src/mailman/app/registrar.py b/src/mailman/app/registrar.py
index 6a2abeba9..654106668 100644
--- a/src/mailman/app/registrar.py
+++ b/src/mailman/app/registrar.py
@@ -31,9 +31,9 @@ import datetime
from pkg_resources import resource_string
from zope.interface import implements
-from mailman.Message import UserNotification
from mailman.Utils import ValidateEmail
from mailman.config import config
+from mailman.email.message import UserNotification
from mailman.i18n import _
from mailman.interfaces.domain import IDomain
from mailman.interfaces.member import MemberRole
diff --git a/src/mailman/bin/inject.py b/src/mailman/bin/inject.py
index 2bc8a49e3..73f1b9015 100644
--- a/src/mailman/bin/inject.py
+++ b/src/mailman/bin/inject.py
@@ -21,10 +21,10 @@ import sys
from email import message_from_string
from mailman import Utils
-from mailman.Message import Message
from mailman.configuration import config
from mailman.i18n import _
from mailman.inject import inject_text
+from mailman.message import Message
from mailman.options import SingleMailingListOptions
diff --git a/src/mailman/chains/hold.py b/src/mailman/chains/hold.py
index 16238a541..162834791 100644
--- a/src/mailman/chains/hold.py
+++ b/src/mailman/chains/hold.py
@@ -33,12 +33,12 @@ from email.utils import formatdate, make_msgid
from zope.interface import implements
from mailman import i18n
-from mailman.Message import UserNotification
from mailman.Utils import maketext, oneline, wrap, GetCharSet
from mailman.app.moderator import hold_message
from mailman.app.replybot import autorespond_to_sender, can_acknowledge
from mailman.chains.base import TerminalChainBase
from mailman.config import config
+from mailman.email.message import UserNotification
from mailman.interfaces.pending import IPendable
@@ -82,8 +82,7 @@ class HoldChain(TerminalChainBase):
# Get the language to send the response in. If the sender is a
# member, then send it in the member's language, otherwise send it in
# the mailing list's preferred language.
- sender = msg.get_sender()
- member = mlist.members.get_member(sender)
+ member = mlist.members.get_member(msg.sender)
language = (member.preferred_language
if member else mlist.preferred_language)
# A substitution dictionary for the email templates.
@@ -96,7 +95,7 @@ class HoldChain(TerminalChainBase):
substitutions = dict(
listname = mlist.fqdn_listname,
subject = original_subject,
- sender = sender,
+ sender = msg.sender,
reason = 'XXX', #reason,
confirmurl = '{0}/{1}'.format(mlist.script_url('confirm'), token),
admindb_url = mlist.script_url('admindb'),
@@ -118,7 +117,7 @@ class HoldChain(TerminalChainBase):
if (not msgdata.get('fromusenet') and
can_acknowledge(msg) and
mlist.respond_to_post_requests and
- autorespond_to_sender(mlist, sender, language)):
+ autorespond_to_sender(mlist, msg.sender, language)):
# We can respond to the sender with a message indicating their
# posting was held.
subject = _(
@@ -127,7 +126,7 @@ class HoldChain(TerminalChainBase):
text = maketext('postheld.txt', substitutions,
lang=send_language, mlist=mlist)
adminaddr = mlist.bounces_address
- nmsg = UserNotification(sender, adminaddr, subject, text,
+ nmsg = UserNotification(msg.sender, adminaddr, subject, text,
send_language)
nmsg.send(mlist)
# Now the message for the list moderators. This one should appear to
@@ -145,7 +144,8 @@ class HoldChain(TerminalChainBase):
substitutions['subject'] = original_subject
# craft the admin notification message and deliver it
subject = _(
- '$mlist.fqdn_listname post from $sender requires approval')
+ '$mlist.fqdn_listname post from $msg.sender requires '
+ 'approval')
nmsg = UserNotification(mlist.owner_address,
mlist.owner_address,
subject, lang=language)
@@ -174,5 +174,5 @@ also appear in the first line of the body of the reply.""")),
# XXX reason
reason = 'n/a'
log.info('HOLD: %s post from %s held, message-id=%s: %s',
- mlist.fqdn_listname, sender,
+ mlist.fqdn_listname, msg.sender,
msg.get('message-id', 'n/a'), reason)
diff --git a/src/mailman/commands/cmd_help.py b/src/mailman/commands/cmd_help.py
index eeee33ca7..30c8dc4d6 100644
--- a/src/mailman/commands/cmd_help.py
+++ b/src/mailman/commands/cmd_help.py
@@ -42,7 +42,7 @@ def process(res, args):
# Since this message is personalized, add some useful information if the
# address requesting help is a member of the list.
msg = res.msg
- for sender in msg.get_senders():
+ for sender in msg.senders:
if mlist.isMember(sender):
memberurl = mlist.GetOptionsURL(sender, absolute=1)
urlhelp = _(
diff --git a/src/mailman/commands/docs/echo.txt b/src/mailman/commands/docs/echo.txt
index 181cc58c8..95b50b523 100644
--- a/src/mailman/commands/docs/echo.txt
+++ b/src/mailman/commands/docs/echo.txt
@@ -20,7 +20,7 @@ The original message is ignored, but the results receive the echoed command.
>>> from mailman.queue.command import Results
>>> results = Results()
- >>> from mailman.Message import Message
+ >>> from mailman.email.message import Message
>>> print command.process(mlist, Message(), {}, ('foo', 'bar'), results)
ContinueProcessing.yes
>>> print unicode(results)
diff --git a/src/mailman/commands/docs/end.txt b/src/mailman/commands/docs/end.txt
index 4f6af26cb..98ca25bda 100644
--- a/src/mailman/commands/docs/end.txt
+++ b/src/mailman/commands/docs/end.txt
@@ -20,7 +20,7 @@ message isn't even looked at.
>>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'test@example.com')
- >>> from mailman.Message import Message
+ >>> from mailman.email.message import Message
>>> print command.process(mlist, Message(), {}, (), None)
ContinueProcessing.no
diff --git a/src/mailman/commands/docs/join.txt b/src/mailman/commands/docs/join.txt
index 9b85e816c..471a2a5b6 100644
--- a/src/mailman/commands/docs/join.txt
+++ b/src/mailman/commands/docs/join.txt
@@ -25,7 +25,7 @@ The mail command 'join' subscribes an email address to the mailing list.
No address to join
------------------
- >>> from mailman.Message import Message
+ >>> from mailman.email.message import Message
>>> from mailman.app.lifecycle import create_list
>>> from mailman.queue.command import Results
>>> mlist = create_list(u'alpha@example.com')
diff --git a/src/mailman/commands/join.py b/src/mailman/commands/join.py
index c14f3142b..81a018cff 100644
--- a/src/mailman/commands/join.py
+++ b/src/mailman/commands/join.py
@@ -61,7 +61,7 @@ example:
real_name, address = parseaddr(msg['from'])
# Address could be None or the empty string.
if not address:
- address = msg.get_sender()
+ address = msg.sender
if not address:
print >> results, _(
'$self.name: No valid address found to subscribe')
diff --git a/src/mailman/config/schema.cfg b/src/mailman/config/schema.cfg
index df20a7370..c93d58986 100644
--- a/src/mailman/config/schema.cfg
+++ b/src/mailman/config/schema.cfg
@@ -40,20 +40,6 @@ var_dir: /tmp/mailman
# The default language for this server.
default_language: en
-# When allowing only members to post to a mailing list, how is the sender of
-# the message determined? If this variable is set to Yes, then first the
-# message's envelope sender is used, with a fallback to the sender if there is
-# no envelope sender. Set this variable to No to always use the sender.
-#
-# The envelope sender is set by the SMTP delivery and is thus less easily
-# spoofed than the sender, which is typically just taken from the From: header
-# and thus easily spoofed by the end-user. However, sometimes the envelope
-# sender isn't set correctly and this will manifest itself by postings being
-# held for approval even if they appear to come from a list member. If you
-# are having this problem, set this variable to No, but understand that some
-# spoofed messages may get through.
-use_envelope_sender: no
-
# Membership tests for posting purposes are usually performed by looking at a
# set of headers, passing the test if any of their values match a member of
# the list. Headers are checked in the order given in this variable. The
diff --git a/src/mailman/docs/message.txt b/src/mailman/docs/message.txt
index dab9ddf0e..704842fe4 100644
--- a/src/mailman/docs/message.txt
+++ b/src/mailman/docs/message.txt
@@ -12,13 +12,12 @@ When Mailman needs to send a message to a user, it creates a UserNotification
instance, and then calls the .send() method on this object. This method
requires a mailing list instance.
- >>> mlist = config.db.list_manager.create(u'_xtest@example.com')
- >>> mlist.preferred_language = u'en'
+ >>> mlist = create_list(u'_xtest@example.com')
The UserNotification constructor takes the recipient address, the sender
address, an optional subject, optional body text, and optional language.
- >>> from mailman.Message import UserNotification
+ >>> from mailman.email.message import UserNotification
>>> msg = UserNotification(
... 'aperson@example.com',
... '_xtest@example.com',
diff --git a/src/mailman/docs/registration.txt b/src/mailman/docs/registration.txt
index d243188bc..5f9e0b7fd 100644
--- a/src/mailman/docs/registration.txt
+++ b/src/mailman/docs/registration.txt
@@ -59,30 +59,30 @@ Some amount of sanity checks are performed on the email address, although
honestly, not as much as probably should be done. Still, some patently bad
addresses are rejected outright.
- >>> registrar.register('')
+ >>> registrar.register(u'')
Traceback (most recent call last):
...
- InvalidEmailAddress: ''
- >>> registrar.register('some name@example.com')
+ InvalidEmailAddress: u''
+ >>> registrar.register(u'some name@example.com')
Traceback (most recent call last):
...
- InvalidEmailAddress: 'some name@example.com'
- >>> registrar.register('<script>@example.com')
+ InvalidEmailAddress: u'some name@example.com'
+ >>> registrar.register(u'<script>@example.com')
Traceback (most recent call last):
...
- InvalidEmailAddress: '<script>@example.com'
- >>> registrar.register('\xa0@example.com')
+ InvalidEmailAddress: u'<script>@example.com'
+ >>> registrar.register(u'\xa0@example.com')
Traceback (most recent call last):
...
- InvalidEmailAddress: '\xa0@example.com'
- >>> registrar.register('noatsign')
+ InvalidEmailAddress: u'\xa0@example.com'
+ >>> registrar.register(u'noatsign')
Traceback (most recent call last):
...
- InvalidEmailAddress: 'noatsign'
- >>> registrar.register('nodom@ain')
+ InvalidEmailAddress: u'noatsign'
+ >>> registrar.register(u'nodom@ain')
Traceback (most recent call last):
...
- InvalidEmailAddress: 'nodom@ain'
+ InvalidEmailAddress: u'nodom@ain'
Register an email address
diff --git a/src/mailman/email/__init__.py b/src/mailman/email/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/src/mailman/email/__init__.py
diff --git a/src/mailman/Message.py b/src/mailman/email/message.py
index ac41a758c..ca36e7ae5 100644
--- a/src/mailman/Message.py
+++ b/src/mailman/email/message.py
@@ -18,9 +18,19 @@
"""Standard Mailman message object.
This is a subclass of email.message.Message but provides a slightly extended
-interface which is more convenient for use inside Mailman.
+interface which is more convenient for use inside Mailman. It also supports
+safe pickle deserialization, even if the email package adds additional Message
+attributes.
"""
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'Message',
+ ]
+
+
import re
import email
import email.message
@@ -30,35 +40,37 @@ from email.charset import Charset
from email.header import Header
from lazr.config import as_boolean
-from mailman import Utils
+from mailman.Utils import GetCharSet
from mailman.config import config
-COMMASPACE = ', '
-mo = re.match(r'([\d.]+)', email.__version__)
-VERSION = tuple(int(s) for s in mo.group().split('.'))
+COMMASPACE = ', '
+VERSION = tuple(int(v) for v in email.__version__.split('.'))
class Message(email.message.Message):
def __init__(self):
- # We need a version number so that we can optimize __setstate__()
+ # We need a version number so that we can optimize __setstate__().
self.__version__ = VERSION
email.message.Message.__init__(self)
def __getitem__(self, key):
+ # Ensure that header values are unicodes.
value = email.message.Message.__getitem__(self, key)
if isinstance(value, str):
return unicode(value, 'ascii')
return value
def get(self, name, failobj=None):
+ # Ensure that header values are unicodes.
value = email.message.Message.get(self, name, failobj)
if isinstance(value, str):
return unicode(value, 'ascii')
return value
def get_all(self, name, failobj=None):
+ # Ensure all header values are unicodes.
missing = object()
all_values = email.message.Message.get_all(self, name, missing)
if all_values is missing:
@@ -70,133 +82,71 @@ class Message(email.message.Message):
def __repr__(self):
return self.__str__()
- def __setstate__(self, d):
- # The base class attributes have changed over time. Which could
- # affect Mailman if messages are sitting in the queue at the time of
- # upgrading the email package. We shouldn't burden email with this,
- # so we handle schema updates here.
- self.__dict__ = d
- # We know that email 2.4.3 is up-to-date
- version = d.get('__version__', (0, 0, 0))
- d['__version__'] = VERSION
- if version >= VERSION:
- return
- # Messages grew a _charset attribute between email version 0.97 and 1.1
- if not d.has_key('_charset'):
- self._charset = None
- # Messages grew a _default_type attribute between v2.1 and v2.2
- if not d.has_key('_default_type'):
- # We really have no idea whether this message object is contained
- # inside a multipart/digest or not, so I think this is the best we
- # can do.
- self._default_type = 'text/plain'
- # Header instances used to allow both strings and Charsets in their
- # _chunks, but by email 2.4.3 now it's just Charsets.
- headers = []
- hchanged = 0
- for k, v in self._headers:
- if isinstance(v, Header):
- chunks = []
- cchanged = 0
- for s, charset in v._chunks:
- if isinstance(charset, str):
- charset = Charset(charset)
- cchanged = 1
- chunks.append((s, charset))
- if cchanged:
- v._chunks = chunks
- hchanged = 1
- headers.append((k, v))
- if hchanged:
- self._headers = headers
-
- # I think this method ought to eventually be deprecated
- def get_sender(self):
- """Return the address considered to be the author of the email.
+ def __setstate__(self, values):
+ # The base class has grown and changed attributes over time. This can
+ # break messages sitting in Mailman's queues at the time of upgrading
+ # the email package. We can't (yet) change the email package to be
+ # safer for pickling, so we handle such changes here. Note that we're
+ # using Python 2.6's email package version 4.0.1 as a base line here.
+ self.__dict__ = values
+ # The pickled instance should have an __version__ string, but it may
+ # not if it's an email package message.
+ version = values.get('__version__', (0, 0, 0))
+ values['__version__'] = VERSION
+ # There's really nothing to check; there's nothing newer than email
+ # 4.0.1 at the moment.
- This can return either the From: header, the Sender: header or the
- envelope header (a.k.a. the unixfrom header). The first non-empty
- header value found is returned. However the search order is
- determined by the following:
+ @property
+ def sender(self):
+ """The address considered to be the author of the email.
- - If config.mailman.use_envelope_sender is true, then the search order
- is Sender:, From:, unixfrom
+ This is the first non-None value in the list of senders.
- - Otherwise, the search order is From:, Sender:, unixfrom
-
- unixfrom should never be empty. The return address is always
- lower cased.
-
- This method differs from get_senders() in that it returns one and only
- one address, and uses a different search order.
+ :return: The email address of the first found sender, or the empty
+ string if no sender address was found.
+ :rtype: email address
"""
- senderfirst = as_boolean(config.mailman.use_envelope_sender)
- if senderfirst:
- headers = ('sender', 'from')
- else:
- headers = ('from', 'sender')
- for h in headers:
- # Use only the first occurrance of Sender: or From:, although it's
- # not likely there will be more than one.
- fieldval = self[h]
- if not fieldval:
- continue
- addrs = email.utils.getaddresses([fieldval])
- try:
- realname, address = addrs[0]
- except IndexError:
- continue
+ for address in self.senders:
+ # This could be None or the empty string.
if address:
- break
- else:
- # We didn't find a non-empty header, so let's fall back to the
- # unixfrom address. This should never be empty, but if it ever
- # is, it's probably a Really Bad Thing. Further, we just assume
- # that if the unixfrom exists, the second field is the address.
- unixfrom = self.get_unixfrom()
- if unixfrom:
- address = unixfrom.split()[1]
- else:
- # TBD: now what?!
- address = ''
- return address.lower()
+ return address
+ return ''
- def get_senders(self):
+ @property
+ def senders(self):
"""Return a list of addresses representing the author of the email.
- The list will contain the following addresses (in order)
- depending on availability:
+ The list will contain email addresses in the order determined by the
+ configuration variable `sender_headers` in the `[mailman]` section.
+ By default it uses this list of headers in order:
1. From:
- 2. unixfrom (From_)
+ 2. envelope sender (i.e. From_, unixfrom, or RFC 2821 MAIL FROM)
3. Reply-To:
4. Sender:
- The return addresses are always lower cased.
+ The return addresses are guaranteed to be lower case or None. There
+ may be more than four values in the returned list, since some of the
+ originator headers above can appear multiple times in the message, or
+ contain multiple values.
+
+ :return: The list of email addresses that can be considered the sender
+ of the message.
+ :rtype: A list of email addresses or Nones
"""
- pairs = []
+ envelope_sender = self.get_unixfrom()
+ senders = []
for header in config.mailman.sender_headers.split():
header = header.lower()
if header == 'from_':
- # get_unixfrom() returns None if there's no envelope
- unix_from = self.get_unixfrom()
- fieldval = (unix_from if unix_from is not None else '')
- try:
- pairs.append(('', fieldval.split()[1]))
- except IndexError:
- # Ignore badly formatted unixfroms
- pass
+ senders.append(envelope_sender.lower()
+ if envelope_sender is not None
+ else '')
else:
- fieldvals = self.get_all(header)
- if fieldvals:
- pairs.extend(email.utils.getaddresses(fieldvals))
- authors = []
- for pair in pairs:
- address = pair[1]
- if address is not None:
- address = address.lower()
- authors.append(address)
- return authors
+ field_values = self.get_all(header, [])
+ senders.extend(address.lower() for (real_name, address)
+ in email.utils.getaddresses(field_values))
+ return senders
def get_filename(self, failobj=None):
"""Some MUA have bugs in RFC2231 filename encoding and cause
@@ -217,7 +167,7 @@ class UserNotification(Message):
Message.__init__(self)
charset = 'us-ascii'
if lang is not None:
- charset = Utils.GetCharSet(lang)
+ charset = GetCharSet(lang)
if text is not None:
self.set_payload(text.encode(charset), charset)
if subject is None:
@@ -263,7 +213,11 @@ class UserNotification(Message):
if mlist is not None:
enqueue_kws['listname'] = mlist.fqdn_listname
enqueue_kws.update(_kws)
- virginq.enqueue(self, **enqueue_kws)
+ # Keywords must be strings in Python 2.6.
+ str_keywords = dict()
+ for key, val in enqueue_kws.items():
+ str_keywords[str(key)] = val
+ virginq.enqueue(self, **str_keywords)
diff --git a/src/mailman/inject.py b/src/mailman/inject.py
index 079236932..bd30d116e 100644
--- a/src/mailman/inject.py
+++ b/src/mailman/inject.py
@@ -15,7 +15,7 @@
# You should have received a copy of the GNU General Public License along with
# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
@@ -27,8 +27,8 @@ __all__ = [
from email import message_from_string
from email.utils import formatdate, make_msgid
-from mailman.Message import Message
from mailman.config import config
+from mailman.email.message import Message
diff --git a/src/mailman/mta/smtp_direct.py b/src/mailman/mta/smtp_direct.py
index 717b3eb90..327820e72 100644
--- a/src/mailman/mta/smtp_direct.py
+++ b/src/mailman/mta/smtp_direct.py
@@ -151,7 +151,7 @@ def process(mlist, msg, msgdata):
# VERPing, it doesn't matter because bulkdeliver is working on a copy, but
# otherwise msg gets changed. If the list is anonymous, the original
# sender is long gone, but Cleanse.py has logged it.
- origsender = msgdata.get('original_sender', msg.get_sender())
+ origsender = msgdata.get('original_sender', msg.sender)
# `undelivered' is a copy of chunks that we pop from to do deliveries.
# This seems like a good tradeoff between robustness and resource
# utilization. If delivery really fails (i.e. qfiles/shunt type
diff --git a/src/mailman/pipeline/acknowledge.py b/src/mailman/pipeline/acknowledge.py
index de520df65..2ee27a5ae 100644
--- a/src/mailman/pipeline/acknowledge.py
+++ b/src/mailman/pipeline/acknowledge.py
@@ -30,8 +30,8 @@ __all__ = [
from zope.interface import implements
-from mailman import Message
from mailman import Utils
+from mailman.email.message import Message, UserNotification
from mailman.i18n import _
from mailman.interfaces.handler import IHandler
@@ -47,7 +47,7 @@ class Acknowledge:
def process(self, mlist, msg, msgdata):
"""See `IHandler`."""
# Extract the sender's address and find them in the user database
- sender = msgdata.get('original_sender', msg.get_sender())
+ sender = msgdata.get('original_sender', msg.sender)
member = mlist.members.get_member(sender)
if member is None or not member.acknowledge_posts:
# Either the sender is not a member, in which case we can't know
@@ -75,6 +75,6 @@ class Acknowledge:
# necessary for general delivery. Then enqueue it to the outgoing
# queue.
subject = _('$realname post acknowledgment')
- usermsg = Message.UserNotification(sender, mlist.bounces_address,
- subject, text, lang)
+ usermsg = UserNotification(sender, mlist.bounces_address,
+ subject, text, lang)
usermsg.send(mlist)
diff --git a/src/mailman/pipeline/calculate_recipients.py b/src/mailman/pipeline/calculate_recipients.py
index 9837c1e6b..0850db929 100644
--- a/src/mailman/pipeline/calculate_recipients.py
+++ b/src/mailman/pipeline/calculate_recipients.py
@@ -56,8 +56,7 @@ class CalculateRecipients:
return
# Should the original sender should be included in the recipients list?
include_sender = True
- sender = msg.get_sender()
- member = mlist.members.get_member(sender)
+ member = mlist.members.get_member(msg.sender)
if member and not member.receive_own_postings:
include_sender = False
# Support for urgent messages, which bypasses digests and disabled
diff --git a/src/mailman/pipeline/cook_headers.py b/src/mailman/pipeline/cook_headers.py
index 529d7ce5d..7da413cd2 100644
--- a/src/mailman/pipeline/cook_headers.py
+++ b/src/mailman/pipeline/cook_headers.py
@@ -74,7 +74,7 @@ def process(mlist, msg, msgdata):
# message, we want to save some of the information in the msgdata
# dictionary for later. Specifically, the sender header will get waxed,
# but we need it for the Acknowledge module later.
- msgdata['original_sender'] = msg.get_sender()
+ msgdata['original_sender'] = msg.sender
# VirginRunner sets _fasttrack for internally crafted messages.
fasttrack = msgdata.get('_fasttrack')
if not msgdata.get('isdigest') and not fasttrack:
diff --git a/src/mailman/pipeline/decorate.py b/src/mailman/pipeline/decorate.py
index e1fa0c155..87313446d 100644
--- a/src/mailman/pipeline/decorate.py
+++ b/src/mailman/pipeline/decorate.py
@@ -32,8 +32,8 @@ from email.MIMEText import MIMEText
from zope.interface import implements
from mailman import Utils
-from mailman.Message import Message
from mailman.config import config
+from mailman.email.message import Message
from mailman.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.utilities.string import expand
diff --git a/src/mailman/pipeline/docs/cook-headers.txt b/src/mailman/pipeline/docs/cook-headers.txt
index ce13a45b6..edbaaa133 100644
--- a/src/mailman/pipeline/docs/cook-headers.txt
+++ b/src/mailman/pipeline/docs/cook-headers.txt
@@ -39,8 +39,8 @@ But if there was no original sender, then the empty string will be saved.
... """)
>>> msgdata = {}
>>> process(mlist, msg, msgdata)
- >>> msgdata['original_sender']
- ''
+ >>> print msgdata['original_sender']
+ <BLANKLINE>
X-BeenThere header
diff --git a/src/mailman/pipeline/file_recipients.py b/src/mailman/pipeline/file_recipients.py
index 89d10d783..fd2db596a 100644
--- a/src/mailman/pipeline/file_recipients.py
+++ b/src/mailman/pipeline/file_recipients.py
@@ -58,8 +58,7 @@ class FileRecipients:
return
# If the sender is a member of the list, remove them from the file
# recipients.
- sender = msg.get_sender()
- member = mlist.members.get_member(sender)
+ member = mlist.members.get_member(msg.sender)
if member is not None:
addrs.discard(member.address.address)
msgdata['recips'] = addrs
diff --git a/src/mailman/pipeline/moderate.py b/src/mailman/pipeline/moderate.py
index 0b38c3a5a..b5bf38dc9 100644
--- a/src/mailman/pipeline/moderate.py
+++ b/src/mailman/pipeline/moderate.py
@@ -30,10 +30,10 @@ import re
from email.MIMEMessage import MIMEMessage
from email.MIMEText import MIMEText
-from mailman import Message
from mailman import Utils
from mailman.config import config
from mailman.core import errors
+from mailman.email.message import Message
from mailman.i18n import _
@@ -55,7 +55,7 @@ def process(mlist, msg, msgdata):
if msgdata.get('approved') or msgdata.get('fromusenet'):
return
# First of all, is the poster a member or not?
- for sender in msg.get_senders():
+ for sender in msg.senders:
if mlist.isMember(sender):
break
else:
@@ -92,7 +92,7 @@ def process(mlist, msg, msgdata):
# their own thing.
return
else:
- sender = msg.get_sender()
+ sender = msg.sender
# From here on out, we're dealing with non-members.
if matches_p(sender, mlist.accept_these_nonmembers):
return
@@ -154,7 +154,6 @@ error, contact the mailing list owner at %(listowner)s."""))
def do_discard(mlist, msg):
- sender = msg.get_sender()
# Do we forward auto-discards to the list owners?
if mlist.forward_auto_discards:
lang = mlist.preferred_language
diff --git a/src/mailman/pipeline/replybot.py b/src/mailman/pipeline/replybot.py
index e24777774..a204504b1 100644
--- a/src/mailman/pipeline/replybot.py
+++ b/src/mailman/pipeline/replybot.py
@@ -31,8 +31,8 @@ import datetime
from zope.interface import implements
-from mailman import Message
from mailman import Utils
+from mailman.email.message import Message, UserNotification
from mailman.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.utilities.string import expand
@@ -71,16 +71,15 @@ def process(mlist, msg, msgdata):
# Now see if we're in the grace period for this sender. graceperiod <= 0
# means always autorespond, as does an "X-Ack: yes" header (useful for
# debugging).
- sender = msg.get_sender()
now = time.time()
graceperiod = mlist.autoresponse_graceperiod
if graceperiod > NODELTA and ack <> 'yes':
if toadmin:
- quiet_until = mlist.admin_responses.get(sender, 0)
+ quiet_until = mlist.admin_responses.get(msg.sender, 0)
elif torequest:
- quiet_until = mlist.request_responses.get(sender, 0)
+ quiet_until = mlist.request_responses.get(msg.sender, 0)
else:
- quiet_until = mlist.postings_responses.get(sender, 0)
+ quiet_until = mlist.postings_responses.get(msg.sender, 0)
if quiet_until > now:
return
# Okay, we know we're going to auto-respond to this sender, craft the
@@ -102,8 +101,8 @@ def process(mlist, msg, msgdata):
rtext = mlist.autoresponse_postings_text
# Interpolation and Wrap the response text.
text = Utils.wrap(expand(rtext, d))
- outmsg = Message.UserNotification(sender, mlist.bounces_address,
- subject, text, mlist.preferred_language)
+ outmsg = UserNotification(msg.sender, mlist.bounces_address,
+ subject, text, mlist.preferred_language)
outmsg['X-Mailer'] = _('The Mailman Replybot')
# prevent recursions and mail loops!
outmsg['X-Ack'] = 'No'
@@ -113,11 +112,11 @@ def process(mlist, msg, msgdata):
# graceperiod is in days, we need # of seconds
quiet_until = now + graceperiod * 24 * 60 * 60
if toadmin:
- mlist.admin_responses[sender] = quiet_until
+ mlist.admin_responses[msg.sender] = quiet_until
elif torequest:
- mlist.request_responses[sender] = quiet_until
+ mlist.request_responses[msg.sender] = quiet_until
else:
- mlist.postings_responses[sender] = quiet_until
+ mlist.postings_responses[msg.sender] = quiet_until
diff --git a/src/mailman/pipeline/to_digest.py b/src/mailman/pipeline/to_digest.py
index ebb40a77c..78d8e1970 100644
--- a/src/mailman/pipeline/to_digest.py
+++ b/src/mailman/pipeline/to_digest.py
@@ -30,8 +30,8 @@ import datetime
from zope.interface import implements
-from mailman.Message import Message
from mailman.config import config
+from mailman.email.message import Message
from mailman.i18n import _
from mailman.interfaces.handler import IHandler
from mailman.interfaces.mailinglist import DigestFrequency
diff --git a/src/mailman/queue/__init__.py b/src/mailman/queue/__init__.py
index ead077e90..1f284ba4d 100644
--- a/src/mailman/queue/__init__.py
+++ b/src/mailman/queue/__init__.py
@@ -49,9 +49,9 @@ from cStringIO import StringIO
from lazr.config import as_boolean, as_timedelta
from zope.interface import implements
-from mailman import Message
from mailman import i18n
from mailman.config import config
+from mailman.email.message import Message
from mailman.interfaces.runner import IRunner
from mailman.interfaces.switchboard import ISwitchboard
from mailman.utilities.filesystem import makedirs
@@ -186,7 +186,7 @@ class Switchboard:
# have to generate the message later when we do size restriction
# checking.
original_size = len(msg)
- msg = email.message_from_string(msg, Message.Message)
+ msg = email.message_from_string(msg, Message)
msg.original_size = original_size
data['original_size'] = original_size
return msg, data
@@ -427,9 +427,8 @@ class Runner:
# will be the list's preferred language. However, we must take
# special care to reset the defaults, otherwise subsequent messages
# may be translated incorrectly.
- sender = msg.get_sender()
- if sender:
- member = mlist.members.get_member(sender)
+ if msg.sender:
+ member = mlist.members.get_member(msg.sender)
language = (member.preferred_language
if member is not None
else mlist.preferred_language)
diff --git a/src/mailman/queue/command.py b/src/mailman/queue/command.py
index d2be7c9fd..8a7793e9e 100644
--- a/src/mailman/queue/command.py
+++ b/src/mailman/queue/command.py
@@ -37,14 +37,14 @@ from email.Header import decode_header, make_header
from email.Iterators import typed_subpart_iterator
from zope.interface import implements
-from mailman import Message
from mailman.config import config
+from mailman.email.message import Message, UserNotification
from mailman.i18n import _
from mailman.interfaces.command import ContinueProcessing, IEmailResults
from mailman.queue import Runner
-NL = '\n'
+NL = '\n'
log = logging.getLogger('mailman.vette')
@@ -195,10 +195,9 @@ class CommandRunner(Runner):
# Send a reply, but do not attach the original message. This is a
# compromise because the original message is often helpful in tracking
# down problems, but it's also a vector for backscatter spam.
- reply = Message.UserNotification(
- msg.get_sender(), mlist.bounces_address,
- _('The results of your email commands'),
- lang=msgdata['lang'])
+ reply = UserNotification(msg.sender, mlist.bounces_address,
+ _('The results of your email commands'),
+ lang=msgdata['lang'])
# Find a charset for the response body. Try ascii first, then
# latin-1 and finally falling back to utf-8.
reply_body = unicode(results)
diff --git a/src/mailman/queue/docs/outgoing.txt b/src/mailman/queue/docs/outgoing.txt
index 6722dee84..1c9d89041 100644
--- a/src/mailman/queue/docs/outgoing.txt
+++ b/src/mailman/queue/docs/outgoing.txt
@@ -11,7 +11,6 @@ recipient set will be batched, whether messages will be personalized and
VERP'd, etc. The outgoing runner doesn't itself support retrying but it can
move messages to the 'retry queue' for handling delivery failures.
- >>> from mailman.app.lifecycle import create_list
>>> mlist = create_list(u'test@example.com')
>>> from mailman.app.membership import add_member
diff --git a/src/mailman/queue/lmtp.py b/src/mailman/queue/lmtp.py
index 3ac8796ca..8befa72b4 100644
--- a/src/mailman/queue/lmtp.py
+++ b/src/mailman/queue/lmtp.py
@@ -38,9 +38,9 @@ import asyncore
from email.utils import parseaddr
-from mailman.Message import Message
from mailman.config import config
from mailman.database.transaction import txn
+from mailman.email.message import Message
from mailman.queue import Runner
elog = logging.getLogger('mailman.error')
diff --git a/src/mailman/queue/maildir.py b/src/mailman/queue/maildir.py
index e8d454d39..eeb503f65 100644
--- a/src/mailman/queue/maildir.py
+++ b/src/mailman/queue/maildir.py
@@ -56,8 +56,8 @@ import logging
from email.Parser import Parser
from email.Utils import parseaddr
-from mailman.Message import Message
from mailman.config import config
+from mailman.message import Message
from mailman.queue import Runner
log = logging.getLogger('mailman.error')
diff --git a/src/mailman/rules/moderation.py b/src/mailman/rules/moderation.py
index 708983f6b..c4d0a1bfb 100644
--- a/src/mailman/rules/moderation.py
+++ b/src/mailman/rules/moderation.py
@@ -43,7 +43,7 @@ class Moderation:
def check(self, mlist, msg, msgdata):
"""See `IRule`."""
- for sender in msg.get_senders():
+ for sender in msg.senders:
member = mlist.members.get_member(sender)
if member is not None and member.is_moderated:
return True
@@ -61,7 +61,7 @@ class NonMember:
def check(self, mlist, msg, msgdata):
"""See `IRule`."""
- for sender in msg.get_senders():
+ for sender in msg.senders:
if mlist.members.get_member(sender) is not None:
# The sender is a member of the mailing list.
return False
diff --git a/src/mailman/tests/test_documentation.py b/src/mailman/tests/test_documentation.py
index 8408265a6..e0ddf025c 100644
--- a/src/mailman/tests/test_documentation.py
+++ b/src/mailman/tests/test_documentation.py
@@ -38,9 +38,9 @@ from email import message_from_string
import mailman
-from mailman.Message import Message
from mailman.app.lifecycle import create_list
from mailman.config import config
+from mailman.email.message import Message
from mailman.testing.layers import SMTPLayer