Example #1
0
 def add_to_toc(self, msg, count):
     """Add a message to the table of contents."""
     subject = msg.get('subject', _('(no subject)'))
     subject = oneline(subject, in_unicode=True)
     # Don't include the redundant subject prefix in the toc
     mo = re.match('(re:? *)?({0})'.format(
         re.escape(self._mlist.subject_prefix)),
                   subject, re.IGNORECASE)
     if mo:
         subject = subject[:mo.start(2)] + subject[mo.end(2):]
     # Take only the first author we find.
     username = ''
     addresses = getaddresses(
         [oneline(msg.get('from', ''), in_unicode=True)])
     if addresses:
         username = addresses[0][0]
         if not username:
             username = addresses[0][1]
     if username:
         username = '******'.format(username)
     lines = wrap('{0:2}. {1}'. format(count, subject), 65).split('\n')
     # See if the user's name can fit on the last line
     if len(lines[-1]) + len(username) > 70:
         lines.append(username)
     else:
         lines[-1] += username
     # Add this subject to the accumulating topics
     first = True
     for line in lines:
         if first:
             print(' ', line, file=self._toc)
             first = False
         else:
             print('     ', line.lstrip(), file=self._toc)
Example #2
0
 def add_to_toc(self, msg, count):
     """Add a message to the table of contents."""
     subject = msg.get('subject', _('(no subject)'))
     subject = oneline(subject, in_unicode=True)
     # Don't include the redundant subject prefix in the toc
     mo = re.match(
         '(re:? *)?({0})'.format(re.escape(self._mlist.subject_prefix)),
         subject, re.IGNORECASE)
     if mo:
         subject = subject[:mo.start(2)] + subject[mo.end(2):]
     # Take only the first author we find.
     username = ''
     addresses = getaddresses(
         [oneline(msg.get('from', ''), in_unicode=True)])
     if addresses:
         username = addresses[0][0]
         if not username:
             username = addresses[0][1]
     if username:
         username = '******'.format(username)
     lines = wrap('{:2}. {}'.format(count, subject), 65).split('\n')
     # See if the user's name can fit on the last line
     if len(lines[-1]) + len(username) > 70:
         lines.append(username)
     else:
         lines[-1] += username
     # Add this subject to the accumulating topics
     first = True
     for line in lines:
         if first:
             print(' ', line, file=self._toc)
             first = False
         else:
             print('     ', line.lstrip(), file=self._toc)
Example #3
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print(self._separator30, file=self._text)
         print(file=self._text)
     # Each message section contains a few headers.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{}: {}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print(value, file=self._text)
     print(file=self._text)
     # Add the payload.  If the decoded payload is empty, this may be a
     # multipart message.  In that case, just stringify it.
     payload = msg.get_payload(decode=True)
     if not payload:
         payload = msg.as_string().split('\n\n', 1)[1]
     if isinstance(payload, bytes):
         try:
             # Do the decoding inside the try/except so that if the charset
             # conversion fails, we'll just drop back to ascii.
             charset = msg.get_content_charset('us-ascii')
             payload = payload.decode(charset, 'replace')
         except (LookupError, TypeError):
             # Unknown or empty charset.
             payload = payload.decode('us-ascii', 'replace')
     print(payload, file=self._text)
     if not payload.endswith('\n'):
         print(file=self._text)
Example #4
0
def bounce_message(mlist, msg, error=None):
    """Bounce the message back to the original author.

    :param mlist: The mailing list that the message was posted to.
    :type mlist: `IMailingList`
    :param msg: The original message.
    :type msg: `email.message.Message`
    :param error: Optional exception causing the bounce.  The exception
        instance must have a `.message` attribute.
    :type error: Exception
    """
    # Bounce a message back to the sender, with an error message if provided
    # in the exception argument.  .sender might be None or the empty string.
    if not msg.sender:
        # 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 = oneline(subject, mlist.preferred_language.charset)
    if error is None:
        notice = _('[No bounce details are available]')
    else:
        notice = _(error.message)
    # Currently we always craft bounces as MIME messages.
    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')
    txt = MIMEText(notice, _charset=mlist.preferred_language.charset)
    bmsg.attach(txt)
    bmsg.attach(MIMEMessage(msg))
    bmsg.send(mlist)
Example #5
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print >> self._text, self._separator30
         print >> self._text
     # Each message section contains a few headers.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{0}: {1}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print >> self._text, value
     print >> self._text
     # Add the payload.  If the decoded payload is empty, this may be a
     # multipart message.  In that case, just stringify it.
     payload = msg.get_payload(decode=True)
     payload = (payload if payload else msg.as_string().split('\n\n', 1)[1])
     try:
         charset = msg.get_content_charset('us-ascii')
         payload = unicode(payload, charset, 'replace')
     except (LookupError, TypeError):
         # Unknown or empty charset.
         payload = unicode(payload, 'us-ascii', 'replace')
     print >> self._text, payload
     if not payload.endswith('\n'):
         print >> self._text
Example #6
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print(self._separator30, file=self._text)
         print(file=self._text)
     # Each message section contains a few headers.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{0}: {1}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print(value, file=self._text)
     print(file=self._text)
     # Add the payload.  If the decoded payload is empty, this may be a
     # multipart message.  In that case, just stringify it.
     payload = msg.get_payload(decode=True)
     if not payload:
         payload = msg.as_string().split('\n\n', 1)[1]
     if isinstance(payload, bytes):
         try:
             # Do the decoding inside the try/except so that if the charset
             # conversion fails, we'll just drop back to ascii.
             charset = msg.get_content_charset('us-ascii')
             payload = payload.decode(charset, 'replace')
         except (LookupError, TypeError):
             # Unknown or empty charset.
             payload = payload.decode('us-ascii', 'replace')
     print(payload, file=self._text)
     if not payload.endswith('\n'):
         print(file=self._text)
Example #7
0
def bounce_message(mlist, msg, error=None):
    """Bounce the message back to the original author.

    :param mlist: The mailing list that the message was posted to.
    :type mlist: `IMailingList`
    :param msg: The original message.
    :type msg: `email.message.Message`
    :param error: Optional exception causing the bounce.  The exception
        instance must have a `.message` attribute.
    :type error: Exception
    """
    # Bounce a message back to the sender, with an error message if provided
    # in the exception argument.  .sender might be None or the empty string.
    if not msg.sender:
        # 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 = oneline(subject, mlist.preferred_language.charset)
    if error is None:
        notice = _('[No bounce details are available]')
    else:
        notice = _(error.message)
    # Currently we always craft bounces as MIME messages.
    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')
    txt = MIMEText(notice, _charset=mlist.preferred_language.charset)
    bmsg.attach(txt)
    bmsg.attach(MIMEMessage(msg))
    bmsg.send(mlist)
Example #8
0
def get_file_ext(m):
    """
    Get filename extension. Caution: some virus don't put filename
    in 'Content-Disposition' header.
"""
    fext = ''
    filename = m.get_filename('') or m.get_param('name', '')
    if filename:
        fext = os.path.splitext(oneline(filename,'utf-8'))[1]
        if len(fext) > 1:
            fext = fext[1:]
        else:
            fext = ''
    return fext
Example #9
0
def get_file_ext(m):
    """
    Get filename extension. Caution: some virus don't put filename
    in 'Content-Disposition' header.
"""
    fext = ''
    filename = m.get_filename('') or m.get_param('name', '')
    if filename:
        fext = os.path.splitext(oneline(filename, 'utf-8', in_unicode=True))[1]
        if len(fext) > 1:
            fext = fext[1:]
        else:
            fext = ''
    return fext.lower()
Example #10
0
 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.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
         # whether they want an acknowlegment or not, or they are a member
         # who definitely does not want an acknowlegment.
         return
     # Okay, they are a member that wants an acknowledgment of their post.
     # Give them their original subject.  BAW: do we want to use the
     # decoded header?
     original_subject = msgdata.get(
         'origsubj', msg.get('subject', _('(no subject)')))
     # Get the user's preferred language.
     language_manager = getUtility(ILanguageManager)
     language = (language_manager[msgdata['lang']]
                 if 'lang' in msgdata
                 else member.preferred_language)
     charset = language_manager[language.code].charset
     # Now get the acknowledgement template.
     display_name = mlist.display_name
     text = make('postack.txt',
                 mailing_list=mlist,
                 language=language.code,
                 wrap=False,
                 subject=oneline(original_subject, charset),
                 list_name=mlist.list_name,
                 display_name=display_name,
                 listinfo_url=mlist.script_url('listinfo'),
                 optionsurl=member.options_url,
                 )
     # Craft the outgoing message, with all headers and attributes
     # necessary for general delivery.  Then enqueue it to the outgoing
     # queue.
     subject = _('$display_name post acknowledgment')
     usermsg = UserNotification(sender, mlist.bounces_address,
                                subject, text, language)
     usermsg.send(mlist)
Example #11
0
 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.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
         # whether they want an acknowlegment or not, or they are a member
         # who definitely does not want an acknowlegment.
         return
     # Okay, they are a member that wants an acknowledgment of their post.
     # Give them their original subject.  BAW: do we want to use the
     # decoded header?
     original_subject = msgdata.get('origsubj',
                                    msg.get('subject', _('(no subject)')))
     # Get the user's preferred language.
     language_manager = getUtility(ILanguageManager)
     language = (language_manager[msgdata['lang']]
                 if 'lang' in msgdata else member.preferred_language)
     # Now get the acknowledgement template.
     display_name = mlist.display_name  # noqa: F841
     template = getUtility(ITemplateLoader).get('list:user:notice:post',
                                                mlist,
                                                language=language.code)
     text = expand(
         template,
         mlist,
         dict(
             subject=oneline(original_subject, in_unicode=True),
             # For backward compatibility.
             list_name=mlist.list_name,
         ))
     # Craft the outgoing message, with all headers and attributes
     # necessary for general delivery.  Then enqueue it to the outgoing
     # queue.
     subject = _('$display_name post acknowledgment')
     usermsg = UserNotification(sender, mlist.bounces_address, subject,
                                text, language)
     usermsg.send(mlist)
Example #12
0
 def add_message(self, msg, count):
     """Add the message to the digest."""
     if count > 1:
         print(self._separator30, file=self._text)
         print(file=self._text)
     # Each message section contains a few headers.
     # add the Message: n header first.
     print('Message: {}'.format(count), file=self._text)
     # Then the others.
     for header in config.digests.plain_digest_keep_headers.split():
         if header in msg:
             value = oneline(msg[header], in_unicode=True)
             value = wrap('{}: {}'.format(header, value))
             value = '\n\t'.join(value.split('\n'))
             print(value, file=self._text)
     print(file=self._text)
     # Get the scrubbed payload.  This is the original payload with all
     # non text/plain parts replaced by notes that they've been removed.
     payload = scrub(msg)
     # Add the payload.
     print(payload, file=self._text)
     if not payload.endswith('\n'):
         print(file=self._text)
Example #13
0
def bounce_message(mlist, msg, error=None):
    """Bounce the message back to the original author.

    :param mlist: The mailing list that the message was posted to.
    :type mlist: `IMailingList`
    :param msg: The original message.
    :type msg: `email.message.Message`
    :param error: Optional exception causing the bounce.  The exception
        instance must have a `.message` attribute.  The exception *may* have a
        non-None `.reasons` attribute which would be a list of reasons for the
        rejection, and it may have a non-None `.substitutions` attribute.  The
        latter, along with the formatted reasons will be interpolated into the
        message (`.reasons` gets put into the `$reasons` placeholder).
    :type error: RejectMessage
    """
    # Bounce a message back to the sender, with an error message if provided
    # in the exception argument.  .sender might be None or the empty string.
    if not msg.sender:
        # 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 = oneline(subject, mlist.preferred_language.charset)
    notice = (_('[No bounce details are available]')
              if error is None else str(error))
    # Currently we always craft bounces as MIME messages.
    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')
    txt = MIMEText(notice, _charset=mlist.preferred_language.charset)
    bmsg.attach(txt)
    bmsg.attach(MIMEMessage(msg))
    bmsg.send(mlist)
Example #14
0
    def _process(self, mlist, msg, msgdata):
        """See `TerminalChainBase`."""
        # Start by decorating the message with a header that contains a list
        # of all the rules that matched.  These metadata could be None or an
        # empty list.
        rule_hits = msgdata.get('rule_hits')
        if rule_hits:
            msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits)
        rule_misses = msgdata.get('rule_misses')
        if rule_misses:
            msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses)
        # Hold the message by adding it to the list's request database.
        request_id = hold_message(mlist, msg, msgdata, None)
        # Calculate a confirmation token to send to the author of the
        # message.
        pendable = HeldMessagePendable(id=request_id)
        token = getUtility(IPendings).add(pendable)
        # 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.
        member = mlist.members.get_member(msg.sender)
        language = (member.preferred_language
                    if member else mlist.preferred_language)
        # A substitution dictionary for the email templates.
        charset = mlist.preferred_language.charset
        original_subject = msg.get('subject')
        if original_subject is None:
            original_subject = _('(no subject)')
        else:
            # This must be encoded to the mailing list's perferred charset,
            # ignoring incompatible characters, otherwise when creating the
            # notification messages, we could get a Unicode error.
            oneline_subject = oneline(original_subject, in_unicode=True)
            bytes_subject = oneline_subject.encode(charset, 'replace')
            original_subject = bytes_subject.decode(charset)
        substitutions = dict(
            subject=original_subject,
            sender_email=msg.sender,
            reasons=_compose_reasons(msgdata),
            # For backward compatibility.
            sender=msg.sender,
            )
        # At this point the message is held, but now we have to craft at least
        # two responses.  The first will go to the original author of the
        # message and it will contain the token allowing them to approve or
        # discard the message.  The second one will go to the moderators of
        # the mailing list, if the list is so configured.
        #
        # Start by possibly sending a response to the message author.  There
        # are several reasons why we might not go through with this.  If the
        # message was gated from NNTP, the author may not even know about this
        # list, so don't spam them.  If the author specifically requested that
        # acknowledgments not be sent, or if the message was bulk email, then
        # we do not send the response.  It's also possible that either the
        # mailing list, or the author (if they are a member) have been
        # configured to not send such responses.
        if (not msgdata.get('fromusenet') and
                can_acknowledge(msg) and
                mlist.respond_to_post_requests and
                autorespond_to_sender(mlist, msg.sender, language)):
            # We can respond to the sender with a message indicating their
            # posting was held.
            subject = _(
              'Your message to $mlist.fqdn_listname awaits moderator approval')
            send_language_code = msgdata.get('lang', language.code)
            template = getUtility(ITemplateLoader).get(
                'list:user:notice:hold', mlist,
                language=send_language_code)
            text = wrap(expand(template, mlist, dict(
                language=send_language_code,
                **substitutions)))
            adminaddr = mlist.bounces_address
            nmsg = UserNotification(
                msg.sender, adminaddr, subject, text,
                getUtility(ILanguageManager)[send_language_code])
            nmsg.send(mlist)
        # Now the message for the list moderators.  This one should appear to
        # come from <list>-owner since we really don't need to do bounce
        # processing on it.
        if mlist.admin_immed_notify:
            # Now let's temporarily set the language context to that which the
            # administrators are expecting.
            with _.using(mlist.preferred_language.code):
                language = mlist.preferred_language
                charset = language.charset
                substitutions['subject'] = original_subject
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                substitutions['reasons'] = _compose_reasons(msgdata, 55)
                # craft the admin notification message and deliver it
                subject = _(
                    '$mlist.fqdn_listname post from $msg.sender requires '
                    'approval')
                nmsg = UserNotification(mlist.owner_address,
                                        mlist.owner_address,
                                        subject, lang=language)
                nmsg.set_type('multipart/mixed')
                template = getUtility(ITemplateLoader).get(
                    'list:admin:action:post', mlist)
                text = MIMEText(expand(template, mlist, substitutions),
                                _charset=charset)
                dmsg = MIMEText(wrap(_("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                                _charset=language.charset)
                dmsg['Subject'] = 'confirm ' + token
                dmsg['From'] = mlist.request_address
                dmsg['Date'] = formatdate(localtime=True)
                dmsg['Message-ID'] = make_msgid()
                nmsg.attach(text)
                nmsg.attach(MIMEMessage(msg))
                nmsg.attach(MIMEMessage(dmsg))
                nmsg.send(mlist, **dict(tomoderators=True))
        # Log the held message.  Log messages are not translated, so recast
        # the reasons in the English.
        with _.using('en'):
            reasons = msgdata.get('moderation_reasons', ['N/A'])
            log.info('HOLD: %s post from %s held, message-id=%s: %s',
                     mlist.fqdn_listname, msg.sender,
                     msg.get('message-id', 'n/a'), SEMISPACE.join(reasons))
        notify(HoldEvent(mlist, msg, msgdata, self))
Example #15
0
 def parse_attachment(self, part, counter, filter_html=True):
     # Store name, content-type and size
     # Figure out the attachment type and get the decoded data
     decodedpayload = part.get_payload(decode=True)
     # BAW: mimetypes ought to handle non-standard, but commonly found types,
     # e.g. image/jpg (should be image/jpeg).  For now we just store such
     # things as application/octet-streams since that seems the safest.
     ctype = part.get_content_type()
     charset = get_charset(part, default=None, guess=False)
     # i18n file name is encoded
     try:
         filename = oneline(part.get_filename(''), in_unicode=True)
     except TypeError:
         # Workaround for https://bugs.launchpad.net/mailman/+bug/1060951
         # (accented filenames)
         filename = "attachment.bin"
     filename, fnext = os.path.splitext(filename)
     # For safety, we should confirm this is valid ext for content-type
     # but we can use fnext if we introduce fnext filtering
     # TODO: re-implement this
     #if mm_cfg.SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION:
     #    # HTML message doesn't have filename :-(
     #    ext = fnext or guess_extension(ctype, fnext)
     #else:
     #    ext = guess_extension(ctype, fnext)
     ext = fnext or guess_extension(ctype, fnext)
     if not ext:
         # We don't know what it is, so assume it's just a shapeless
         # application/octet-stream, unless the Content-Type: is
         # message/rfc822, in which case we know we'll coerce the type to
         # text/plain below.
         if ctype == 'message/rfc822':
             ext = '.txt'
         else:
             ext = '.bin'
     # Allow only alphanumerics, dash, underscore, and dot
     ext = sre.sub('', ext)
     # Now base the filename on what's in the attachment, uniquifying it if
     # necessary.
     if not filename:
         filebase = 'attachment'
     else:
         # Sanitize the filename given in the message headers
         parts = pre.split(filename)
         filename = parts[-1]
         # Strip off leading dots
         filename = dre.sub('', filename)
         # Allow only alphanumerics, dash, underscore, and dot
         # i18n filenames are not supported yet,
         # see https://bugs.launchpad.net/bugs/1060951
         filename = sre.sub('', filename)
         # If the filename's extension doesn't match the type we guessed,
         # which one should we go with?  For now, let's go with the one we
         # guessed so attachments can't lie about their type.  Also, if the
         # filename /has/ no extension, then tack on the one we guessed.
         # The extension was removed from the name above.
         filebase = filename
     # TODO: bring back the HTML sanitizer feature
     if ctype == 'message/rfc822':
         submsg = part.get_payload()
         # Don't HTML-escape it, this is the frontend's job
         ## BAW: I'm sure we can eventually do better than this. :(
         #decodedpayload = websafe(str(submsg))
         decodedpayload = str(submsg)
     return (counter, filebase + ext, ctype, charset, decodedpayload)
Example #16
0
    def _process(self, mlist, msg, msgdata):
        """See `TerminalChainBase`."""
        # Start by decorating the message with a header that contains a list
        # of all the rules that matched.  These metadata could be None or an
        # empty list.
        rule_hits = msgdata.get('rule_hits')
        if rule_hits:
            msg['X-Mailman-Rule-Hits'] = SEMISPACE.join(rule_hits)
        rule_misses = msgdata.get('rule_misses')
        if rule_misses:
            msg['X-Mailman-Rule-Misses'] = SEMISPACE.join(rule_misses)
        # Hold the message by adding it to the list's request database.
        request_id = hold_message(mlist, msg, msgdata, None)
        # Calculate a confirmation token to send to the author of the
        # message.
        pendable = HeldMessagePendable(type=HeldMessagePendable.PEND_KEY,
                                       id=request_id)
        token = getUtility(IPendings).add(pendable)
        # 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.
        member = mlist.members.get_member(msg.sender)
        language = (member.preferred_language
                    if member else mlist.preferred_language)
        # A substitution dictionary for the email templates.
        charset = mlist.preferred_language.charset
        original_subject = msg.get('subject')
        if original_subject is None:
            original_subject = _('(no subject)')
        else:
            original_subject = oneline(original_subject, in_unicode=True)
        substitutions = dict(
            listname    = mlist.fqdn_listname,
            subject     = original_subject,
            sender      = msg.sender,
            reasons     = _compose_reasons(msgdata),
            )
        # At this point the message is held, but now we have to craft at least
        # two responses.  The first will go to the original author of the
        # message and it will contain the token allowing them to approve or
        # discard the message.  The second one will go to the moderators of
        # the mailing list, if the list is so configured.
        #
        # Start by possibly sending a response to the message author.  There
        # are several reasons why we might not go through with this.  If the
        # message was gated from NNTP, the author may not even know about this
        # list, so don't spam them.  If the author specifically requested that
        # acknowledgments not be sent, or if the message was bulk email, then
        # we do not send the response.  It's also possible that either the
        # mailing list, or the author (if they are a member) have been
        # configured to not send such responses.
        if (not msgdata.get('fromusenet') and
            can_acknowledge(msg) and
            mlist.respond_to_post_requests and
            autorespond_to_sender(mlist, msg.sender, language)):
            # We can respond to the sender with a message indicating their
            # posting was held.
            subject = _(
              'Your message to $mlist.fqdn_listname awaits moderator approval')
            send_language_code = msgdata.get('lang', language.code)
            text = make('postheld.txt',
                        mailing_list=mlist,
                        language=send_language_code,
                        **substitutions)
            adminaddr = mlist.bounces_address
            nmsg = UserNotification(
                msg.sender, adminaddr, subject, text,
                getUtility(ILanguageManager)[send_language_code])
            nmsg.send(mlist)
        # Now the message for the list moderators.  This one should appear to
        # come from <list>-owner since we really don't need to do bounce
        # processing on it.
        if mlist.admin_immed_notify:
            # Now let's temporarily set the language context to that which the
            # administrators are expecting.
            with _.using(mlist.preferred_language.code):
                language = mlist.preferred_language
                charset = language.charset
                substitutions['subject'] = original_subject
                # We need to regenerate or re-translate a few values in the
                # substitution dictionary.
                substitutions['reasons'] = _compose_reasons(msgdata, 55)
                # craft the admin notification message and deliver it
                subject = _(
                    '$mlist.fqdn_listname post from $msg.sender requires '
                    'approval')
                nmsg = UserNotification(mlist.owner_address,
                                        mlist.owner_address,
                                        subject, lang=language)
                nmsg.set_type('multipart/mixed')
                text = MIMEText(make('postauth.txt',
                                     mailing_list=mlist,
                                     wrap=False,
                                     **substitutions),
                                _charset=charset)
                dmsg = MIMEText(wrap(_("""\
If you reply to this message, keeping the Subject: header intact, Mailman will
discard the held message.  Do this if the message is spam.  If you reply to
this message and include an Approved: header with the list password in it, the
message will be approved for posting to the list.  The Approved: header can
also appear in the first line of the body of the reply.""")),
                                _charset=language.charset)
                dmsg['Subject'] = 'confirm ' + token
                dmsg['From'] = mlist.request_address
                dmsg['Date'] = formatdate(localtime=True)
                dmsg['Message-ID'] = make_msgid()
                nmsg.attach(text)
                nmsg.attach(MIMEMessage(msg))
                nmsg.attach(MIMEMessage(dmsg))
                nmsg.send(mlist, **dict(tomoderators=True))
        # Log the held message.  Log messages are not translated, so recast
        # the reasons in the English.
        with _.using('en'):
            reasons = _compose_reasons(msgdata)
            log.info('HOLD: %s post from %s held, message-id=%s: %s',
                     mlist.fqdn_listname, msg.sender,
                     msg.get('message-id', 'n/a'), SEMISPACE.join(reasons))
        notify(HoldEvent(mlist, msg, msgdata, self))
Example #17
0
 def parse_attachment(self, part, counter, filter_html=True):
     # Store name, content-type and size
     # Figure out the attachment type and get the decoded data
     decodedpayload = part.get_payload(decode=True)
     # BAW: mimetypes ought to handle non-standard, but commonly found types,
     # e.g. image/jpg (should be image/jpeg).  For now we just store such
     # things as application/octet-streams since that seems the safest.
     ctype = part.get_content_type()
     if not isinstance(ctype, unicode):
         ctype = ctype.decode("ascii")
     charset = get_charset(part, default=None, guess=False)
     # i18n file name is encoded
     try:
         filename = oneline(part.get_filename(''), in_unicode=True)
     except (TypeError, UnicodeDecodeError):
         # Workaround for https://bugs.launchpad.net/mailman/+bug/1060951
         # (accented filenames)
         filename = u"attachment.bin"
     filename, fnext = os.path.splitext(filename)
     # For safety, we should confirm this is valid ext for content-type
     # but we can use fnext if we introduce fnext filtering
     # TODO: re-implement this
     #if mm_cfg.SCRUBBER_USE_ATTACHMENT_FILENAME_EXTENSION:
     #    # HTML message doesn't have filename :-(
     #    ext = fnext or guess_extension(ctype, fnext)
     #else:
     #    ext = guess_extension(ctype, fnext)
     ext = fnext or guess_extension(ctype, fnext)
     if not ext:
         # We don't know what it is, so assume it's just a shapeless
         # application/octet-stream, unless the Content-Type: is
         # message/rfc822, in which case we know we'll coerce the type to
         # text/plain below.
         if ctype == 'message/rfc822':
             ext = '.txt'
         else:
             ext = '.bin'
     # Allow only alphanumerics, dash, underscore, and dot
     ext = sre.sub('', ext)
     # Now base the filename on what's in the attachment, uniquifying it if
     # necessary.
     if not filename:
         filebase = u'attachment'
     else:
         # Sanitize the filename given in the message headers
         parts = pre.split(filename)
         filename = parts[-1]
         # Strip off leading dots
         filename = dre.sub('', filename)
         # Allow only alphanumerics, dash, underscore, and dot
         # i18n filenames are not supported yet,
         # see https://bugs.launchpad.net/bugs/1060951
         filename = sre.sub('', filename)
         # If the filename's extension doesn't match the type we guessed,
         # which one should we go with?  For now, let's go with the one we
         # guessed so attachments can't lie about their type.  Also, if the
         # filename /has/ no extension, then tack on the one we guessed.
         # The extension was removed from the name above.
         filebase = filename
     # TODO: bring back the HTML sanitizer feature
     if ctype == 'message/rfc822':
         submsg = part.get_payload()
         # Don't HTML-escape it, this is the frontend's job
         ## BAW: I'm sure we can eventually do better than this. :(
         #decodedpayload = websafe(str(submsg))
         decodedpayload = str(submsg)
     return (counter, filebase+ext, ctype, charset, decodedpayload)
Example #18
0
 def test_oneline_bogus_charset(self):
     self.assertEqual(string.oneline('foo', 'bogus'), 'foo')
Example #19
0
 def test_oneline_bogus_charset(self):
     self.assertEqual(string.oneline('foo', 'bogus'), 'foo')