diff options
| -rw-r--r-- | Mailman/MailList.py | 305 |
1 files changed, 144 insertions, 161 deletions
diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 23852fc2a..caddb9228 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -1,4 +1,4 @@ -# Copyright (C) 1998,1999,2000 by the Free Software Foundation, Inc. +# Copyright (C) 1998,1999,2000,2001 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 @@ -23,7 +23,6 @@ Mixes in many task-specific classes. import sys import os import marshal -import string import errno import re import shutil @@ -32,6 +31,8 @@ from types import StringType, IntType, DictType, ListType import urllib from urlparse import urlparse +from mimelib.address import getaddresses + from Mailman import mm_cfg from Mailman import Utils from Mailman import Errors @@ -53,7 +54,8 @@ from Mailman.Logging.Syslog import syslog # other useful classes from Mailman.pythonlib.StringIO import StringIO from Mailman import Message -from Mailman.Handlers import HandlerAPI + +EMPTYSTRING = '' @@ -120,16 +122,16 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, in the members dict is the case preserved address, otherwise, the value is 0. """ - if Utils.LCDomain(addr) == string.lower(addr): + if Utils.LCDomain(addr) == addr.lower(): if digest: self.digest_members[addr] = 0 else: self.members[addr] = 0 else: if digest: - self.digest_members[string.lower(addr)] = addr + self.digest_members[addr.lower()] = addr else: - self.members[string.lower(addr)] = addr + self.members[addr.lower()] = addr def GetAdminEmail(self): return '%s-admin@%s' % (self._internal_name, self.host_name) @@ -150,13 +152,13 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, if not self.umbrella_list: return member else: - acct, host = tuple(string.split(member, '@')) + acct, host = tuple(member.split('@')) return "%s%s@%s" % (acct, self.umbrella_member_suffix, host) def GetUserSubscribedAddress(self, member): """Return the member's case preserved address. """ - member = string.lower(member) + member = member.lower() cpuser = self.members.get(member) if type(cpuser) == IntType: return member @@ -173,7 +175,7 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, """Return the member's address lower cased.""" cpuser = self.GetUserSubscribedAddress(member) if cpuser is not None: - return string.lower(cpuser) + return cpuser.lower() return None def GetRequestEmail(self): @@ -187,7 +189,7 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, '/' + self.internal_name() def GetOptionsURL(self, addr, obscure=0, absolute=0): - addr = string.lower(addr) + addr = addr.lower() url = self.GetScriptURL('options', absolute) if obscure: addr = Utils.ObscureEmail(addr) @@ -254,7 +256,7 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, # sadly, matches may or may not be case preserved if not matches or not len(matches): return None - return string.lower(matches[0]) + return matches[0].lower() def InitTempVars(self, name): """Set transient variables of this and inherited classes.""" @@ -288,7 +290,6 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, self.language = {} # This stuff is configurable - self.filter_prog = mm_cfg.DEFAULT_FILTER_PROG self.dont_respond_to_post_requests = 0 self.advertised = mm_cfg.DEFAULT_LIST_ADVERTISED self.max_num_recipients = mm_cfg.DEFAULT_MAX_NUM_RECIPIENTS @@ -314,8 +315,8 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, self.bounce_matching_headers = \ mm_cfg.DEFAULT_BOUNCE_MATCHING_HEADERS self.anonymous_list = mm_cfg.DEFAULT_ANONYMOUS_LIST - self.real_name = '%s%s' % (string.upper(self._internal_name[0]), - self._internal_name[1:]) + internalname = self.internal_name() + self.real_name = internalname[0].upper() + internalname[1:] self.description = '' self.info = '' self.welcome_msg = '' @@ -357,19 +358,20 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, WIDTH = mm_cfg.TEXTFIELDWIDTH # XXX: Should this text be migrated into the templates dir? - config_info['general'] = [_( - "Fundamental list characteristics, including descriptive" - " info and basic behaviors."), + config_info['general'] = [ + _("Fundamental list characteristics, including descriptive" + " info and basic behaviors."), + ('real_name', mm_cfg.String, WIDTH, 0, _('The public name of this list (make case-changes only).'), - _("The capitalization of this name can be changed to make it" - " presentable in polite company as a proper noun, or to make an" - " acronym part all upper case, etc. However, the name" - " will be advertised as the email address (e.g., in subscribe" - " confirmation notices), so it should <em>not</em> be otherwise" - " altered. (Email addresses are not case sensitive, but" - " they are sensitive to almost everything else:-)")), + _("The capitalization of this name can be changed to make it" + " presentable in polite company as a proper noun, or to make an" + " acronym part all upper case, etc. However, the name" + " will be advertised as the email address (e.g., in subscribe" + " confirmation notices), so it should <em>not</em> be otherwise" + " altered. (Email addresses are not case sensitive, but" + " they are sensitive to almost everything else:-)")), ('owner', mm_cfg.EmailList, (3, WIDTH), 0, _("The list admin's email address - having multiple" @@ -384,47 +386,47 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, ('description', mm_cfg.String, WIDTH, 0, _('A terse phrase identifying this list.'), - _("This description is used when the mailing list is listed with" - " other mailing lists, or in headers, and so forth. It should" - " be as succinct as you can get it, while still identifying" - " what the list is.")), + _("This description is used when the mailing list is listed with" + " other mailing lists, or in headers, and so forth. It should" + " be as succinct as you can get it, while still identifying" + " what the list is.")), - ('info', mm_cfg.Text, (7, WIDTH), 0, _( - ' An introductory description - a few paragraphs - about the' - ' list. It will be included, as html, at the top of the' - ' listinfo page. Carriage returns will end a paragraph - see' - ' the details for more info.'), + ('info', mm_cfg.Text, (7, WIDTH), 0, + _(' An introductory description - a few paragraphs - about the' + ' list. It will be included, as html, at the top of the' + ' listinfo page. Carriage returns will end a paragraph - see' + ' the details for more info.'), - _("The text will be treated as html <em>except</em> that newlines" - " newlines will be translated to <br> - so you can use" - " links, preformatted text, etc, but don't put in carriage" - " returns except where you mean to separate paragraphs. And" - " review your changes - bad html (like some unterminated HTML" - " constructs) can prevent display of the entire listinfo page.")), + _("The text will be treated as html <em>except</em> that newlines" + " newlines will be translated to <br> - so you can use" + " links, preformatted text, etc, but don't put in carriage" + " returns except where you mean to separate paragraphs. And" + " review your changes - bad html (like some unterminated HTML" + " constructs) can prevent display of the entire listinfo page.")), ('subject_prefix', mm_cfg.String, WIDTH, 0, _('Prefix for subject line of list postings.'), - _("This text will be prepended to subject lines of messages" - " posted to the list, to distinguish mailing list messages in" - " in mailbox summaries. Brevity is premium here, it's ok" - " to shorten long mailing list names to something more concise," - " as long as it still identifies the mailing list.")), + _("This text will be prepended to subject lines of messages" + " posted to the list, to distinguish mailing list messages in" + " in mailbox summaries. Brevity is premium here, it's ok" + " to shorten long mailing list names to something more concise," + " as long as it still identifies the mailing list.")), ('welcome_msg', mm_cfg.Text, (4, WIDTH), 0, _('List-specific text prepended to new-subscriber welcome message'), - _("This value, if any, will be added to the front of the" - " new-subscriber welcome message. The rest of the" - " welcome message already describes the important addresses" - " and URLs for the mailing list, so you don't need to include" - " any of that kind of stuff here. This should just contain" - " mission-specific kinds of things, like etiquette policies" - " or team orientation, or that kind of thing.")), + _("This value, if any, will be added to the front of the" + " new-subscriber welcome message. The rest of the" + " welcome message already describes the important addresses" + " and URLs for the mailing list, so you don't need to include" + " any of that kind of stuff here. This should just contain" + " mission-specific kinds of things, like etiquette policies" + " or team orientation, or that kind of thing.")), ('goodbye_msg', mm_cfg.Text, (4, WIDTH), 0, _('Text sent to people leaving the list. If empty, no special' - ' text will be added to the unsubscribe message.')), + ' text will be added to the unsubscribe message.')), ('reply_goes_to_list', mm_cfg.Radio, (_('Poster'), _('This list'), _('Explicit address')), 0, @@ -458,7 +460,7 @@ mailing lists, select <tt>Explicit address</tt> and set the <tt>Reply-To:</tt> address below to point to the parallel list.""")), ('reply_to_address', mm_cfg.Email, WIDTH, 0,_( - 'Explicit <tt>Reply-To:</tt> header.'), + 'Explicit <tt>Reply-To:</tt> header.'), # Details for reply_to_address _("""This is the address set in the <tt>Reply-To:</tt> header @@ -495,33 +497,33 @@ it will not be changed.""")), " the administrative requests queue, notifying the " " administrator of the new request, in the process. ")), - ('umbrella_list', mm_cfg.Radio, (_('No'), _('Yes')), 0,_( - 'Send password reminders to, eg, "-owner" address instead of' - ' directly to user.'), + ('umbrella_list', mm_cfg.Radio, (_('No'), _('Yes')), 0, + _('Send password reminders to, eg, "-owner" address instead of' + ' directly to user.'), - _("Set this to yes when this list is intended to cascade only to" - " other mailing lists. When set, meta notices like confirmations" - " and password reminders will be directed to an address derived" - " from the member\'s address - it will have the value of" - ' \"umbrella_member_suffix\" appended to the' - " member\'s account name.")), + _("Set this to yes when this list is intended to cascade only to" + " other mailing lists. When set, meta notices like confirmations" + " and password reminders will be directed to an address derived" + " from the member\'s address - it will have the value of" + ' \"umbrella_member_suffix\" appended to the' + " member\'s account name.")), - ('umbrella_member_suffix', mm_cfg.String, WIDTH, 0,_( - 'Suffix for use when this list is an umbrella for other lists,' - ' according to setting of previous "umbrella_list" setting.'), + ('umbrella_member_suffix', mm_cfg.String, WIDTH, 0, + _('Suffix for use when this list is an umbrella for other lists,' + ' according to setting of previous "umbrella_list" setting.'), _('When \"umbrella_list\" is set to indicate that this list has' - " other mailing lists as members, then administrative notices" - " like confirmations and password reminders need to not be sent" - " to the member list addresses, but rather to the owner of those" - " member lists. In that case, the value of this setting is" - " appended to the member\'s account name for such notices." - " \'-owner\' is the typical choice. This setting has no" - ' effect when \"umbrella_list\" is \"No\".')), + " other mailing lists as members, then administrative notices" + " like confirmations and password reminders need to not be sent" + " to the member list addresses, but rather to the owner of those" + " member lists. In that case, the value of this setting is" + " appended to the member\'s account name for such notices." + " \'-owner\' is the typical choice. This setting has no" + ' effect when \"umbrella_list\" is \"No\".')), ('send_reminders', mm_cfg.Radio, (_('No'), _('Yes')), 0, _('Send monthly password reminders or no? Overrides the previous ' - 'option.')), + 'option.')), ('send_welcome_msg', mm_cfg.Radio, (_('No'), _('Yes')), 0, _('Send welcome message when people subscribe?'), @@ -530,16 +532,15 @@ it will not be changed.""")), "is most useful for transparently migrating lists from " "some other mailing list manager to Mailman.")), - ('admin_immed_notify', mm_cfg.Radio, (_('No'), _('Yes')), 0, _('Should administrator get immediate notice of new requests, ' 'as well as daily notices about collected ones?'), - _("List admins are sent daily reminders of pending admin approval" - " requests, like subscriptions to a moderated list or postings" - " that are being held for one reason or another. Setting this" - " option causes notices to be sent immediately on the arrival" - " of new requests, as well.")), + _("List admins are sent daily reminders of pending admin approval" + " requests, like subscriptions to a moderated list or postings" + " that are being held for one reason or another. Setting this" + " option causes notices to be sent immediately on the arrival" + " of new requests, as well.")), ('admin_notify_mchanges', mm_cfg.Radio, (_('No'), _('Yes')), 0, _('Should administrator get notices of subscribes/unsubscribes?')), @@ -548,10 +549,10 @@ it will not be changed.""")), (_('Yes'), _('No')), 0, _('Send mail to poster when their posting is held for approval?'), - _("Approval notices are sent when mail triggers certain of the" - " limits <em>except</em> routine list moderation and spam" - " filters, for which notices are <em>not</em> sent. This" - " option overrides ever sending the notice.")), + _("Approval notices are sent when mail triggers certain of the" + " limits <em>except</em> routine list moderation and spam" + " filters, for which notices are <em>not</em> sent. This" + " option overrides ever sending the notice.")), ('max_message_size', mm_cfg.Number, 7, 0, _('Maximum length in Kb of a message body. Use 0 for no limit.')), @@ -559,11 +560,11 @@ it will not be changed.""")), ('host_name', mm_cfg.Host, WIDTH, 0, _('Host name this list prefers.'), - _("The host_name is the preferred name for email to mailman-related" - " addresses on this host, and generally should be the mail" - " host's exchanger address, if any. This setting can be useful" - " for selecting among alternative names of a host that has" - " multiple addresses.")), + _("The host_name is the preferred name for email to mailman-related" + " addresses on this host, and generally should be the mail" + " host's exchanger address, if any. This setting can be useful" + " for selecting among alternative names of a host that has" + " multiple addresses.")), ('web_page_url', mm_cfg.String, WIDTH, 0, _('''Base URL for Mailman web interface. The URL must end in a @@ -985,7 +986,7 @@ it will not be changed.""")), # name = Utils.ParseAddrs(name) # Remove spaces... it's a common thing for people to add... - name = string.join(string.split(name), '') + name = EMPTYSTRING.join(name.split()) # lower case only the domain part name = Utils.LCDomain(name) @@ -993,7 +994,7 @@ it will not be changed.""")), Utils.ValidateEmail(name) if self.IsMember(name): raise Errors.MMAlreadyAMember - if name == string.lower(self.GetListEmail()): + if name == self.GetListEmail().lower(): # Trying to subscribe the list to itself! raise Errors.MMBadEmailError @@ -1035,9 +1036,9 @@ it will not be changed.""")), (self.real_name, cookie), text) msg['Reply-To'] = self.GetRequestEmail() - HandlerAPI.DeliverToUser(self, msg) + msg.send(self) if recipient != name: - who = "%s (%s)" % (name, string.split(recipient, '@')[0]) + who = "%s (%s)" % (name, recipient.split('@')[0]) else: who = name syslog('subscribe', '%s: pending %s %s' % (self.internal_name(), who, by)) @@ -1068,7 +1069,7 @@ it will not be changed.""")), self.Save() def ApprovedAddMember(self, name, password, digest, - lang=None, ack=None, admin_notif=None): + ack=None, admin_notif=None, lang=None): res = self.ApprovedAddMembers([name], [password], digest, lang, ack, admin_notif) # There should be exactly one (key, value) pair in the returned dict, @@ -1082,8 +1083,8 @@ it will not be changed.""")), e, v = res raise e, v - def ApprovedAddMembers(self, names, passwords, digest, lang=None, - ack=None, admin_notif=None): + def ApprovedAddMembers(self, names, passwords, digest, + ack=None, admin_notif=None, lang=None): """Subscribe members in list `names'. Passwords can be supplied in the passwords list. If an empty @@ -1148,7 +1149,7 @@ it will not be changed.""")), password = passwords[i] if not password: password = Utils.MakeRandomPassword() - self.passwords[string.lower(name)] = password + self.passwords[name.lower()] = password # An address has been added successfully, make sure the # list config is saved later on dirty = 1 @@ -1167,12 +1168,12 @@ it will not be changed.""")), if ack: self.SendSubscribeAck( name, - self.passwords[string.lower(name)], + self.passwords[name.lower()], digest) if admin_notif: adminaddr = self.GetAdminEmail() - subject = (_('%s subscription notification') % - self.real_name) + realname = self.real_name + subject = _('%(realname)s subscription notification') text = Utils.maketext( "adminsubscribeack.txt", {"listname" : self.real_name, @@ -1180,7 +1181,7 @@ it will not be changed.""")), }, lang) msg = Message.UserNotification( self.owner, mm_cfg.MAILMAN_OWNER, subject, text) - HandlerAPI.DeliverToUser(self, msg) + msg.send(self) return result def DeleteMember(self, name, whence=None, admin_notif=None, userack=1): @@ -1233,7 +1234,7 @@ it will not be changed.""")), msg = Message.UserNotification(self.owner, mm_cfg.MAILMAN_OWNER, subject, text) - HandlerAPI.DeliverToUser(self, msg) + msg.send(self) if whence: whence = "; %s" % whence else: @@ -1255,15 +1256,15 @@ it will not be changed.""")), # specifically To: Cc: and Resent-to: to = [] for header in ('to', 'cc', 'resent-to', 'resent-cc'): - to.extend(msg.getaddrlist(header)) + to.extend(getaddresses(msg.getall(header))) for fullname, addr in to: # It's possible that if the header doesn't have a valid # (i.e. RFC822) value, we'll get None for the address. So skip # it. if addr is None: continue - addr = string.lower(addr) - localpart = string.split(addr, '@')[0] + addr = addr.lower() + localpart = addr.split('@')[0] if (# TBD: backwards compatibility: deprecated localpart == self.internal_name() or # exact match against the complete list address @@ -1292,8 +1293,8 @@ it will not be changed.""")), # 2. If that match fails, or the pattern does have an `@' in it, we # try matching against the entire recip address. for addr, localpart in recips: - for alias in string.split(self.acceptable_aliases, '\n'): - stripped = string.strip(alias) + for alias in self.acceptable_aliases.split('\n'): + stripped = alias.strip() if not stripped: # ignore blank or empty lines continue @@ -1309,63 +1310,45 @@ it will not be changed.""")), # - Leading whitespace in the matchexp is trimmed - you can defeat # that by, eg, containing it in gratuitous square brackets. all = [] - for line in string.split(self.bounce_matching_headers, '\n'): - stripped = string.strip(line) - if not stripped or (stripped[0] == "#"): - # Skip blank lines and lines *starting* with a '#'. + for line in self.bounce_matching_headers.split('\n'): + line = line.strip() + # Skip blank lines and lines *starting* with a '#'. + if not line or line[0] == "#": continue - else: - try: - h, e = re.split(":[ \t]*", stripped, 1) - try: - re.compile(e) - all.append((h, e, stripped)) - except re.error, cause: - # The regexp in this line is malformed -- log it - # and ignore it - syslog('config', - '%s - bad regexp %s [%s] ' - 'in bounce_matching_header line %s' - % (self.real_name, `e`, `cause`, `stripped`)) - except ValueError: - # Whoops - some bad data got by: - syslog('config', '%s - bad bounce_matching_header line %s' - % (self.real_name, `stripped`)) - return all - - def HasMatchingHeader(self, msg): - """True if named header field (case-insensitive) matches regexp. + i = line.find(':') + if i < 0: + # This didn't look like a header line. BAW: should do a + # better job of informing the list admin. + syslog('config', 'bad bounce_matching_header line: %s\n%s' + % (self.real_name, line)) + else: + header = line[:i] + value = line[i+1:].lstrip() + try: + cre = re.compile(value, re.IGNORECASE) + except re.error, e: + # The regexp was malformed. BAW: should do a better + # job of informing the list admin. + syslog('config', + 'bad regexp in bounce_matching_header line: %s' + '\n%s (cause: %s)' % + (self.real_name, value, e)) + else: + all.append((header, cre, line)) + return all - Case insensitive. + def hasMatchingHeader(self, msg): + """Return true if named header field matches a regexp in the + bounce_matching_header list variable. Returns constraint line which matches or empty string for no - matches.""" - - pairs = self.parse_matching_header_opt() - - for field, matchexp, line in pairs: - fragments = msg.getallmatchingheaders(field) - subjs = [] - l = len(field) - for f in fragments: - # Consolidate header lines, stripping header name & whitespace. - if (len(f) > l - and f[l] == ":" - and string.lower(field) == string.lower(f[0:l])): - # Non-continuation line - trim header name: - subjs.append(f[l+2:]) - elif not subjs: - # Whoops - non-continuation that matches? - subjs.append(f) - else: - # Continuation line. - subjs[-1] = subjs[-1] + f - for s in subjs: - # This is safe because parse_matching_header_opt only - # returns valid regexps - if re.search(matchexp, s, re.I): - return line - return 0 + matches. + """ + for header, cre, line in self.parse_matching_header_opt(): + for value in msg.getall(header): + if cre.search(value): + return line + return 0 def Locked(self): return self.__lock.locked() @@ -1399,7 +1382,7 @@ it will not be changed.""")), return self._full_path def SetPreferredLanguage(self, name, lang): - lcname = string.lower(name) + lcname = name.lower() if lang <> self.preferred_language: self.language[lcname] = lang else: @@ -1407,7 +1390,7 @@ it will not be changed.""")), del self.language[lcname] def GetPreferredLanguage(self, name): - lcname = string.lower(name) + lcname = name.lower() if self.language.has_key(lcname): return self.language[lcname] else: |
