Example #1
0
    def setUp(self):
        self.fp = StringIO()
        w = self.fp.write
        w('Content-Type: multipart/mixed; boundary="foo"\r\n\r\n')
        w('This is a multipart message. Ignore this bit.\r\n')
        w('\r\n--foo\r\n')

        w('Content-Type: text/plain\r\n\r\n')
        w('Hello, world!\r\n')
        w('\r\n')
        w('Blah blah\r\n')
        w('foo\r\n')
        w('-foo\r\n')
        w('\r\n--foo\r\n')

        w('Content-Type: multipart/alternative; boundary="bar"\r\n\r\n')
        w('This is a multipart message. Ignore this bit.\r\n')
        w('\r\n--bar\r\n')

        w('Content-Type: text/plain\r\n\r\n')
        w('Hello, world!\r\n')
        w('\r\n')
        w('Blah blah\r\n')
        w('\r\n--bar\r\n')

        w('Content-Type: text/html\r\n\r\n')
        w('<b>Hello, world!</b>\r\n')
        w('\r\n--bar--\r\n')
        w('\r\n--foo\r\n')

        w('Content-Type: text/plain\r\n\r\n')
        w('Last bit\n')
        w('\r\n--foo--\r\n')
        self.fp.seek(0)
Example #2
0
    def test_Version_Check(self):

        # test for valid versions
        from roundup.version_check import VERSION_NEEDED
        self.assertEqual((2, 7), VERSION_NEEDED)
        del (sys.modules['roundup.version_check'])

        # fake an invalid version
        real_ver = sys.version_info
        sys.version_info = (2, 1)

        # exit is called on failure, but that breaks testing so
        # just return and discard the exit code.
        real_exit = sys.exit
        sys.exit = lambda code: code

        # error case uses print(), capture and check
        capturedOutput = StringIO()
        sys.stdout = capturedOutput
        from roundup.version_check import VERSION_NEEDED
        sys.stdout = sys.__stdout__
        self.assertIn("Roundup requires Python 2.7", capturedOutput.getvalue())

        # reset to valid values for future tests
        sys.exit = real_exit
        sys.version_info = real_ver
Example #3
0
def gen_message(spec):
    """Create a basic MIME message according to 'spec'.

    Each line of a spec has one content-type, which is optionally indented.
    The indentation signifies how deep in the MIME hierarchy the
    content-type is.

    """
    def getIndent(line):
        """Get the current line's indentation, using four-space indents."""
        count = 0
        for char in line:
            if char != ' ':
                break
            count += 1
        return count // 4

    # A note on message/rfc822: The content of such an attachment is an
    # email with at least one header line. RFC2046 tells us: """   A
    # media type of "message/rfc822" indicates that the body contains an
    # encapsulated message, with the syntax of an RFC 822 message.
    # However, unlike top-level RFC 822 messages, the restriction that
    # each "message/rfc822" body must include a "From", "Date", and at
    # least one destination header is removed and replaced with the
    # requirement that at least one of "From", "Subject", or "Date" must
    # be present."""
    # This means we have to add a newline after the mime-header before
    # the subject, otherwise the subject is part of the mime header not
    # part of the email header.
    table = {
        'multipart/signed': '    boundary="boundary-%(indent)s";\n',
        'multipart/mixed': '    boundary="boundary-%(indent)s";\n',
        'multipart/alternative': '    boundary="boundary-%(indent)s";\n',
        'text/plain': '    name="foo.txt"\nfoo\n',
        'text/html':
        '    name="bar.html"\n<html><body>bar &gt;</body></html>\n',
        'application/pgp-signature': '    name="foo.gpg"\nfoo\n',
        'application/pdf': '    name="foo.pdf"\nfoo\n',
        'message/rfc822': '\nSubject: foo\n\nfoo\n'
    }

    parts = []
    for line in spec.splitlines():
        content_type = line.strip()
        if not content_type:
            continue

        indent = getIndent(line)
        if indent:
            parts.append('\n--boundary-%s\n' % indent)
        parts.append('Content-type: %s;\n' % content_type)
        parts.append(table[content_type] % {'indent': indent + 1})

    for i in range(indent, 0, -1):
        parts.append('\n--boundary-%s--\n' % i)

    return email.message_from_file(StringIO(''.join(parts)), RoundupMessage)
Example #4
0
 def run_cgi(self):
     """ Execute the CGI command. Wrap an innner call in an error
         handler so all errors can be caught.
     """
     try:
         self.inner_run_cgi()
     except client.NotFound:
         self.send_error(404, self.path)
     except client.Unauthorised as message:
         self.send_error(403, '%s (%s)' % (self.path, message))
     except:
         exc, val, tb = sys.exc_info()
         if hasattr(socket, 'timeout') and isinstance(val, socket.timeout):
             self.log_error('timeout')
         else:
             # it'd be nice to be able to detect if these are going to have
             # any effect...
             self.send_response(400)
             self.send_header('Content-Type', 'text/html')
             self.end_headers()
             if self.DEBUG_MODE:
                 try:
                     reload(cgitb)
                     self.wfile.write(s2b(cgitb.breaker()))
                     self.wfile.write(s2b(cgitb.html()))
                 except:
                     s = StringIO()
                     traceback.print_exc(None, s)
                     self.wfile.write(b"<pre>")
                     self.wfile.write(s2b(cgi.escape(s.getvalue())))
                     self.wfile.write(b"</pre>\n")
             else:
                 # user feedback
                 self.wfile.write(s2b(cgitb.breaker()))
                 ts = time.ctime()
                 self.wfile.write(
                     s2b('''<p>%s: An error occurred. Please check
                 the server log for more information.</p>''' % ts))
                 # out to the logfile
                 print('EXCEPTION AT', ts)
                 traceback.print_exc()
Example #5
0
class MultipartTestCase(unittest.TestCase):
    def setUp(self):
        self.fp = StringIO()
        w = self.fp.write
        w('Content-Type: multipart/mixed; boundary="foo"\r\n\r\n')
        w('This is a multipart message. Ignore this bit.\r\n')
        w('\r\n--foo\r\n')

        w('Content-Type: text/plain\r\n\r\n')
        w('Hello, world!\r\n')
        w('\r\n')
        w('Blah blah\r\n')
        w('foo\r\n')
        w('-foo\r\n')
        w('\r\n--foo\r\n')

        w('Content-Type: multipart/alternative; boundary="bar"\r\n\r\n')
        w('This is a multipart message. Ignore this bit.\r\n')
        w('\r\n--bar\r\n')

        w('Content-Type: text/plain\r\n\r\n')
        w('Hello, world!\r\n')
        w('\r\n')
        w('Blah blah\r\n')
        w('\r\n--bar\r\n')

        w('Content-Type: text/html\r\n\r\n')
        w('<b>Hello, world!</b>\r\n')
        w('\r\n--bar--\r\n')
        w('\r\n--foo\r\n')

        w('Content-Type: text/plain\r\n\r\n')
        w('Last bit\n')
        w('\r\n--foo--\r\n')
        self.fp.seek(0)

    def testMultipart(self):
        m = email.message_from_file(self.fp, RoundupMessage)
        self.assert_(m is not None)

        it = iter(m.get_payload())

        # first text/plain
        p = next(it, None)
        self.assert_(p is not None)
        self.assertEqual(p.get_content_type(), 'text/plain')
        self.assertEqual(p.get_payload(),
                         'Hello, world!\r\n\r\nBlah blah\r\nfoo\r\n-foo\r\n')

        # sub-multipart
        p = next(it, None)
        self.assert_(p is not None)
        self.assertEqual(p.get_content_type(), 'multipart/alternative')

        # sub-multipart text/plain
        qit = iter(p.get_payload())
        q = next(qit, None)
        self.assert_(q is not None)
        self.assertEqual(q.get_content_type(), 'text/plain')
        self.assertEqual(q.get_payload(), 'Hello, world!\r\n\r\nBlah blah\r\n')

        # sub-multipart text/html
        q = next(qit, None)
        self.assert_(q is not None)
        self.assertEqual(q.get_content_type(), 'text/html')
        self.assertEqual(q.get_payload(), '<b>Hello, world!</b>\r\n')

        # sub-multipart end
        q = next(qit, None)
        self.assert_(q is None)

        # final text/plain
        p = next(it, None)
        self.assert_(p is not None)
        self.assertEqual(p.get_content_type(), 'text/plain')
        self.assertEqual(p.get_payload(), 'Last bit\n')

        # end
        p = next(it, None)
        self.assert_(p is None)

    def TestExtraction(self, spec, expected, convert_html_with=False):
        if convert_html_with:
            from roundup.dehtml import dehtml
            html2text = dehtml(convert_html_with).html2text
        else:
            html2text = None

        self.assertEqual(
            gen_message(spec).extract_content(html2text=html2text), expected)

    def testTextPlain(self):
        self.TestExtraction('text/plain', ('foo\n', [], False))

    def testAttachedTextPlain(self):
        self.TestExtraction(
            """
multipart/mixed
    text/plain
    text/plain""", ('foo\n', [('foo.txt', 'text/plain', 'foo\n')], False))

    def testMultipartMixed(self):
        self.TestExtraction(
            """
multipart/mixed
    text/plain
    application/pdf""",
            ('foo\n', [('foo.pdf', 'application/pdf', b'foo\n')], False))

    def testMultipartMixedHtml(self):
        # test with html conversion enabled
        self.TestExtraction("""
multipart/mixed
    text/html
    application/pdf""", ('bar >\n', [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with='dehtml')

        # test with html conversion disabled
        self.TestExtraction("""
multipart/mixed
    text/html
    application/pdf""", (None, [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with=False)

    def testMultipartAlternative(self):
        self.TestExtraction(
            """
multipart/alternative
    text/plain
    application/pdf
        """, ('foo\n', [('foo.pdf', 'application/pdf', b'foo\n')], False))

    def testMultipartAlternativeHtml(self):
        self.TestExtraction("""
multipart/alternative
    text/html
    application/pdf""", ('bar >\n', [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with='dehtml')

        self.TestExtraction("""
multipart/alternative
    text/html
    application/pdf""", (None, [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with=False)

    def testMultipartAlternativeHtmlText(self):
        # text should take priority over html when html is first
        self.TestExtraction("""
multipart/alternative
    text/html
    text/plain
    application/pdf""", ('foo\n', [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with='dehtml')

        # text should take priority over html when text is first
        self.TestExtraction("""
multipart/alternative
    text/plain
    text/html
    application/pdf""", ('foo\n', [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with='dehtml')

        # text should take priority over html when text is second and
        # html is disabled
        self.TestExtraction("""
multipart/alternative
    text/html
    text/plain
    application/pdf""", ('foo\n', [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with=False)

        # text should take priority over html when text is first and
        # html is disabled
        self.TestExtraction("""
multipart/alternative
    text/plain
    text/html
    application/pdf""", ('foo\n', [
            ('bar.html', 'text/html', '<html><body>bar &gt;</body></html>\n'),
            ('foo.pdf', 'application/pdf', b'foo\n')
        ], False),
                            convert_html_with=False)

    def testDeepMultipartAlternative(self):
        self.TestExtraction(
            """
multipart/mixed
    multipart/alternative
        text/plain
        application/pdf
        """, ('foo\n', [('foo.pdf', 'application/pdf', b'foo\n')], False))

    def testSignedText(self):
        self.TestExtraction(
            """
multipart/signed
    text/plain
    application/pgp-signature""", ('foo\n', [], False))

    def testSignedAttachments(self):
        self.TestExtraction(
            """
multipart/signed
    multipart/mixed
        text/plain
        application/pdf
    application/pgp-signature""",
            ('foo\n', [('foo.pdf', 'application/pdf', b'foo\n')], False))

    def testAttachedSignature(self):
        self.TestExtraction(
            """
multipart/mixed
    text/plain
    application/pgp-signature""", ('foo\n', [
                ('foo.gpg', 'application/pgp-signature', b'foo\n')
            ], False))

    def testMessageRfc822(self):
        self.TestExtraction("""
multipart/mixed
    message/rfc822""", (None, [
            ('foo.eml', 'message/rfc822', 'Subject: foo\n\nfoo\n')
        ], False))
Example #6
0
 def StringIO(self):
     # Third-party products wishing to provide a full Unicode-aware
     # StringIO can do so by monkey-patching this method.
     return StringIO()