def loginpage(mlist, scriptname, msg='', frontpage=None): url = mlist.GetScriptURL(scriptname) if frontpage: actionurl = url else: actionurl = Utils.GetRequestURI(url) if msg: msg = FontAttr(msg, color='#ff0000', size='+1').Format() # give an HTTP 401 for authentication failure print 'Status: 401 Unauthorized' if scriptname == 'admindb': who = _('Moderator') else: who = _('Administrator') # Language stuff charset = Utils.GetCharSet(mlist.preferred_language) print 'Content-type: text/html; charset=' + charset + '\n\n' print Utils.maketext('admlogin.html', { 'listname': mlist.real_name, 'path': actionurl, 'message': msg, 'who': who, }, mlist=mlist) print mlist.GetMailmanFooter()
def __sendAdminBounceNotice(self, member, msg, did=_('disabled')): # BAW: This is a bit kludgey, but we're not providing as much # information in the new admin bounce notices as we used to (some of # it was of dubious value). However, we'll provide empty, strange, or # meaningless strings for the unused %()s fields so that the language # translators don't have to provide new templates. siteowner = Utils.get_site_email(self.host_name) text = Utils.maketext('bounce.txt', { 'listname': self.real_name, 'addr': member, 'negative': '', 'did': did, 'but': '', 'reenable': '', 'owneraddr': siteowner, }, mlist=self) subject = _('Bounce action notification') umsg = Message.UserNotification(self.GetOwnerEmail(), siteowner, subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get # a MultipartConversionError. umsg.set_type('multipart/mixed') umsg.attach( MIMEText(text, _charset=Utils.GetCharSet(self.preferred_language))) if isinstance(msg, StringType): umsg.attach(MIMEText(msg)) else: umsg.attach(MIMEMessage(msg)) umsg.send(self)
def process(mlist, msg, msgdata): # Extract the sender's address and find them in the user database sender = msgdata.get('original_sender', msg.get_sender()) try: ack = mlist.getMemberOption(sender, mm_cfg.AcknowledgePosts) if not ack: return except Errors.NotAMemberError: return # Okay, they want acknowledgement of their post. Give them their original # subject. BAW: do we want to use the decoded header? origsubj = msgdata.get('origsubj', msg.get('subject', _('(no subject)'))) # Get the user's preferred language lang = msgdata.get('lang', mlist.getMemberLanguage(sender)) # Now get the acknowledgement template realname = mlist.real_name text = Utils.maketext( 'postack.txt', { 'subject': Utils.oneline(origsubj, Utils.GetCharSet(lang)), 'listname': realname, 'listinfo_url': mlist.GetScriptURL('listinfo', absolute=1), 'optionsurl': mlist.GetOptionsURL(sender, absolute=1), }, lang=lang, mlist=mlist, raw=1) # Craft the outgoing message, with all headers and attributes # necessary for general delivery. Then enqueue it to the outgoing # queue. subject = _('%(realname)s post acknowledgement') usermsg = Message.UserNotification(sender, mlist.GetBouncesEmail(), subject, text, lang) usermsg.send(mlist)
def decode_headers(self): """MIME-decode headers. If the email, subject, or author attributes contain non-ASCII characters using the encoded-word syntax of RFC 2047, decoded versions of those attributes are placed in the self.decoded (a dictionary). If the list's charset differs from the header charset, an attempt is made to decode the headers as Unicode. If that fails, they are left undecoded. """ author = self.decode_charset(self.author) subject = self.decode_charset(self.subject) if author: self.decoded['author'] = author email = self.decode_charset(self.email) if email: self.decoded['email'] = email if subject: if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: otrans = i18n.get_translation() try: i18n.set_language(self._lang) atmark = unicode(_(' at '), Utils.GetCharSet(self._lang)) subject = re.sub(r'([-+,.\w]+)@([-+.\w]+)', '\g<1>' + atmark + '\g<2>', subject) finally: i18n.set_translation(otrans) self.decoded['subject'] = subject self.decoded['stripped'] = self.strip_subject(subject or self.subject)
def Format(self, indent=0, **kws): charset = 'us-ascii' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s' % charset] output.append('Cache-control: no-cache\n') if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, '<HTML>', '<HEAD>']) if mm_cfg.IMAGE_LOGOS: output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) # Hit all the bases output.append('<META http-equiv="Content-Type" ' 'content="text/html; charset=%s">' % charset) if self.title: output.append('%s<TITLE>%s</TITLE>' % (tab, self.title)) # Add CSS to visually hide some labeling text but allow screen # readers to read it. output.append("""\ <style type="text/css"> div.hidden {position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden;} </style> """) if mm_cfg.WEB_HEAD_ADD: output.append(mm_cfg.WEB_HEAD_ADD) output.append('%s</HEAD>' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) if mm_cfg.WEB_ALINK_COLOR: kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) if mm_cfg.WEB_LINK_COLOR: kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in kws.items(): quals.append('%s="%s"' % (k, v)) output.append('%s<BODY %s' % (tab, SPACE.join(quals))) # Language direction direction = Utils.GetDirection(self.language) output.append('dir="%s">' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: output.append('%s</BODY>' % tab) output.append('%s</HTML>' % tab) return NL.join(output)
def GetStandardReplacements(self, lang=None): dmember_len = len(self.getDigestMemberKeys()) member_len = len(self.getRegularMemberKeys()) # If only one language is enabled for this mailing list, omit the # language choice buttons. if len(self.GetAvailableLanguages()) == 1: listlangs = _(Utils.GetLanguageDescr(self.preferred_language)) else: listlangs = self.GetLangSelectBox(lang).Format() if lang: cset = Utils.GetCharSet(lang) or 'us-ascii' else: cset = Utils.GetCharSet(self.preferred_language) or 'us-ascii' d = { '<mm-mailman-footer>' : self.GetMailmanFooter(), '<mm-list-name>' : self.real_name, '<mm-email-user>' : self._internal_name, '<mm-list-description>' : Utils.websafe(self.GetDescription(cset)), '<mm-list-info>' : '<!---->' + BR.join(self.info.split(NL)) + '<!---->', '<mm-form-end>' : self.FormatFormEnd(), '<mm-archive>' : self.FormatArchiveAnchor(), '</mm-archive>' : '</a>', '<mm-list-subscription-msg>' : self.FormatSubscriptionMsg(), '<mm-restricted-list-message>' : \ self.RestrictedListMessage(_('The current archive'), self.archive_private), '<mm-num-reg-users>' : repr(member_len), '<mm-num-digesters>' : repr(dmember_len), '<mm-num-members>' : (repr(member_len + dmember_len)), '<mm-posting-addr>' : '%s' % self.GetListEmail(), '<mm-request-addr>' : '%s' % self.GetRequestEmail(), '<mm-owner>' : self.GetOwnerEmail(), '<mm-reminder>' : self.FormatReminder(self.preferred_language), '<mm-host>' : self.host_name, '<mm-list-langs>' : listlangs, } if mm_cfg.IMAGE_LOGOS: d['<mm-favicon>'] = mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON return d
def decode_charset(self, field): # TK: This function was rewritten for unifying to Unicode. # Convert 'field' into Unicode one line string. try: pairs = decode_header(field) ustr = make_header(pairs).__unicode__() except (LookupError, UnicodeError, ValueError, HeaderParseError): # assume list's language cset = Utils.GetCharSet(self._mlist.preferred_language) if cset == 'us-ascii': cset = 'iso-8859-1' # assume this for English list ustr = unicode(field, cset, 'replace') return u''.join(ustr.splitlines())
def uheader(mlist, s, header_name=None, continuation_ws='\t', maxlinelen=None): # Get the charset to encode the string in. Then search if there is any # non-ascii character is in the string. If there is and the charset is # us-ascii then we use iso-8859-1 instead. If the string is ascii only # we use 'us-ascii' if another charset is specified. charset = Utils.GetCharSet(mlist.preferred_language) if nonascii.search(s): # use list charset but ... if charset == 'us-ascii': charset = 'iso-8859-1' else: # there is no nonascii so ... charset = 'us-ascii' return Header(s, charset, maxlinelen, header_name, continuation_ws)
def uheader(mlist, s, header_name=None, continuation_ws=' ', maxlinelen=None): # Get the charset to encode the string in. Then search if there is any # non-ascii character is in the string. If there is and the charset is # us-ascii then we use iso-8859-1 instead. If the string is ascii only # we use 'us-ascii' if another charset is specified. charset = Utils.GetCharSet(mlist.preferred_language) if charset == 'us-ascii': charset = 'iso-8859-1' try: return Header(s, charset, maxlinelen, header_name, continuation_ws) except UnicodeError: syslog('error', 'list: %s: can\'t decode "%s" as %s', mlist.internal_name(), s, charset) return Header('', charset, maxlinelen, header_name, continuation_ws)
def MailUserPassword(self, user): listfullname = '%s@%s' % (self.real_name, self.host_name) requestaddr = self.GetRequestEmail() # find the lowercased version of the user's address adminaddr = self.GetBouncesEmail() assert self.isMember(user) if not self.getMemberPassword(user): # The user's password somehow got corrupted. Generate a new one # for him, after logging this bogosity. syslog('error', 'User %s had a false password for list %s', user, self.internal_name()) waslocked = self.Locked() if not waslocked: self.Lock() try: self.setMemberPassword(user, Utils.MakeRandomPassword()) self.Save() finally: if not waslocked: self.Unlock() # Now send the user his password cpuser = self.getMemberCPAddress(user) recipient = self.GetMemberAdminEmail(cpuser) subject = _('%(listfullname)s mailing list reminder') # Get user's language and charset lang = self.getMemberLanguage(user) cset = Utils.GetCharSet(lang) password = self.getMemberPassword(user) # TK: Make unprintables to ? # The list owner should allow users to set language options if they # want to use non-us-ascii characters in password and send it back. password = unicode(password, cset, 'replace').encode(cset, 'replace') # get the text from the template text = Utils.maketext( 'userpass.txt', { 'user': cpuser, 'listname': self.real_name, 'fqdn_lname': self.GetListEmail(), 'password': password, 'options_url': self.GetOptionsURL(user, absolute=True), 'requestaddr': requestaddr, 'owneraddr': self.GetOwnerEmail(), }, lang=lang, mlist=self) msg = Message.UserNotification(recipient, adminaddr, subject, text, lang) msg['X-No-Archive'] = 'yes' msg.send(self, verp=mm_cfg.VERP_PERSONALIZED_DELIVERIES)
def BounceMessage(self, msg, msgdata, e=None): # Bounce a message back to the sender, with an error message if # provided in the exception argument. sender = msg.get_sender() subject = msg.get('subject', _('(no subject)')) subject = Utils.oneline(subject, Utils.GetCharSet(self.preferred_language)) if e is None: notice = _('[No bounce details are available]') else: notice = _(e.notice()) # Currently we always craft bounces as MIME messages. bmsg = Message.UserNotification(msg.get_sender(), self.GetOwnerEmail(), subject, lang=self.preferred_language) # BAW: Be sure you set the type before trying to attach, or you'll get # a MultipartConversionError. bmsg.set_type('multipart/mixed') txt = MIMEText(notice, _charset=Utils.GetCharSet(self.preferred_language)) bmsg.attach(txt) bmsg.attach(MIMEMessage(msg)) bmsg.send(self)
def ForwardMessage(self, msg, text=None, subject=None, tomoderators=True): # Wrap the message as an attachment if text is None: text = _('No reason given') if subject is None: text = _('(no subject)') text = MIMEText(Utils.wrap(text), _charset=Utils.GetCharSet(self.preferred_language)) attachment = MIMEMessage(msg) notice = Message.OwnerNotification( self, subject, tomoderators=tomoderators) # Make it look like the message is going to the -owner address notice.set_type('multipart/mixed') notice.attach(text) notice.attach(attachment) notice.send(self)
def process(mlist, msg, msgdata): # This is the negation of we're wrapping because dmarc_moderation_action # is wrap this message or from_is_list applies and is wrap. if not (msgdata.get('from_is_list') == 2 or (mlist.from_is_list == 2 and msgdata.get('from_is_list') == 0)): # Now see if we need to add a From:, Reply-To: or Cc: without wrapping. # See comments in CookHeaders.change_header for why we do this here. a_h = msgdata.get('add_header') if a_h: if a_h.get('From'): del msg['from'] msg['From'] = a_h.get('From') if a_h.get('Reply-To'): del msg['reply-to'] msg['Reply-To'] = a_h.get('Reply-To') if a_h.get('Cc'): del msg['cc'] msg['Cc'] = a_h.get('Cc') return # There are various headers in msg that we don't want, so we basically # make a copy of the msg, then delete almost everything and set/copy # what we want. omsg = copy.deepcopy(msg) for key in list(msg.keys()): if key.lower() not in KEEPERS: del msg[key] msg['MIME-Version'] = '1.0' msg['Message-ID'] = Utils.unique_message_id(mlist) # Add the headers from CookHeaders. for k, v in list(msgdata['add_header'].items()): msg[k] = v # Are we including dmarc_wrapped_message_text? I.e., do we have text and # are we wrapping because of dmarc_moderation_action? if mlist.dmarc_wrapped_message_text and msgdata.get('from_is_list') == 2: part1 = text.MIMEText(Utils.wrap(mlist.dmarc_wrapped_message_text), 'plain', Utils.GetCharSet(mlist.preferred_language)) part1['Content-Disposition'] = 'inline' part2 = message.MIMEMessage(omsg) part2['Content-Disposition'] = 'inline' msg['Content-Type'] = 'multipart/mixed' msg.set_payload([part1, part2]) else: msg['Content-Type'] = 'message/rfc822' msg['Content-Disposition'] = 'inline' msg.set_payload([omsg])
def html_TOC(self): mlist = self.maillist listname = mlist.internal_name() mbox = os.path.join(mlist.archive_dir() + '.mbox', listname + '.mbox') d = { "listname": mlist.real_name, "listinfo": mlist.GetScriptURL('listinfo', absolute=1), "fullarch": '../%s.mbox/%s.mbox' % (listname, listname), "size": sizeof(mbox, mlist.preferred_language), 'meta': '', } # Avoid i18n side-effects otrans = i18n.get_translation() i18n.set_language(mlist.preferred_language) try: if not self.archives: d["noarchive_msg"] = _( '<P>Currently, there are no archives. </P>') d["archive_listing_start"] = "" d["archive_listing_end"] = "" d["archive_listing"] = "" else: d["noarchive_msg"] = "" d["archive_listing_start"] = quick_maketext( 'archliststart.html', lang=mlist.preferred_language, mlist=mlist) d["archive_listing_end"] = quick_maketext('archlistend.html', mlist=mlist) accum = [] for a in self.archives: accum.append(self.html_TOC_entry(a)) d["archive_listing"] = EMPTYSTRING.join(accum) finally: i18n.set_translation(otrans) # The TOC is always in the charset of the list's preferred language d['meta'] += html_charset % Utils.GetCharSet(mlist.preferred_language) # The site can disable public access to the mbox file. if mm_cfg.PUBLIC_MBOX: template = 'archtoc.html' else: template = 'archtocnombox.html' return quick_maketext(template, d, mlist=mlist)
def ParseTags(self, template, replacements, lang=None): if lang is None: charset = 'us-ascii' else: charset = Utils.GetCharSet(lang) text = Utils.maketext(template, raw=1, lang=lang, mlist=self) parts = re.split('(</?[Mm][Mm]-[^>]*>)', text) i = 1 while i < len(parts): tag = parts[i].lower() if replacements.has_key(tag): repl = replacements[tag] if isinstance(repl, type(u'')): repl = repl.encode(charset, 'replace') parts[i] = repl else: parts[i] = '' i = i + 2 return EMPTYSTRING.join(parts)
def as_html(self): d = self.__dict__.copy() # avoid i18n side-effects otrans = i18n.get_translation() i18n.set_language(self._lang) try: d["prev"], d["prev_wsubj"] = self._get_prev() d["next"], d["next_wsubj"] = self._get_next() d["email_html"] = self.quote(self.email) d["title"] = self.quote(self.subject) d["subject_html"] = self.quote(self.subject) d["message_id"] = self.quote(self._message_id) # TK: These two _url variables are used to compose a response # from the archive web page. So, ... d["subject_url"] = url_quote('Re: ' + self.subject) d["in_reply_to_url"] = url_quote(self._message_id) if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: # Point the mailto url back to the list author = re.sub('@', _(' at '), self.author) emailurl = self._mlist.GetListEmail() else: author = self.author emailurl = self.email d["author_html"] = self.quote(author) d["email_url"] = url_quote(emailurl) d["datestr_html"] = self.quote(i18n.ctime(int(self.date))) d["body"] = self._get_body() d['listurl'] = self._mlist.GetScriptURL('listinfo', absolute=1) d['listname'] = self._mlist.real_name d['encoding'] = '' finally: i18n.set_translation(otrans) charset = Utils.GetCharSet(self._lang) d["encoding"] = html_charset % charset self._add_decoded(d) return quick_maketext('article.html', d, lang=self._lang, mlist=self._mlist)
def sendProbe(self, member, msg): listname = self.real_name # Put together the substitution dictionary. d = { 'listname': listname, 'address': member, 'optionsurl': self.GetOptionsURL(member, absolute=True), 'owneraddr': self.GetOwnerEmail(), } text = Utils.maketext('probe.txt', d, lang=self.getMemberLanguage(member), mlist=self) # Calculate the VERP'd sender address for bounce processing of the # probe message. token = self.pend_new(Pending.PROBE_BOUNCE, member, msg) probedict = { 'bounces': self.internal_name() + '-bounces', 'token': token, } probeaddr = '%s@%s' % ( (mm_cfg.VERP_PROBE_FORMAT % probedict), self.host_name) # Calculate the Subject header, in the member's preferred language ulang = self.getMemberLanguage(member) otrans = i18n.get_translation() i18n.set_language(ulang) try: subject = _('%(listname)s mailing list probe message') finally: i18n.set_translation(otrans) outer = Message.UserNotification(member, probeaddr, subject, lang=ulang) outer.set_type('multipart/mixed') text = MIMEText(text, _charset=Utils.GetCharSet(ulang)) outer.attach(text) outer.attach(MIMEMessage(msg)) # Turn off further VERP'ing in the final delivery step. We set # probe_token for the OutgoingRunner to more easily handling local # rejects of probe messages. outer.send(self, envsender=probeaddr, verp=False, probe_token=token)
def __init__(self, recip, sender, subject=None, text=None, lang=None): Message.__init__(self) charset = None if lang is not None: charset = Charset(Utils.GetCharSet(lang)) if text is not None: self.set_payload(text, charset) if subject is None: subject = '(no subject)' self['Subject'] = Header(subject, charset, header_name='Subject', errors='replace') self['From'] = sender if isinstance(recip, list): self['To'] = COMMASPACE.join(recip) self.recips = recip else: self['To'] = recip self.recips = [recip]
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 varhelp = '%s/?VARHELP=privacy/sender/discard_these_nonmembers' % \ mlist.GetScriptURL('admin', absolute=1) nmsg = Message.UserNotification(mlist.GetOwnerEmail(), mlist.GetBouncesEmail(), _('Auto-discard notification'), lang=lang) nmsg.set_type('multipart/mixed') text = MIMEText(Utils.wrap( _('The attached message has been automatically discarded.')), _charset=Utils.GetCharSet(lang)) nmsg.attach(text) nmsg.attach(MIMEMessage(msg)) nmsg.send(mlist) # Discard this sucker raise Errors.DiscardMessage
def quick_maketext(templatefile, dict=None, lang=None, mlist=None): if mlist is None: listname = '' else: listname = mlist._internal_name if lang is None: if mlist is None: lang = mm_cfg.DEFAULT_SERVER_LANGUAGE else: lang = mlist.preferred_language cachekey = (templatefile, lang, listname) filepath = _templatefilepathcache.get(cachekey) if filepath: template = _templatecache.get(filepath) if filepath is None or template is None: # Use the basic maketext, with defaults to get the raw template template, filepath = Utils.findtext(templatefile, lang=lang, raw=True, mlist=mlist) _templatefilepathcache[cachekey] = filepath _templatecache[filepath] = template # Copied from Utils.maketext() text = template if dict is not None: try: sdict = SafeDict(dict) try: text = sdict.interpolate(template) except UnicodeError: # Try again after coercing the template to unicode utemplate = unicode(template, Utils.GetCharSet(lang), 'replace') text = sdict.interpolate(utemplate) except (TypeError, ValueError): # The template is really screwed up pass # Make sure the text is in the given character set, or html-ify any bogus # characters. return Utils.uncanonstr(text, lang)
def Format(self, indent=0, **kws): charset = 'us-ascii' if self.language and Utils.IsLanguage(self.language): charset = Utils.GetCharSet(self.language) output = ['Content-Type: text/html; charset=%s' % charset] output.append('Cache-control: no-cache\n') if not self.suppress_head: kws.setdefault('bgcolor', self.bgcolor) tab = ' ' * indent output.extend([tab, '<HTML>', '<HEAD>']) if mm_cfg.IMAGE_LOGOS: output.append('<LINK REL="SHORTCUT ICON" HREF="%s">' % (mm_cfg.IMAGE_LOGOS + mm_cfg.SHORTCUT_ICON)) # Hit all the bases output.append('<META http-equiv="Content-Type" ' 'content="text/html; charset=%s">' % charset) if self.title: output.append('%s<TITLE>%s</TITLE>' % (tab, self.title)) output.append('%s</HEAD>' % tab) quals = [] # Default link colors if mm_cfg.WEB_VLINK_COLOR: kws.setdefault('vlink', mm_cfg.WEB_VLINK_COLOR) if mm_cfg.WEB_ALINK_COLOR: kws.setdefault('alink', mm_cfg.WEB_ALINK_COLOR) if mm_cfg.WEB_LINK_COLOR: kws.setdefault('link', mm_cfg.WEB_LINK_COLOR) for k, v in kws.items(): quals.append('%s="%s"' % (k, v)) output.append('%s<BODY %s' % (tab, SPACE.join(quals))) # Language direction direction = Utils.GetDirection(self.language) output.append('dir="%s">' % direction) # Always do this... output.append(Container.Format(self, indent)) if not self.suppress_head: output.append('%s</BODY>' % tab) output.append('%s</HTML>' % tab) return NL.join(output)
def as_text(self): d = self.__dict__.copy() # We need to guarantee a valid From_ line, even if there are # bososities in the headers. if not d.get('fromdate', '').strip(): d['fromdate'] = time.ctime(time.time()) if not d.get('email', '').strip(): d['email'] = '*****@*****.**' if not d.get('datestr', '').strip(): d['datestr'] = time.ctime(time.time()) # headers = [ 'From %(email)s %(fromdate)s', 'From: %(email)s (%(author)s)', 'Date: %(datestr)s', 'Subject: %(subject)s' ] if d['_in_reply_to']: headers.append('In-Reply-To: %(_in_reply_to)s') if d['_references']: headers.append('References: %(_references)s') if d['_message_id']: headers.append('Message-ID: %(_message_id)s') body = EMPTYSTRING.join(self.body) cset = Utils.GetCharSet(self._lang) # Coerce the body to Unicode and replace any invalid characters. if not isinstance(body, types.UnicodeType): body = unicode(body, cset, 'replace') if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: otrans = i18n.get_translation() try: i18n.set_language(self._lang) atmark = unicode(_(' at '), cset) body = re.sub(r'([-+,.\w]+)@([-+.\w]+)', '\g<1>' + atmark + '\g<2>', body) finally: i18n.set_translation(otrans) # Return body to character set of article. body = body.encode(cset, 'replace') return NL.join(headers) % d + '\n\n' + body + '\n'
def __init__(self, maillist): # can't init the database while other processes are writing to it! # XXX TODO- implement native locking # with mailman's LockFile module for HyperDatabase.HyperDatabase # dir = maillist.archive_dir() db = HyperDatabase.HyperDatabase(dir, maillist) self.__super_init(dir, reload=1, database=db) self.maillist = maillist self._lock_file = None self.lang = maillist.preferred_language self.charset = Utils.GetCharSet(maillist.preferred_language) if hasattr(self.maillist, 'archive_volume_frequency'): if self.maillist.archive_volume_frequency == 0: self.ARCHIVE_PERIOD = 'year' elif self.maillist.archive_volume_frequency == 2: self.ARCHIVE_PERIOD = 'quarter' elif self.maillist.archive_volume_frequency == 3: self.ARCHIVE_PERIOD = 'week' elif self.maillist.archive_volume_frequency == 4: self.ARCHIVE_PERIOD = 'day' else: self.ARCHIVE_PERIOD = 'month' yre = r'(?P<year>[0-9]{4,4})' mre = r'(?P<month>[01][0-9])' dre = r'(?P<day>[0123][0-9])' self._volre = { 'year': '^' + yre + '$', 'quarter': '^' + yre + r'q(?P<quarter>[1234])$', 'month': '^' + yre + r'-(?P<month>[a-zA-Z]+)$', 'week': r'^Week-of-Mon-' + yre + mre + dre, 'day': '^' + yre + mre + dre + '$' }
def send_response(self): # Helper def indent(lines): return [' ' + line for line in lines] # Quick exit for some commands which don't need a response if not self.respond: return resp = [ Utils.wrap( _("""\ The results of your email command are provided below. Attached is your original message. """)) ] if self.results: resp.append(_('- Results:')) resp.extend(indent(self.results)) # Ignore empty lines unprocessed = [ line for line in self.commands[self.lineno:] if line and line.strip() ] if unprocessed and mm_cfg.RESPONSE_INCLUDE_LEVEL >= 2: resp.append(_('\n- Unprocessed:')) resp.extend(indent(unprocessed)) if not unprocessed and not self.results: # The user sent an empty message; return a helpful one. resp.append( Utils.wrap( _("""\ No commands were found in this message. To obtain instructions, send a message containing just the word "help". """))) if self.ignored and mm_cfg.RESPONSE_INCLUDE_LEVEL >= 2: resp.append(_('\n- Ignored:')) resp.extend(indent(self.ignored)) resp.append(_('\n- Done.\n\n')) # Encode any unicode strings into the list charset, so we don't try to # join unicode strings and invalid ASCII. charset = Utils.GetCharSet(self.msgdata['lang']) encoded_resp = [] for item in resp: if isinstance(item, UnicodeType): item = item.encode(charset, 'replace') encoded_resp.append(item) results = MIMEText(NL.join(encoded_resp), _charset=charset) # Safety valve for mail loops with misconfigured email 'bots. We # don't respond to commands sent with "Precedence: bulk|junk|list" # unless they explicitly "X-Ack: yes", but not all mail 'bots are # correctly configured, so we max out the number of responses we'll # give to an address in a single day. # # BAW: We wait until now to make this decision since our sender may # not be self.msg.get_sender(), but I'm not sure this is right. recip = self.returnaddr or self.msg.get_sender() if not self.mlist.autorespondToSender(recip, self.msgdata['lang']): return msg = Message.UserNotification(recip, self.mlist.GetOwnerEmail(), _('The results of your email commands'), lang=self.msgdata['lang']) msg.set_type('multipart/mixed') msg.attach(results) if mm_cfg.RESPONSE_INCLUDE_LEVEL == 1: self.msg.set_payload( _('Message body suppressed by Mailman site configuration\n')) if mm_cfg.RESPONSE_INCLUDE_LEVEL == 0: orig = MIMEText(_( 'Original message suppressed by Mailman site configuration\n'), _charset=charset) else: orig = MIMEMessage(self.msg) msg.attach(orig) msg.send(self.mlist)
def UpdateOldVars(l, stored_state): """Transform old variable values into new ones, deleting old ones. stored_state is last snapshot from file, as opposed to from InitVars().""" def PreferStored(oldname, newname, newdefault=uniqueval, l=l, state=stored_state): """Use specified old value if new value is not in stored state. If the old attr does not exist, and no newdefault is specified, the new attr is *not* created - so either specify a default or be positive that the old attr exists - or don't depend on the new attr. """ if hasattr(l, oldname): if not state.has_key(newname): setattr(l, newname, getattr(l, oldname)) delattr(l, oldname) if not hasattr(l, newname) and newdefault is not uniqueval: setattr(l, newname, newdefault) def recode(mlist, f, t): """If the character set for a list's preferred_language has changed, attempt to recode old string values into the new character set. mlist is the list, f is the old charset and t is the new charset. """ for x in dir(mlist): if x.startswith('_'): continue nv = doitem(getattr(mlist, x), f, t) if nv: setattr(mlist, x, nv) def doitem(v, f, t): """Recursively process lists, tuples and dictionary values and convert strings as needed. Return either the updated item or None if no change.""" changed = False if isinstance(v, str): return convert(v, f, t) elif isinstance(v, list): for i in range(len(v)): nv = doitem(v[i], f, t) if nv: changed = True v[i] = nv if changed: return v else: return None elif isinstance(v, tuple): nt = () for i in range(len(v)): nv = doitem(v[i], f, t) if nv: changed = True nt += (nv, ) else: nt += (v[i], ) if changed: return nt else: return None elif isinstance(v, dict): for k, ov in v.items(): nv = doitem(ov, f, t) if nv: changed = True v[k] = nv if changed: return v else: return None else: return None def convert(s, f, t): """This does the actual character set conversion of the string s from charset f to charset t.""" try: u = unicode(s, f) is_f = True except ValueError: is_f = False try: unicode(s, t) is_t = True except ValueError: is_t = False if is_f and not is_t: return u.encode(t, 'replace') else: return None # Migrate to 2.1b3, baw 17-Aug-2001 if hasattr(l, 'dont_respond_to_post_requests'): oldval = getattr(l, 'dont_respond_to_post_requests') if not hasattr(l, 'respond_to_post_requests'): l.respond_to_post_requests = not oldval del l.dont_respond_to_post_requests # Migrate to 2.1b3, baw 13-Oct-2001 # Basic defaults for new variables if not hasattr(l, 'default_member_moderation'): l.default_member_moderation = mm_cfg.DEFAULT_DEFAULT_MEMBER_MODERATION if not hasattr(l, 'accept_these_nonmembers'): l.accept_these_nonmembers = [] if not hasattr(l, 'hold_these_nonmembers'): l.hold_these_nonmembers = [] if not hasattr(l, 'reject_these_nonmembers'): l.reject_these_nonmembers = [] if not hasattr(l, 'discard_these_nonmembers'): l.discard_these_nonmembers = [] if not hasattr(l, 'forward_auto_discards'): l.forward_auto_discards = mm_cfg.DEFAULT_FORWARD_AUTO_DISCARDS if not hasattr(l, 'generic_nonmember_action'): l.generic_nonmember_action = mm_cfg.DEFAULT_GENERIC_NONMEMBER_ACTION # Now convert what we can... Note that the interaction between the # MM2.0.x attributes `moderated', `member_posting_only', and `posters' is # so confusing, it makes my brain really ache. Which is why they go away # in MM2.1. I think the best we can do semantically is the following: # # - If moderated == yes, then any sender who's address is not on the # posters attribute would get held for approval. If the sender was on # the posters list, then we'd defer judgement to a later step # - If member_posting_only == yes, then members could post without holds, # and if there were any addresses added to posters, they could also post # without holds. # - If member_posting_only == no, then what happens depends on the value # of the posters attribute: # o If posters was empty, then anybody can post without their # message being held for approval # o If posters was non-empty, then /only/ those addresses could post # without approval, i.e. members not on posters would have their # messages held for approval. # # How to translate this mess to MM2.1 values? I'm sure I got this wrong # before, but here's how we're going to do it, as of MM2.1b3. # # - We'll control member moderation through their Moderate flag, and # non-member moderation through the generic_nonmember_action, # hold_these_nonmembers, and accept_these_nonmembers. # - If moderated == yes then we need to troll through the addresses on # posters, and any non-members would get added to # accept_these_nonmembers. /Then/ we need to troll through the # membership and any member on posters would get their Moderate flag # unset, while members not on posters would get their Moderate flag set. # Then generic_nonmember_action gets set to 1 (hold) so nonmembers get # moderated, and default_member_moderation will be set to 1 (hold) so # new members will also get held for moderation. We'll stop here. # - We only get to here if moderated == no. # - If member_posting_only == yes, then we'll turn off the Moderate flag # for members. We troll through the posters attribute and add all those # addresses to accept_these_nonmembers. We'll also set # generic_nonmember_action to 1 and default_member_moderation to 0. # We'll stop here. # - We only get to here if member_posting_only == no # - If posters is empty, then anybody could post without being held for # approval, so we'll set generic_nonmember_action to 0 (accept), and # we'll turn off the Moderate flag for all members. We'll also turn off # default_member_moderation so new members can post without approval. # We'll stop here. # - We only get here if posters is non-empty. # - This means that /only/ the addresses on posters got to post without # being held for approval. So first, we troll through posters and add # all non-members to accept_these_nonmembers. Then we troll through the # membership and if their address is on posters, we'll clear their # Moderate flag, otherwise we'll set it. We'll turn on # default_member_moderation so new members get moderated. We'll set # generic_nonmember_action to 1 (hold) so all other non-members will get # moderated. And I think we're finally done. # # SIGH. if hasattr(l, 'moderated'): # We'll assume we're converting all these attributes at once if l.moderated: #syslog('debug', 'Case 1') for addr in l.posters: if not l.isMember(addr): l.accept_these_nonmembers.append(addr) for member in l.getMembers(): l.setMemberOption( member, mm_cfg.Moderate, # reset for explicitly named members member not in l.posters) l.generic_nonmember_action = 1 l.default_member_moderation = 1 elif l.member_posting_only: #syslog('debug', 'Case 2') for addr in l.posters: if not l.isMember(addr): l.accept_these_nonmembers.append(addr) for member in l.getMembers(): l.setMemberOption(member, mm_cfg.Moderate, 0) l.generic_nonmember_action = 1 l.default_member_moderation = 0 elif not l.posters: #syslog('debug', 'Case 3') for member in l.getMembers(): l.setMemberOption(member, mm_cfg.Moderate, 0) l.generic_nonmember_action = 0 l.default_member_moderation = 0 else: #syslog('debug', 'Case 4') for addr in l.posters: if not l.isMember(addr): l.accept_these_nonmembers.append(addr) for member in l.getMembers(): l.setMemberOption( member, mm_cfg.Moderate, # reset for explicitly named members member not in l.posters) l.generic_nonmember_action = 1 l.default_member_moderation = 1 # Now get rid of the old attributes del l.moderated del l.posters del l.member_posting_only if hasattr(l, 'forbidden_posters'): # For each of the posters on this list, if they are members, toggle on # their moderation flag. If they are not members, then add them to # hold_these_nonmembers. forbiddens = l.forbidden_posters for addr in forbiddens: if l.isMember(addr): l.setMemberOption(addr, mm_cfg.Moderate, 1) else: l.hold_these_nonmembers.append(addr) del l.forbidden_posters # Migrate to 1.0b6, klm 10/22/1998: PreferStored('reminders_to_admins', 'umbrella_list', mm_cfg.DEFAULT_UMBRELLA_LIST) # Migrate up to 1.0b5: PreferStored('auto_subscribe', 'open_subscribe') PreferStored('closed', 'private_roster') PreferStored('mimimum_post_count_before_removal', 'mimimum_post_count_before_bounce_action') PreferStored('bad_posters', 'forbidden_posters') PreferStored('automatically_remove', 'automatic_bounce_action') if hasattr(l, "open_subscribe"): if l.open_subscribe: if mm_cfg.ALLOW_OPEN_SUBSCRIBE: l.subscribe_policy = 0 else: l.subscribe_policy = 1 else: l.subscribe_policy = 2 # admin approval delattr(l, "open_subscribe") if not hasattr(l, "administrivia"): setattr(l, "administrivia", mm_cfg.DEFAULT_ADMINISTRIVIA) if not hasattr(l, "admin_member_chunksize"): setattr(l, "admin_member_chunksize", mm_cfg.DEFAULT_ADMIN_MEMBER_CHUNKSIZE) # # this attribute was added then deleted, so there are a number of # cases to take care of # if hasattr(l, "posters_includes_members"): if l.posters_includes_members: if l.posters: l.member_posting_only = 1 else: if l.posters: l.member_posting_only = 0 delattr(l, "posters_includes_members") elif l.data_version <= 10 and l.posters: # make sure everyone gets the behavior the list used to have, but only # for really old versions of Mailman (1.0b5 or before). Any newer # version of Mailman should not get this attribute whacked. l.member_posting_only = 0 # # transfer the list data type for holding members and digest members # to the dict data type starting file format version 11 # if type(l.members) is ListType: members = {} for m in l.members: members[m] = 1 l.members = members if type(l.digest_members) is ListType: dmembers = {} for dm in l.digest_members: dmembers[dm] = 1 l.digest_members = dmembers # # set admin_notify_mchanges # if not hasattr(l, "admin_notify_mchanges"): setattr(l, "admin_notify_mchanges", mm_cfg.DEFAULT_ADMIN_NOTIFY_MCHANGES) # # Convert the members and digest_members addresses so that the keys of # both these are always lowercased, but if there is a case difference, the # value contains the case preserved value # for k in l.members.keys(): if k.lower() <> k: l.members[k.lower()] = Utils.LCDomain(k) del l.members[k] elif type(l.members[k]) == StringType and k == l.members[k].lower(): # already converted pass else: l.members[k] = 0 for k in l.digest_members.keys(): if k.lower() <> k: l.digest_members[k.lower()] = Utils.LCDomain(k) del l.digest_members[k] elif type(l.digest_members[k]) == StringType and \ k == l.digest_members[k].lower(): # already converted pass else: l.digest_members[k] = 0 # # Convert pre 2.2 topics regexps which were compiled in verbose mode # to a non-verbose equivalent. # if stored_state['data_version'] < 106 and stored_state.has_key('topics'): l.topics = [] for name, pattern, description, emptyflag in stored_state['topics']: pattern = Utils.strip_verbose_pattern(pattern) l.topics.append((name, pattern, description, emptyflag)) # # Romanian and Russian had their character sets changed in 2.1.19 # to utf-8. If there are any strings in the old encoding, try to recode # them. # if stored_state['data_version'] < 108: if l.preferred_language == 'ro': if Utils.GetCharSet('ro') == 'utf-8': recode(l, 'iso-8859-2', 'utf-8') if l.preferred_language == 'ru': if Utils.GetCharSet('ru') == 'utf-8': recode(l, 'koi8-r', 'utf-8') # # from_is_list was called author_is_list in 2.1.16rc2 (only). PreferStored('author_is_list', 'from_is_list', mm_cfg.DEFAULT_FROM_IS_LIST)
def NewVars(l): """Add defaults for these new variables if they don't exist.""" def add_only_if_missing(attr, initval, l=l): if not hasattr(l, attr): setattr(l, attr, initval) # 1.2 beta 1, baw 18-Feb-2000 # Autoresponder mixin class attributes add_only_if_missing('autorespond_postings', 0) add_only_if_missing('autorespond_admin', 0) add_only_if_missing('autorespond_requests', 0) add_only_if_missing('autoresponse_postings_text', '') add_only_if_missing('autoresponse_admin_text', '') add_only_if_missing('autoresponse_request_text', '') add_only_if_missing('autoresponse_graceperiod', 90) add_only_if_missing('postings_responses', {}) add_only_if_missing('admin_responses', {}) add_only_if_missing('reply_goes_to_list', '') add_only_if_missing('preferred_language', mm_cfg.DEFAULT_SERVER_LANGUAGE) add_only_if_missing('available_languages', []) add_only_if_missing('digest_volume_frequency', mm_cfg.DEFAULT_DIGEST_VOLUME_FREQUENCY) add_only_if_missing('digest_last_sent_at', 0) add_only_if_missing('mod_password', None) add_only_if_missing('post_password', None) add_only_if_missing('moderator', []) add_only_if_missing('topics', []) add_only_if_missing('topics_enabled', 0) add_only_if_missing('topics_bodylines_limit', 5) add_only_if_missing('one_last_digest', {}) add_only_if_missing('usernames', {}) add_only_if_missing('personalize', 0) add_only_if_missing('first_strip_reply_to', mm_cfg.DEFAULT_FIRST_STRIP_REPLY_TO) add_only_if_missing('subscribe_auto_approval', mm_cfg.DEFAULT_SUBSCRIBE_AUTO_APPROVAL) add_only_if_missing('unsubscribe_policy', mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY) add_only_if_missing('send_goodbye_msg', mm_cfg.DEFAULT_SEND_GOODBYE_MSG) add_only_if_missing('include_rfc2369_headers', 1) add_only_if_missing('include_list_post_header', 1) add_only_if_missing('include_sender_header', 1) add_only_if_missing('bounce_score_threshold', mm_cfg.DEFAULT_BOUNCE_SCORE_THRESHOLD) add_only_if_missing('bounce_info_stale_after', mm_cfg.DEFAULT_BOUNCE_INFO_STALE_AFTER) add_only_if_missing('bounce_you_are_disabled_warnings', mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS) add_only_if_missing( 'bounce_you_are_disabled_warnings_interval', mm_cfg.DEFAULT_BOUNCE_YOU_ARE_DISABLED_WARNINGS_INTERVAL) add_only_if_missing('bounce_unrecognized_goes_to_list_owner', mm_cfg.DEFAULT_BOUNCE_UNRECOGNIZED_GOES_TO_LIST_OWNER) add_only_if_missing('bounce_notify_owner_on_bounce_increment', mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_BOUNCE_INCREMENT) add_only_if_missing('bounce_notify_owner_on_disable', mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_DISABLE) add_only_if_missing('bounce_notify_owner_on_removal', mm_cfg.DEFAULT_BOUNCE_NOTIFY_OWNER_ON_REMOVAL) add_only_if_missing('ban_list', []) add_only_if_missing('filter_mime_types', mm_cfg.DEFAULT_FILTER_MIME_TYPES) add_only_if_missing('pass_mime_types', mm_cfg.DEFAULT_PASS_MIME_TYPES) add_only_if_missing('filter_content', mm_cfg.DEFAULT_FILTER_CONTENT) add_only_if_missing('convert_html_to_plaintext', mm_cfg.DEFAULT_CONVERT_HTML_TO_PLAINTEXT) add_only_if_missing('filter_action', mm_cfg.DEFAULT_FILTER_ACTION) add_only_if_missing('delivery_status', {}) # This really ought to default to mm_cfg.HOLD, but that doesn't work with # the current GUI description model. So, 0==Hold, 1==Reject, 2==Discard add_only_if_missing('member_moderation_action', 0) add_only_if_missing('member_moderation_notice', '') add_only_if_missing('dmarc_moderation_action', mm_cfg.DEFAULT_DMARC_MODERATION_ACTION) add_only_if_missing('dmarc_quarantine_moderation_action', mm_cfg.DEFAULT_DMARC_QUARANTINE_MODERATION_ACTION) add_only_if_missing('dmarc_none_moderation_action', mm_cfg.DEFAULT_DMARC_NONE_MODERATION_ACTION) add_only_if_missing('dmarc_moderation_notice', '') add_only_if_missing('dmarc_wrapped_message_text', mm_cfg.DEFAULT_DMARC_WRAPPED_MESSAGE_TEXT) add_only_if_missing('member_verbosity_threshold', mm_cfg.DEFAULT_MEMBER_VERBOSITY_THRESHOLD) add_only_if_missing('member_verbosity_interval', mm_cfg.DEFAULT_MEMBER_VERBOSITY_INTERVAL) add_only_if_missing('equivalent_domains', mm_cfg.DEFAULT_EQUIVALENT_DOMAINS) add_only_if_missing('new_member_options', mm_cfg.DEFAULT_NEW_MEMBER_OPTIONS) # Emergency moderation flag add_only_if_missing('emergency', 0) add_only_if_missing('hold_and_cmd_autoresponses', {}) add_only_if_missing('news_prefix_subject_too', 1) # Should prefixes be encoded? if Utils.GetCharSet(l.preferred_language) == 'us-ascii': encode = 0 else: encode = 2 add_only_if_missing('encode_ascii_prefixes', encode) add_only_if_missing('news_moderation', 0) add_only_if_missing('header_filter_rules', []) # Scrubber in regular delivery add_only_if_missing('scrub_nondigest', 0) # ContentFilter by file extensions add_only_if_missing('filter_filename_extensions', mm_cfg.DEFAULT_FILTER_FILENAME_EXTENSIONS) add_only_if_missing('pass_filename_extensions', []) # automatic discard add_only_if_missing('max_days_to_hold', 0) add_only_if_missing('nonmember_rejection_notice', '') # multipart/alternative collapse add_only_if_missing('collapse_alternatives', mm_cfg.DEFAULT_COLLAPSE_ALTERNATIVES) # exclude/include lists add_only_if_missing('regular_exclude_lists', mm_cfg.DEFAULT_REGULAR_EXCLUDE_LISTS) add_only_if_missing('regular_include_lists', mm_cfg.DEFAULT_REGULAR_INCLUDE_LISTS) add_only_if_missing('regular_exclude_ignore', mm_cfg.DEFAULT_REGULAR_EXCLUDE_IGNORE)
def _handleForm(self, mlist, category, subcat, cgidata, doc): # TK: If there is no hdrfilter_* in cgidata, we should not touch # the header filter rules. if not cgidata.has_key('hdrfilter_rebox_01'): return # First deal with rules = [] # We start i at 1 and keep going until we no longer find items keyed # with the marked tags. i = 1 downi = None while True: deltag = 'hdrfilter_delete_%02d' % i reboxtag = 'hdrfilter_rebox_%02d' % i actiontag = 'hdrfilter_action_%02d' % i wheretag = 'hdrfilter_where_%02d' % i addtag = 'hdrfilter_add_%02d' % i newtag = 'hdrfilter_new_%02d' % i uptag = 'hdrfilter_up_%02d' % i downtag = 'hdrfilter_down_%02d' % i i += 1 # Was this a delete? If so, we can just ignore this entry if cgidata.has_key(deltag): continue # Get the data for the current box pattern = cgidata.getfirst(reboxtag) try: action = int(cgidata.getfirst(actiontag)) # We'll get a TypeError when the actiontag is missing and the # .getvalue() call returns None. except (ValueError, TypeError): action = mm_cfg.DEFER if pattern is None: # We came to the end of the boxes break if cgidata.has_key(newtag) and not pattern: # This new entry is incomplete. if i == 2: # OK it is the first. continue doc.addError( _("""Header filter rules require a pattern. Incomplete filter rules will be ignored.""")) continue # Make sure the pattern was a legal regular expression. # Convert it to unicode if necessary. mo = re.match('.*charset=([-_a-z0-9]+)', os.environ.get('CONTENT_TYPE', ''), re.IGNORECASE) if mo: cset = mo.group(1) else: cset = Utils.GetCharSet(mlist.preferred_language) try: upattern = Utils.xml_to_unicode(pattern, cset) re.compile(upattern) pattern = upattern except (re.error, TypeError): safepattern = Utils.websafe(pattern) doc.addError( _("""The header filter rule pattern '%(safepattern)s' is not a legal regular expression. This rule will be ignored.""")) continue # Was this an add item? if cgidata.has_key(addtag): # Where should the new one be added? where = cgidata.getfirst(wheretag) if where == 'before': # Add a new empty rule box before the current one rules.append(('', mm_cfg.DEFER, True)) rules.append((pattern, action, False)) # Default is to add it after... else: rules.append((pattern, action, False)) rules.append(('', mm_cfg.DEFER, True)) # Was this an up movement? elif cgidata.has_key(uptag): # As long as this one isn't the first rule, move it up if rules: rules.insert(-1, (pattern, action, False)) else: rules.append((pattern, action, False)) # Was this the down movement? elif cgidata.has_key(downtag): downi = i - 2 rules.append((pattern, action, False)) # Otherwise, just retain this one in the list else: rules.append((pattern, action, False)) # Move any down button filter rule if downi is not None: rule = rules[downi] del rules[downi] rules.insert(downi + 1, rule) mlist.header_filter_rules = rules
def _decode(h): if not h: return h return Utils.oneline(h, Utils.GetCharSet(mlist.preferred_language))
def __processbody_URLquote(self, lines): # XXX a lot to do here: # 1. use lines directly, rather than source and dest # 2. make it clearer # 3. make it faster # TK: Prepare for unicode obscure. atmark = _(' at ') if lines and isinstance(lines[0], types.UnicodeType): atmark = unicode(atmark, Utils.GetCharSet(self.lang), 'replace') source = lines[:] dest = lines last_line_was_quoted = 0 for i in xrange(0, len(source)): Lorig = L = source[i] prefix = suffix = "" if L is None: continue # Italicise quoted text if self.IQUOTES: quoted = quotedpat.match(L) if quoted is None: last_line_was_quoted = 0 else: quoted = quoted.end(0) prefix = CGIescape(L[:quoted], self.lang) + '<i>' suffix = '</I>' if self.SHOWHTML: suffix += '<BR>' if not last_line_was_quoted: prefix = '<BR>' + prefix L = L[quoted:] last_line_was_quoted = 1 # Check for an e-mail address L2 = "" jr = emailpat.search(L) kr = urlpat.search(L) while jr is not None or kr is not None: if jr == None: j = -1 else: j = jr.start(0) if kr is None: k = -1 else: k = kr.start(0) if j != -1 and (j < k or k == -1): text = jr.group(1) length = len(text) if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: text = re.sub('@', atmark, text) URL = self.maillist.GetScriptURL('listinfo', absolute=1) else: URL = 'mailto:' + text pos = j elif k != -1 and (j > k or j == -1): text = URL = kr.group(1) length = len(text) pos = k else: # j==k raise ValueError, "j==k: This can't happen!" #length = len(text) #self.message("URL: %s %s %s \n" # % (CGIescape(L[:pos]), URL, CGIescape(text))) L2 += '%s<A HREF="%s">%s</A>' % (CGIescape( L[:pos], self.lang), html_quote(URL), CGIescape(text, self.lang)) L = L[pos + length:] jr = emailpat.search(L) kr = urlpat.search(L) if jr is None and kr is None: L = CGIescape(L, self.lang) L = prefix + L2 + L + suffix source[i] = None dest[i] = L
def __init__(self, message=None, sequence=0, keepHeaders=[], lang=mm_cfg.DEFAULT_SERVER_LANGUAGE, mlist=None): self.__super_init(message, sequence, keepHeaders) self.prev = None self.next = None # Trim Re: from the subject line i = 0 while i != -1: result = REpat.match(self.subject) if result: i = result.end(0) self.subject = self.subject[i:] else: i = -1 # Useful to keep around self._lang = lang self._mlist = mlist if mm_cfg.ARCHIVER_OBSCURES_EMAILADDRS: # Avoid i18n side-effects. Note that the language for this # article (for this list) could be different from the site-wide # preferred language, so we need to ensure no side-effects will # occur. Think what happens when executing bin/arch. otrans = i18n.get_translation() try: i18n.set_language(lang) if self.author == self.email: self.author = self.email = re.sub('@', _(' at '), self.email) else: self.email = re.sub('@', _(' at '), self.email) finally: i18n.set_translation(otrans) # Snag the content-* headers. RFC 1521 states that their values are # case insensitive. ctype = message.get('Content-Type', 'text/plain') cenc = message.get('Content-Transfer-Encoding', '') self.ctype = ctype.lower() self.cenc = cenc.lower() self.decoded = {} cset = Utils.GetCharSet(mlist.preferred_language) cset_out = Charset(cset).output_charset or cset if isinstance(cset_out, unicode): # email 3.0.1 (python 2.4) doesn't like unicode cset_out = cset_out.encode('us-ascii') charset = message.get_content_charset(cset_out) if charset: charset = charset.lower().strip() if charset[0] == '"' and charset[-1] == '"': charset = charset[1:-1] if charset[0] == "'" and charset[-1] == "'": charset = charset[1:-1] try: body = message.get_payload(decode=True) except binascii.Error: body = None if body and charset != Utils.GetCharSet(self._lang): # decode body try: body = unicode(body, charset) except (UnicodeError, LookupError): body = None if body: self.body = [l + "\n" for l in body.splitlines()] self.decode_headers()