diff options
Diffstat (limited to '')
-rw-r--r-- | Mailman/Handlers/Decorate.py | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/Mailman/Handlers/Decorate.py b/Mailman/Handlers/Decorate.py new file mode 100644 index 00000000..5605e321 --- /dev/null +++ b/Mailman/Handlers/Decorate.py @@ -0,0 +1,183 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Decorate a message by sticking the header and footer around it. +""" + +from types import ListType +from email.MIMEText import MIMEText + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman.Message import Message +from Mailman.i18n import _ +from Mailman.SafeDict import SafeDict +from Mailman.Logging.Syslog import syslog + + + +def process(mlist, msg, msgdata): + # Digests and Mailman-craft messages should not get additional headers + if msgdata.get('isdigest') or msgdata.get('nodecorate'): + return + d = {} + if msgdata.get('personalize'): + # Calculate the extra personalization dictionary. Note that the + # length of the recips list better be exactly 1. + recips = msgdata.get('recips') + assert type(recips) == ListType and len(recips) == 1 + member = recips[0].lower() + d['user_address'] = member + try: + d['user_delivered_to'] = mlist.getMemberCPAddress(member) + # BAW: Hmm, should we allow this? + d['user_password'] = mlist.getMemberPassword(member) + d['user_language'] = mlist.getMemberLanguage(member) + d['user_name'] = mlist.getMemberName(member) or _('not available') + d['user_optionsurl'] = mlist.GetOptionsURL(member) + except Errors.NotAMemberError: + pass + # These strings are descriptive for the log file and shouldn't be i18n'd + header = decorate(mlist, mlist.msg_header, 'non-digest header', d) + footer = decorate(mlist, mlist.msg_footer, 'non-digest footer', d) + # Escape hatch if both the footer and header are empty + if not header and not footer: + return + # Be MIME smart here. We only attach the header and footer by + # concatenation when the message is a non-multipart of type text/plain. + # Otherwise, if it is not a multipart, we make it a multipart, and then we + # add the header and footer as text/plain parts. + # + # BJG: In addition, only add the footer if the message's character set + # matches the charset of the list's preferred language. This is a + # suboptimal solution, and should be solved by allowing a list to have + # multiple headers/footers, for each language the list supports. + # + # Also, if the list's preferred charset is us-ascii, we can always + # safely add the header/footer to a plain text message since all + # charsets Mailman supports are strict supersets of us-ascii -- + # no, UTF-16 emails are not supported yet. + mcset = msg.get_param('charset', 'us-ascii').lower() + lcset = Utils.GetCharSet(mlist.preferred_language) + msgtype = msg.get_type('text/plain') + # BAW: If the charsets don't match, should we add the header and footer by + # MIME multipart chroming the message? + wrap = 1 + if not msg.is_multipart() and msgtype == 'text/plain' and \ + msg.get('content-transfer-encoding', '').lower() <> 'base64' and \ + (lcset == 'us-ascii' or mcset == lcset): + oldpayload = msg.get_payload() + frontsep = endsep = '' + if header and not header.endswith('\n'): + frontsep = '\n' + if footer and not oldpayload.endswith('\n'): + endsep = '\n' + payload = header + frontsep + oldpayload + endsep + footer + msg.set_payload(payload) + wrap = 0 + elif msg.get_type() == 'multipart/mixed': + # The next easiest thing to do is just prepend the header and append + # the footer as additional subparts + mimehdr = MIMEText(header, 'plain', lcset) + mimeftr = MIMEText(footer, 'plain', lcset) + payload = msg.get_payload() + if not isinstance(payload, ListType): + payload = [payload] + if footer: + payload.append(mimeftr) + if header: + payload.insert(0, mimehdr) + msg.set_payload(payload) + wrap = 0 + # If we couldn't add the header or footer in a less intrusive way, we can + # at least do it by MIME encapsulation. We want to keep as much of the + # outer chrome as possible. + if not wrap: + return + # Because of the way Message objects are passed around to process(), we + # need to play tricks with the outer message -- i.e. the outer one must + # remain the same instance. So we're going to create a clone of the outer + # message, with all the header chrome intact, then copy the payload to it. + # This will give us a clone of the original message, and it will form the + # basis of the interior, wrapped Message. + inner = Message() + # Which headers to copy? Let's just do the Content-* headers + for h, v in msg.items(): + if h.lower().startswith('content-'): + inner[h] = v + inner.set_payload(msg.get_payload()) + # For completeness + inner.set_unixfrom(msg.get_unixfrom()) + inner.preamble = msg.preamble + inner.epilogue = msg.epilogue + # Don't copy get_charset, as this might be None, even if + # get_content_charset isn't. However, do make sure there is a default + # content-type, even if the original message was not MIME. + inner.set_default_type(msg.get_default_type()) + # BAW: HACK ALERT. + if hasattr(msg, '__version__'): + inner.__version__ = msg.__version__ + # Now, play games with the outer message to make it contain three + # subparts: the header (if any), the wrapped message, and the footer (if + # any). + payload = [inner] + if header: + mimehdr = MIMEText(header, 'plain', lcset) + payload.insert(0, mimehdr) + if footer: + mimeftr = MIMEText(footer, 'plain', lcset) + payload.append(mimeftr) + msg.set_payload(payload) + del msg['content-type'] + del msg['content-transfer-encoding'] + del msg['content-disposition'] + msg['Content-Type'] = 'multipart/mixed' + + + +def decorate(mlist, template, what, extradict={}): + # `what' is just a descriptive phrase used in the log message + # + # BAW: We've found too many situations where Python can be fooled into + # interpolating too much revealing data into a format string. For + # example, a footer of "% silly %(real_name)s" would give a header + # containing all list attributes. While we've previously removed such + # really bad ones like `password' and `passwords', it's much better to + # provide a whitelist of known good attributes, then to try to remove a + # blacklist of known bad ones. + d = SafeDict({'real_name' : mlist.real_name, + 'list_name' : mlist.internal_name(), + # For backwards compatibility + '_internal_name': mlist.internal_name(), + 'host_name' : mlist.host_name, + 'web_page_url' : mlist.web_page_url, + 'description' : mlist.description, + 'info' : mlist.info, + 'cgiext' : mm_cfg.CGIEXT, + }) + d.update(extradict) + # Using $-strings? + if getattr(mlist, 'use_dollar_strings', 0): + template = Utils.to_percent(template) + # Interpolate into the template + try: + text = (template % d).replace('\r\n', '\n') + except (ValueError, TypeError), e: + syslog('error', 'Exception while calculating %s:\n%s', what, e) + what = what.upper() + text = template + return text |