diff options
| author | Barry Warsaw | 2009-02-09 22:19:18 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-02-09 22:19:18 -0500 |
| commit | 98c52ea14883f0261fd7a2f2fe8db42d96331ddb (patch) | |
| tree | a0c5817f4c226ed14b3fe510314e825980b6cb4e /src | |
| parent | 84a81e4a90349f7116863d2f45cda1ee31b5b3b5 (diff) | |
| download | mailman-98c52ea14883f0261fd7a2f2fe8db42d96331ddb.tar.gz mailman-98c52ea14883f0261fd7a2f2fe8db42d96331ddb.tar.zst mailman-98c52ea14883f0261fd7a2f2fe8db42d96331ddb.zip | |
Diffstat (limited to 'src')
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 |
