"Fossies" - the Fresh Open Source Software Archive

Member "roundup-2.0.0/test/test_multipart.py" (26 Aug 2019, 11576 Bytes) of package /linux/www/roundup-2.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_multipart.py": 1.6.1_vs_2.0.0.

    1 #
    2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
    3 # This module is free software, and you may redistribute it and/or modify
    4 # under the same terms as Python, so long as this copyright message and
    5 # disclaimer are retained in their original form.
    6 #
    7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
    8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
    9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
   10 # POSSIBILITY OF SUCH DAMAGE.
   11 #
   12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
   13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   14 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
   15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
   16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
   17 
   18 import email
   19 import unittest
   20 from roundup.anypy.strings import StringIO
   21 
   22 from roundup.mailgw import RoundupMessage
   23 
   24 def gen_message(spec):
   25     """Create a basic MIME message according to 'spec'.
   26 
   27     Each line of a spec has one content-type, which is optionally indented.
   28     The indentation signifies how deep in the MIME hierarchy the
   29     content-type is.
   30 
   31     """
   32 
   33     def getIndent(line):
   34         """Get the current line's indentation, using four-space indents."""
   35         count = 0
   36         for char in line:
   37             if char != ' ':
   38                 break
   39             count += 1
   40         return count // 4
   41 
   42     # A note on message/rfc822: The content of such an attachment is an
   43     # email with at least one header line. RFC2046 tells us: """   A
   44     # media type of "message/rfc822" indicates that the body contains an
   45     # encapsulated message, with the syntax of an RFC 822 message.
   46     # However, unlike top-level RFC 822 messages, the restriction that
   47     # each "message/rfc822" body must include a "From", "Date", and at
   48     # least one destination header is removed and replaced with the
   49     # requirement that at least one of "From", "Subject", or "Date" must
   50     # be present."""
   51     # This means we have to add a newline after the mime-header before
   52     # the subject, otherwise the subject is part of the mime header not
   53     # part of the email header.
   54     table = {'multipart/signed': '    boundary="boundary-%(indent)s";\n',
   55              'multipart/mixed': '    boundary="boundary-%(indent)s";\n',
   56              'multipart/alternative': '    boundary="boundary-%(indent)s";\n',
   57              'text/plain': '    name="foo.txt"\nfoo\n',
   58              'text/html': '    name="bar.html"\n<html><body>bar &gt;</body></html>\n',
   59              'application/pgp-signature': '    name="foo.gpg"\nfoo\n',
   60              'application/pdf': '    name="foo.pdf"\nfoo\n',
   61              'message/rfc822': '\nSubject: foo\n\nfoo\n'}
   62 
   63     parts = []
   64     for line in spec.splitlines():
   65         content_type = line.strip()
   66         if not content_type:
   67             continue
   68 
   69         indent = getIndent(line)
   70         if indent:
   71             parts.append('\n--boundary-%s\n' % indent)
   72         parts.append('Content-type: %s;\n' % content_type)
   73         parts.append(table[content_type] % {'indent': indent + 1})
   74 
   75     for i in range(indent, 0, -1):
   76         parts.append('\n--boundary-%s--\n' % i)
   77 
   78     return email.message_from_file(StringIO(''.join(parts)), RoundupMessage)
   79 
   80 class MultipartTestCase(unittest.TestCase):
   81     def setUp(self):
   82         self.fp = StringIO()
   83         w = self.fp.write
   84         w('Content-Type: multipart/mixed; boundary="foo"\r\n\r\n')
   85         w('This is a multipart message. Ignore this bit.\r\n')
   86         w('\r\n--foo\r\n')
   87 
   88         w('Content-Type: text/plain\r\n\r\n')
   89         w('Hello, world!\r\n')
   90         w('\r\n')
   91         w('Blah blah\r\n')
   92         w('foo\r\n')
   93         w('-foo\r\n')
   94         w('\r\n--foo\r\n')
   95 
   96         w('Content-Type: multipart/alternative; boundary="bar"\r\n\r\n')
   97         w('This is a multipart message. Ignore this bit.\r\n')
   98         w('\r\n--bar\r\n')
   99 
  100         w('Content-Type: text/plain\r\n\r\n')
  101         w('Hello, world!\r\n')
  102         w('\r\n')
  103         w('Blah blah\r\n')
  104         w('\r\n--bar\r\n')
  105 
  106         w('Content-Type: text/html\r\n\r\n')
  107         w('<b>Hello, world!</b>\r\n')
  108         w('\r\n--bar--\r\n')
  109         w('\r\n--foo\r\n')
  110 
  111         w('Content-Type: text/plain\r\n\r\n')
  112         w('Last bit\n')
  113         w('\r\n--foo--\r\n')
  114         self.fp.seek(0)
  115 
  116     def testMultipart(self):
  117         m = email.message_from_file(self.fp, RoundupMessage)
  118         self.assertTrue(m is not None)
  119 
  120         it = iter(m.get_payload())
  121 
  122         # first text/plain
  123         p = next(it, None)
  124         self.assertTrue(p is not None)
  125         self.assertEqual(p.get_content_type(), 'text/plain')
  126         self.assertEqual(p.get_payload(),
  127             'Hello, world!\r\n\r\nBlah blah\r\nfoo\r\n-foo\r\n')
  128 
  129         # sub-multipart
  130         p = next(it, None)
  131         self.assertTrue(p is not None)
  132         self.assertEqual(p.get_content_type(), 'multipart/alternative')
  133 
  134         # sub-multipart text/plain
  135         qit = iter(p.get_payload())
  136         q = next(qit, None)
  137         self.assertTrue(q is not None)
  138         self.assertEqual(q.get_content_type(), 'text/plain')
  139         self.assertEqual(q.get_payload(), 'Hello, world!\r\n\r\nBlah blah\r\n')
  140 
  141         # sub-multipart text/html
  142         q = next(qit, None)
  143         self.assertTrue(q is not None)
  144         self.assertEqual(q.get_content_type(), 'text/html')
  145         self.assertEqual(q.get_payload(), '<b>Hello, world!</b>\r\n')
  146 
  147         # sub-multipart end
  148         q = next(qit, None)
  149         self.assertTrue(q is None)
  150 
  151         # final text/plain
  152         p = next(it, None)
  153         self.assertTrue(p is not None)
  154         self.assertEqual(p.get_content_type(), 'text/plain')
  155         self.assertEqual(p.get_payload(),
  156             'Last bit\n')
  157 
  158         # end
  159         p = next(it, None)
  160         self.assertTrue(p is None)
  161 
  162     def TestExtraction(self, spec, expected, convert_html_with=False):
  163         if convert_html_with:
  164             from roundup.dehtml import dehtml
  165             html2text=dehtml(convert_html_with).html2text
  166         else:
  167             html2text=None
  168 
  169         self.assertEqual(gen_message(spec).extract_content(
  170             html2text=html2text), expected)
  171 
  172     def testTextPlain(self):
  173         self.TestExtraction('text/plain', ('foo\n', [], False))
  174 
  175     def testAttachedTextPlain(self):
  176         self.TestExtraction("""
  177 multipart/mixed
  178     text/plain
  179     text/plain""",
  180                   ('foo\n',
  181                    [('foo.txt', 'text/plain', 'foo\n')], False))
  182 
  183     def testMultipartMixed(self):
  184         self.TestExtraction("""
  185 multipart/mixed
  186     text/plain
  187     application/pdf""",
  188                   ('foo\n',
  189                    [('foo.pdf', 'application/pdf', b'foo\n')], False))
  190 
  191     def testMultipartMixedHtml(self):
  192         # test with html conversion enabled
  193         self.TestExtraction("""
  194 multipart/mixed
  195     text/html
  196     application/pdf""",
  197                   ('bar >\n',
  198                    [('bar.html', 'text/html',
  199                       '<html><body>bar &gt;</body></html>\n'),
  200                    ('foo.pdf', 'application/pdf', b'foo\n')], False),
  201                             convert_html_with='dehtml')
  202 
  203         # test with html conversion disabled
  204         self.TestExtraction("""
  205 multipart/mixed
  206     text/html
  207     application/pdf""",
  208                   (None,
  209                    [('bar.html', 'text/html',
  210                       '<html><body>bar &gt;</body></html>\n'),
  211                     ('foo.pdf', 'application/pdf', b'foo\n')], False),
  212                             convert_html_with=False)
  213 
  214     def testMultipartAlternative(self):
  215         self.TestExtraction("""
  216 multipart/alternative
  217     text/plain
  218     application/pdf
  219         """, ('foo\n', [('foo.pdf', 'application/pdf', b'foo\n')], False))
  220 
  221     def testMultipartAlternativeHtml(self):
  222         self.TestExtraction("""
  223 multipart/alternative
  224     text/html
  225     application/pdf""",
  226                   ('bar >\n',
  227                    [('bar.html', 'text/html',
  228                       '<html><body>bar &gt;</body></html>\n'),
  229                    ('foo.pdf', 'application/pdf', b'foo\n')], False),
  230                             convert_html_with='dehtml')
  231 
  232         self.TestExtraction("""
  233 multipart/alternative
  234     text/html
  235     application/pdf""",
  236                   (None,
  237                    [('bar.html', 'text/html',
  238                       '<html><body>bar &gt;</body></html>\n'),
  239                     ('foo.pdf', 'application/pdf', b'foo\n')], False),
  240                             convert_html_with=False)
  241 
  242     def testMultipartAlternativeHtmlText(self):
  243         # text should take priority over html when html is first
  244         self.TestExtraction("""
  245 multipart/alternative
  246     text/html
  247     text/plain
  248     application/pdf""",
  249                   ('foo\n',
  250                    [('bar.html', 'text/html',
  251                       '<html><body>bar &gt;</body></html>\n'),
  252                     ('foo.pdf', 'application/pdf', b'foo\n')], False),
  253                             convert_html_with='dehtml')
  254 
  255         # text should take priority over html when text is first
  256         self.TestExtraction("""
  257 multipart/alternative
  258     text/plain
  259     text/html
  260     application/pdf""",
  261                   ('foo\n',
  262                    [('bar.html', 'text/html',
  263                       '<html><body>bar &gt;</body></html>\n'),
  264                     ('foo.pdf', 'application/pdf', b'foo\n')], False),
  265                             convert_html_with='dehtml')
  266 
  267         # text should take priority over html when text is second and
  268         # html is disabled
  269         self.TestExtraction("""
  270 multipart/alternative
  271     text/html
  272     text/plain
  273     application/pdf""",
  274                   ('foo\n',
  275                    [('bar.html', 'text/html',
  276                       '<html><body>bar &gt;</body></html>\n'),
  277                     ('foo.pdf', 'application/pdf', b'foo\n')], False),
  278                             convert_html_with=False)
  279 
  280         # text should take priority over html when text is first and
  281         # html is disabled
  282         self.TestExtraction("""
  283 multipart/alternative
  284     text/plain
  285     text/html
  286     application/pdf""",
  287                   ('foo\n',
  288                    [('bar.html', 'text/html',
  289                       '<html><body>bar &gt;</body></html>\n'),
  290                     ('foo.pdf', 'application/pdf', b'foo\n')], False),
  291                             convert_html_with=False)
  292 
  293     def testDeepMultipartAlternative(self):
  294         self.TestExtraction("""
  295 multipart/mixed
  296     multipart/alternative
  297         text/plain
  298         application/pdf
  299         """, ('foo\n', [('foo.pdf', 'application/pdf', b'foo\n')], False))
  300 
  301     def testSignedText(self):
  302         self.TestExtraction("""
  303 multipart/signed
  304     text/plain
  305     application/pgp-signature""", ('foo\n', [], False))
  306 
  307     def testSignedAttachments(self):
  308         self.TestExtraction("""
  309 multipart/signed
  310     multipart/mixed
  311         text/plain
  312         application/pdf
  313     application/pgp-signature""",
  314                   ('foo\n',
  315                    [('foo.pdf', 'application/pdf', b'foo\n')], False))
  316 
  317     def testAttachedSignature(self):
  318         self.TestExtraction("""
  319 multipart/mixed
  320     text/plain
  321     application/pgp-signature""",
  322                   ('foo\n',
  323                    [('foo.gpg', 'application/pgp-signature', b'foo\n')], False))
  324 
  325     def testMessageRfc822(self):
  326         self.TestExtraction("""
  327 multipart/mixed
  328     message/rfc822""",
  329                   (None,
  330                    [('foo.eml', 'message/rfc822', 'Subject: foo\n\nfoo\n')], False))
  331 
  332 # vim: set filetype=python ts=4 sw=4 et si