symbian-qemu-0.9.1-12/python-2.6.1/Lib/email/test/test_email.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 # Copyright (C) 2001-2007 Python Software Foundation
       
     2 # Contact: email-sig@python.org
       
     3 # email package unit tests
       
     4 
       
     5 import os
       
     6 import sys
       
     7 import time
       
     8 import base64
       
     9 import difflib
       
    10 import unittest
       
    11 import warnings
       
    12 from cStringIO import StringIO
       
    13 
       
    14 import email
       
    15 
       
    16 from email.Charset import Charset
       
    17 from email.Header import Header, decode_header, make_header
       
    18 from email.Parser import Parser, HeaderParser
       
    19 from email.Generator import Generator, DecodedGenerator
       
    20 from email.Message import Message
       
    21 from email.MIMEAudio import MIMEAudio
       
    22 from email.MIMEText import MIMEText
       
    23 from email.MIMEImage import MIMEImage
       
    24 from email.MIMEBase import MIMEBase
       
    25 from email.MIMEMessage import MIMEMessage
       
    26 from email.MIMEMultipart import MIMEMultipart
       
    27 from email import Utils
       
    28 from email import Errors
       
    29 from email import Encoders
       
    30 from email import Iterators
       
    31 from email import base64MIME
       
    32 from email import quopriMIME
       
    33 
       
    34 from test.test_support import findfile, run_unittest
       
    35 from email.test import __file__ as landmark
       
    36 
       
    37 
       
    38 NL = '\n'
       
    39 EMPTYSTRING = ''
       
    40 SPACE = ' '
       
    41 
       
    42 
       
    43 
       
    44 def openfile(filename, mode='r'):
       
    45     path = os.path.join(os.path.dirname(landmark), 'data', filename)
       
    46     return open(path, mode)
       
    47 
       
    48 
       
    49 
       
    50 # Base test class
       
    51 class TestEmailBase(unittest.TestCase):
       
    52     def ndiffAssertEqual(self, first, second):
       
    53         """Like failUnlessEqual except use ndiff for readable output."""
       
    54         if first <> second:
       
    55             sfirst = str(first)
       
    56             ssecond = str(second)
       
    57             diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
       
    58             fp = StringIO()
       
    59             print >> fp, NL, NL.join(diff)
       
    60             raise self.failureException, fp.getvalue()
       
    61 
       
    62     def _msgobj(self, filename):
       
    63         fp = openfile(findfile(filename))
       
    64         try:
       
    65             msg = email.message_from_file(fp)
       
    66         finally:
       
    67             fp.close()
       
    68         return msg
       
    69 
       
    70 
       
    71 
       
    72 # Test various aspects of the Message class's API
       
    73 class TestMessageAPI(TestEmailBase):
       
    74     def test_get_all(self):
       
    75         eq = self.assertEqual
       
    76         msg = self._msgobj('msg_20.txt')
       
    77         eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
       
    78         eq(msg.get_all('xx', 'n/a'), 'n/a')
       
    79 
       
    80     def test_getset_charset(self):
       
    81         eq = self.assertEqual
       
    82         msg = Message()
       
    83         eq(msg.get_charset(), None)
       
    84         charset = Charset('iso-8859-1')
       
    85         msg.set_charset(charset)
       
    86         eq(msg['mime-version'], '1.0')
       
    87         eq(msg.get_content_type(), 'text/plain')
       
    88         eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
       
    89         eq(msg.get_param('charset'), 'iso-8859-1')
       
    90         eq(msg['content-transfer-encoding'], 'quoted-printable')
       
    91         eq(msg.get_charset().input_charset, 'iso-8859-1')
       
    92         # Remove the charset
       
    93         msg.set_charset(None)
       
    94         eq(msg.get_charset(), None)
       
    95         eq(msg['content-type'], 'text/plain')
       
    96         # Try adding a charset when there's already MIME headers present
       
    97         msg = Message()
       
    98         msg['MIME-Version'] = '2.0'
       
    99         msg['Content-Type'] = 'text/x-weird'
       
   100         msg['Content-Transfer-Encoding'] = 'quinted-puntable'
       
   101         msg.set_charset(charset)
       
   102         eq(msg['mime-version'], '2.0')
       
   103         eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
       
   104         eq(msg['content-transfer-encoding'], 'quinted-puntable')
       
   105 
       
   106     def test_set_charset_from_string(self):
       
   107         eq = self.assertEqual
       
   108         msg = Message()
       
   109         msg.set_charset('us-ascii')
       
   110         eq(msg.get_charset().input_charset, 'us-ascii')
       
   111         eq(msg['content-type'], 'text/plain; charset="us-ascii"')
       
   112 
       
   113     def test_set_payload_with_charset(self):
       
   114         msg = Message()
       
   115         charset = Charset('iso-8859-1')
       
   116         msg.set_payload('This is a string payload', charset)
       
   117         self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
       
   118 
       
   119     def test_get_charsets(self):
       
   120         eq = self.assertEqual
       
   121 
       
   122         msg = self._msgobj('msg_08.txt')
       
   123         charsets = msg.get_charsets()
       
   124         eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
       
   125 
       
   126         msg = self._msgobj('msg_09.txt')
       
   127         charsets = msg.get_charsets('dingbat')
       
   128         eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
       
   129                       'koi8-r'])
       
   130 
       
   131         msg = self._msgobj('msg_12.txt')
       
   132         charsets = msg.get_charsets()
       
   133         eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
       
   134                       'iso-8859-3', 'us-ascii', 'koi8-r'])
       
   135 
       
   136     def test_get_filename(self):
       
   137         eq = self.assertEqual
       
   138 
       
   139         msg = self._msgobj('msg_04.txt')
       
   140         filenames = [p.get_filename() for p in msg.get_payload()]
       
   141         eq(filenames, ['msg.txt', 'msg.txt'])
       
   142 
       
   143         msg = self._msgobj('msg_07.txt')
       
   144         subpart = msg.get_payload(1)
       
   145         eq(subpart.get_filename(), 'dingusfish.gif')
       
   146 
       
   147     def test_get_filename_with_name_parameter(self):
       
   148         eq = self.assertEqual
       
   149 
       
   150         msg = self._msgobj('msg_44.txt')
       
   151         filenames = [p.get_filename() for p in msg.get_payload()]
       
   152         eq(filenames, ['msg.txt', 'msg.txt'])
       
   153 
       
   154     def test_get_boundary(self):
       
   155         eq = self.assertEqual
       
   156         msg = self._msgobj('msg_07.txt')
       
   157         # No quotes!
       
   158         eq(msg.get_boundary(), 'BOUNDARY')
       
   159 
       
   160     def test_set_boundary(self):
       
   161         eq = self.assertEqual
       
   162         # This one has no existing boundary parameter, but the Content-Type:
       
   163         # header appears fifth.
       
   164         msg = self._msgobj('msg_01.txt')
       
   165         msg.set_boundary('BOUNDARY')
       
   166         header, value = msg.items()[4]
       
   167         eq(header.lower(), 'content-type')
       
   168         eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
       
   169         # This one has a Content-Type: header, with a boundary, stuck in the
       
   170         # middle of its headers.  Make sure the order is preserved; it should
       
   171         # be fifth.
       
   172         msg = self._msgobj('msg_04.txt')
       
   173         msg.set_boundary('BOUNDARY')
       
   174         header, value = msg.items()[4]
       
   175         eq(header.lower(), 'content-type')
       
   176         eq(value, 'multipart/mixed; boundary="BOUNDARY"')
       
   177         # And this one has no Content-Type: header at all.
       
   178         msg = self._msgobj('msg_03.txt')
       
   179         self.assertRaises(Errors.HeaderParseError,
       
   180                           msg.set_boundary, 'BOUNDARY')
       
   181 
       
   182     def test_get_decoded_payload(self):
       
   183         eq = self.assertEqual
       
   184         msg = self._msgobj('msg_10.txt')
       
   185         # The outer message is a multipart
       
   186         eq(msg.get_payload(decode=True), None)
       
   187         # Subpart 1 is 7bit encoded
       
   188         eq(msg.get_payload(0).get_payload(decode=True),
       
   189            'This is a 7bit encoded message.\n')
       
   190         # Subpart 2 is quopri
       
   191         eq(msg.get_payload(1).get_payload(decode=True),
       
   192            '\xa1This is a Quoted Printable encoded message!\n')
       
   193         # Subpart 3 is base64
       
   194         eq(msg.get_payload(2).get_payload(decode=True),
       
   195            'This is a Base64 encoded message.')
       
   196         # Subpart 4 has no Content-Transfer-Encoding: header.
       
   197         eq(msg.get_payload(3).get_payload(decode=True),
       
   198            'This has no Content-Transfer-Encoding: header.\n')
       
   199 
       
   200     def test_get_decoded_uu_payload(self):
       
   201         eq = self.assertEqual
       
   202         msg = Message()
       
   203         msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
       
   204         for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
       
   205             msg['content-transfer-encoding'] = cte
       
   206             eq(msg.get_payload(decode=True), 'hello world')
       
   207         # Now try some bogus data
       
   208         msg.set_payload('foo')
       
   209         eq(msg.get_payload(decode=True), 'foo')
       
   210 
       
   211     def test_decode_bogus_uu_payload_quietly(self):
       
   212         msg = Message()
       
   213         msg.set_payload('begin 664 foo.txt\n%<W1F=0000H \n \nend\n')
       
   214         msg['Content-Transfer-Encoding'] = 'x-uuencode'
       
   215         old_stderr = sys.stderr
       
   216         try:
       
   217             sys.stderr = sfp = StringIO()
       
   218             # We don't care about the payload
       
   219             msg.get_payload(decode=True)
       
   220         finally:
       
   221             sys.stderr = old_stderr
       
   222         self.assertEqual(sfp.getvalue(), '')
       
   223 
       
   224     def test_decoded_generator(self):
       
   225         eq = self.assertEqual
       
   226         msg = self._msgobj('msg_07.txt')
       
   227         fp = openfile('msg_17.txt')
       
   228         try:
       
   229             text = fp.read()
       
   230         finally:
       
   231             fp.close()
       
   232         s = StringIO()
       
   233         g = DecodedGenerator(s)
       
   234         g.flatten(msg)
       
   235         eq(s.getvalue(), text)
       
   236 
       
   237     def test__contains__(self):
       
   238         msg = Message()
       
   239         msg['From'] = 'Me'
       
   240         msg['to'] = 'You'
       
   241         # Check for case insensitivity
       
   242         self.failUnless('from' in msg)
       
   243         self.failUnless('From' in msg)
       
   244         self.failUnless('FROM' in msg)
       
   245         self.failUnless('to' in msg)
       
   246         self.failUnless('To' in msg)
       
   247         self.failUnless('TO' in msg)
       
   248 
       
   249     def test_as_string(self):
       
   250         eq = self.assertEqual
       
   251         msg = self._msgobj('msg_01.txt')
       
   252         fp = openfile('msg_01.txt')
       
   253         try:
       
   254             text = fp.read()
       
   255         finally:
       
   256             fp.close()
       
   257         eq(text, msg.as_string())
       
   258         fullrepr = str(msg)
       
   259         lines = fullrepr.split('\n')
       
   260         self.failUnless(lines[0].startswith('From '))
       
   261         eq(text, NL.join(lines[1:]))
       
   262 
       
   263     def test_bad_param(self):
       
   264         msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
       
   265         self.assertEqual(msg.get_param('baz'), '')
       
   266 
       
   267     def test_missing_filename(self):
       
   268         msg = email.message_from_string("From: foo\n")
       
   269         self.assertEqual(msg.get_filename(), None)
       
   270 
       
   271     def test_bogus_filename(self):
       
   272         msg = email.message_from_string(
       
   273         "Content-Disposition: blarg; filename\n")
       
   274         self.assertEqual(msg.get_filename(), '')
       
   275 
       
   276     def test_missing_boundary(self):
       
   277         msg = email.message_from_string("From: foo\n")
       
   278         self.assertEqual(msg.get_boundary(), None)
       
   279 
       
   280     def test_get_params(self):
       
   281         eq = self.assertEqual
       
   282         msg = email.message_from_string(
       
   283             'X-Header: foo=one; bar=two; baz=three\n')
       
   284         eq(msg.get_params(header='x-header'),
       
   285            [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
       
   286         msg = email.message_from_string(
       
   287             'X-Header: foo; bar=one; baz=two\n')
       
   288         eq(msg.get_params(header='x-header'),
       
   289            [('foo', ''), ('bar', 'one'), ('baz', 'two')])
       
   290         eq(msg.get_params(), None)
       
   291         msg = email.message_from_string(
       
   292             'X-Header: foo; bar="one"; baz=two\n')
       
   293         eq(msg.get_params(header='x-header'),
       
   294            [('foo', ''), ('bar', 'one'), ('baz', 'two')])
       
   295 
       
   296     def test_get_param_liberal(self):
       
   297         msg = Message()
       
   298         msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
       
   299         self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
       
   300 
       
   301     def test_get_param(self):
       
   302         eq = self.assertEqual
       
   303         msg = email.message_from_string(
       
   304             "X-Header: foo=one; bar=two; baz=three\n")
       
   305         eq(msg.get_param('bar', header='x-header'), 'two')
       
   306         eq(msg.get_param('quuz', header='x-header'), None)
       
   307         eq(msg.get_param('quuz'), None)
       
   308         msg = email.message_from_string(
       
   309             'X-Header: foo; bar="one"; baz=two\n')
       
   310         eq(msg.get_param('foo', header='x-header'), '')
       
   311         eq(msg.get_param('bar', header='x-header'), 'one')
       
   312         eq(msg.get_param('baz', header='x-header'), 'two')
       
   313         # XXX: We are not RFC-2045 compliant!  We cannot parse:
       
   314         # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
       
   315         # msg.get_param("weird")
       
   316         # yet.
       
   317 
       
   318     def test_get_param_funky_continuation_lines(self):
       
   319         msg = self._msgobj('msg_22.txt')
       
   320         self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
       
   321 
       
   322     def test_get_param_with_semis_in_quotes(self):
       
   323         msg = email.message_from_string(
       
   324             'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
       
   325         self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
       
   326         self.assertEqual(msg.get_param('name', unquote=False),
       
   327                          '"Jim&amp;&amp;Jill"')
       
   328 
       
   329     def test_has_key(self):
       
   330         msg = email.message_from_string('Header: exists')
       
   331         self.failUnless(msg.has_key('header'))
       
   332         self.failUnless(msg.has_key('Header'))
       
   333         self.failUnless(msg.has_key('HEADER'))
       
   334         self.failIf(msg.has_key('headeri'))
       
   335 
       
   336     def test_set_param(self):
       
   337         eq = self.assertEqual
       
   338         msg = Message()
       
   339         msg.set_param('charset', 'iso-2022-jp')
       
   340         eq(msg.get_param('charset'), 'iso-2022-jp')
       
   341         msg.set_param('importance', 'high value')
       
   342         eq(msg.get_param('importance'), 'high value')
       
   343         eq(msg.get_param('importance', unquote=False), '"high value"')
       
   344         eq(msg.get_params(), [('text/plain', ''),
       
   345                               ('charset', 'iso-2022-jp'),
       
   346                               ('importance', 'high value')])
       
   347         eq(msg.get_params(unquote=False), [('text/plain', ''),
       
   348                                        ('charset', '"iso-2022-jp"'),
       
   349                                        ('importance', '"high value"')])
       
   350         msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
       
   351         eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
       
   352 
       
   353     def test_del_param(self):
       
   354         eq = self.assertEqual
       
   355         msg = self._msgobj('msg_05.txt')
       
   356         eq(msg.get_params(),
       
   357            [('multipart/report', ''), ('report-type', 'delivery-status'),
       
   358             ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
       
   359         old_val = msg.get_param("report-type")
       
   360         msg.del_param("report-type")
       
   361         eq(msg.get_params(),
       
   362            [('multipart/report', ''),
       
   363             ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
       
   364         msg.set_param("report-type", old_val)
       
   365         eq(msg.get_params(),
       
   366            [('multipart/report', ''),
       
   367             ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
       
   368             ('report-type', old_val)])
       
   369 
       
   370     def test_del_param_on_other_header(self):
       
   371         msg = Message()
       
   372         msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
       
   373         msg.del_param('filename', 'content-disposition')
       
   374         self.assertEqual(msg['content-disposition'], 'attachment')
       
   375 
       
   376     def test_set_type(self):
       
   377         eq = self.assertEqual
       
   378         msg = Message()
       
   379         self.assertRaises(ValueError, msg.set_type, 'text')
       
   380         msg.set_type('text/plain')
       
   381         eq(msg['content-type'], 'text/plain')
       
   382         msg.set_param('charset', 'us-ascii')
       
   383         eq(msg['content-type'], 'text/plain; charset="us-ascii"')
       
   384         msg.set_type('text/html')
       
   385         eq(msg['content-type'], 'text/html; charset="us-ascii"')
       
   386 
       
   387     def test_set_type_on_other_header(self):
       
   388         msg = Message()
       
   389         msg['X-Content-Type'] = 'text/plain'
       
   390         msg.set_type('application/octet-stream', 'X-Content-Type')
       
   391         self.assertEqual(msg['x-content-type'], 'application/octet-stream')
       
   392 
       
   393     def test_get_content_type_missing(self):
       
   394         msg = Message()
       
   395         self.assertEqual(msg.get_content_type(), 'text/plain')
       
   396 
       
   397     def test_get_content_type_missing_with_default_type(self):
       
   398         msg = Message()
       
   399         msg.set_default_type('message/rfc822')
       
   400         self.assertEqual(msg.get_content_type(), 'message/rfc822')
       
   401 
       
   402     def test_get_content_type_from_message_implicit(self):
       
   403         msg = self._msgobj('msg_30.txt')
       
   404         self.assertEqual(msg.get_payload(0).get_content_type(),
       
   405                          'message/rfc822')
       
   406 
       
   407     def test_get_content_type_from_message_explicit(self):
       
   408         msg = self._msgobj('msg_28.txt')
       
   409         self.assertEqual(msg.get_payload(0).get_content_type(),
       
   410                          'message/rfc822')
       
   411 
       
   412     def test_get_content_type_from_message_text_plain_implicit(self):
       
   413         msg = self._msgobj('msg_03.txt')
       
   414         self.assertEqual(msg.get_content_type(), 'text/plain')
       
   415 
       
   416     def test_get_content_type_from_message_text_plain_explicit(self):
       
   417         msg = self._msgobj('msg_01.txt')
       
   418         self.assertEqual(msg.get_content_type(), 'text/plain')
       
   419 
       
   420     def test_get_content_maintype_missing(self):
       
   421         msg = Message()
       
   422         self.assertEqual(msg.get_content_maintype(), 'text')
       
   423 
       
   424     def test_get_content_maintype_missing_with_default_type(self):
       
   425         msg = Message()
       
   426         msg.set_default_type('message/rfc822')
       
   427         self.assertEqual(msg.get_content_maintype(), 'message')
       
   428 
       
   429     def test_get_content_maintype_from_message_implicit(self):
       
   430         msg = self._msgobj('msg_30.txt')
       
   431         self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
       
   432 
       
   433     def test_get_content_maintype_from_message_explicit(self):
       
   434         msg = self._msgobj('msg_28.txt')
       
   435         self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
       
   436 
       
   437     def test_get_content_maintype_from_message_text_plain_implicit(self):
       
   438         msg = self._msgobj('msg_03.txt')
       
   439         self.assertEqual(msg.get_content_maintype(), 'text')
       
   440 
       
   441     def test_get_content_maintype_from_message_text_plain_explicit(self):
       
   442         msg = self._msgobj('msg_01.txt')
       
   443         self.assertEqual(msg.get_content_maintype(), 'text')
       
   444 
       
   445     def test_get_content_subtype_missing(self):
       
   446         msg = Message()
       
   447         self.assertEqual(msg.get_content_subtype(), 'plain')
       
   448 
       
   449     def test_get_content_subtype_missing_with_default_type(self):
       
   450         msg = Message()
       
   451         msg.set_default_type('message/rfc822')
       
   452         self.assertEqual(msg.get_content_subtype(), 'rfc822')
       
   453 
       
   454     def test_get_content_subtype_from_message_implicit(self):
       
   455         msg = self._msgobj('msg_30.txt')
       
   456         self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
       
   457 
       
   458     def test_get_content_subtype_from_message_explicit(self):
       
   459         msg = self._msgobj('msg_28.txt')
       
   460         self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
       
   461 
       
   462     def test_get_content_subtype_from_message_text_plain_implicit(self):
       
   463         msg = self._msgobj('msg_03.txt')
       
   464         self.assertEqual(msg.get_content_subtype(), 'plain')
       
   465 
       
   466     def test_get_content_subtype_from_message_text_plain_explicit(self):
       
   467         msg = self._msgobj('msg_01.txt')
       
   468         self.assertEqual(msg.get_content_subtype(), 'plain')
       
   469 
       
   470     def test_get_content_maintype_error(self):
       
   471         msg = Message()
       
   472         msg['Content-Type'] = 'no-slash-in-this-string'
       
   473         self.assertEqual(msg.get_content_maintype(), 'text')
       
   474 
       
   475     def test_get_content_subtype_error(self):
       
   476         msg = Message()
       
   477         msg['Content-Type'] = 'no-slash-in-this-string'
       
   478         self.assertEqual(msg.get_content_subtype(), 'plain')
       
   479 
       
   480     def test_replace_header(self):
       
   481         eq = self.assertEqual
       
   482         msg = Message()
       
   483         msg.add_header('First', 'One')
       
   484         msg.add_header('Second', 'Two')
       
   485         msg.add_header('Third', 'Three')
       
   486         eq(msg.keys(), ['First', 'Second', 'Third'])
       
   487         eq(msg.values(), ['One', 'Two', 'Three'])
       
   488         msg.replace_header('Second', 'Twenty')
       
   489         eq(msg.keys(), ['First', 'Second', 'Third'])
       
   490         eq(msg.values(), ['One', 'Twenty', 'Three'])
       
   491         msg.add_header('First', 'Eleven')
       
   492         msg.replace_header('First', 'One Hundred')
       
   493         eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
       
   494         eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
       
   495         self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
       
   496 
       
   497     def test_broken_base64_payload(self):
       
   498         x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
       
   499         msg = Message()
       
   500         msg['content-type'] = 'audio/x-midi'
       
   501         msg['content-transfer-encoding'] = 'base64'
       
   502         msg.set_payload(x)
       
   503         self.assertEqual(msg.get_payload(decode=True), x)
       
   504 
       
   505     def test_get_content_charset(self):
       
   506         msg = Message()
       
   507         msg.set_charset('us-ascii')
       
   508         self.assertEqual('us-ascii', msg.get_content_charset())
       
   509         msg.set_charset(u'us-ascii')
       
   510         self.assertEqual('us-ascii', msg.get_content_charset())
       
   511 
       
   512 
       
   513 
       
   514 # Test the email.Encoders module
       
   515 class TestEncoders(unittest.TestCase):
       
   516     def test_encode_empty_payload(self):
       
   517         eq = self.assertEqual
       
   518         msg = Message()
       
   519         msg.set_charset('us-ascii')
       
   520         eq(msg['content-transfer-encoding'], '7bit')
       
   521 
       
   522     def test_default_cte(self):
       
   523         eq = self.assertEqual
       
   524         msg = MIMEText('hello world')
       
   525         eq(msg['content-transfer-encoding'], '7bit')
       
   526 
       
   527     def test_default_cte(self):
       
   528         eq = self.assertEqual
       
   529         # With no explicit _charset its us-ascii, and all are 7-bit
       
   530         msg = MIMEText('hello world')
       
   531         eq(msg['content-transfer-encoding'], '7bit')
       
   532         # Similar, but with 8-bit data
       
   533         msg = MIMEText('hello \xf8 world')
       
   534         eq(msg['content-transfer-encoding'], '8bit')
       
   535         # And now with a different charset
       
   536         msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
       
   537         eq(msg['content-transfer-encoding'], 'quoted-printable')
       
   538 
       
   539 
       
   540 
       
   541 # Test long header wrapping
       
   542 class TestLongHeaders(TestEmailBase):
       
   543     def test_split_long_continuation(self):
       
   544         eq = self.ndiffAssertEqual
       
   545         msg = email.message_from_string("""\
       
   546 Subject: bug demonstration
       
   547 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
       
   548 \tmore text
       
   549 
       
   550 test
       
   551 """)
       
   552         sfp = StringIO()
       
   553         g = Generator(sfp)
       
   554         g.flatten(msg)
       
   555         eq(sfp.getvalue(), """\
       
   556 Subject: bug demonstration
       
   557 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
       
   558 \tmore text
       
   559 
       
   560 test
       
   561 """)
       
   562 
       
   563     def test_another_long_almost_unsplittable_header(self):
       
   564         eq = self.ndiffAssertEqual
       
   565         hstr = """\
       
   566 bug demonstration
       
   567 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
       
   568 \tmore text"""
       
   569         h = Header(hstr, continuation_ws='\t')
       
   570         eq(h.encode(), """\
       
   571 bug demonstration
       
   572 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
       
   573 \tmore text""")
       
   574         h = Header(hstr)
       
   575         eq(h.encode(), """\
       
   576 bug demonstration
       
   577  12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
       
   578  more text""")
       
   579 
       
   580     def test_long_nonstring(self):
       
   581         eq = self.ndiffAssertEqual
       
   582         g = Charset("iso-8859-1")
       
   583         cz = Charset("iso-8859-2")
       
   584         utf8 = Charset("utf-8")
       
   585         g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
       
   586         cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
       
   587         utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
       
   588         h = Header(g_head, g, header_name='Subject')
       
   589         h.append(cz_head, cz)
       
   590         h.append(utf8_head, utf8)
       
   591         msg = Message()
       
   592         msg['Subject'] = h
       
   593         sfp = StringIO()
       
   594         g = Generator(sfp)
       
   595         g.flatten(msg)
       
   596         eq(sfp.getvalue(), """\
       
   597 Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
       
   598  =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
       
   599  =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
       
   600  =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
       
   601  =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
       
   602  =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
       
   603  =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
       
   604  =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
       
   605  =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
       
   606  =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
       
   607  =?utf-8?b?44Gm44GE44G+44GZ44CC?=
       
   608 
       
   609 """)
       
   610         eq(h.encode(), """\
       
   611 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
       
   612  =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
       
   613  =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
       
   614  =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
       
   615  =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
       
   616  =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
       
   617  =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
       
   618  =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
       
   619  =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
       
   620  =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
       
   621  =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
       
   622 
       
   623     def test_long_header_encode(self):
       
   624         eq = self.ndiffAssertEqual
       
   625         h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
       
   626                    'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
       
   627                    header_name='X-Foobar-Spoink-Defrobnit')
       
   628         eq(h.encode(), '''\
       
   629 wasnipoop; giraffes="very-long-necked-animals";
       
   630  spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
       
   631 
       
   632     def test_long_header_encode_with_tab_continuation(self):
       
   633         eq = self.ndiffAssertEqual
       
   634         h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
       
   635                    'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
       
   636                    header_name='X-Foobar-Spoink-Defrobnit',
       
   637                    continuation_ws='\t')
       
   638         eq(h.encode(), '''\
       
   639 wasnipoop; giraffes="very-long-necked-animals";
       
   640 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
       
   641 
       
   642     def test_header_splitter(self):
       
   643         eq = self.ndiffAssertEqual
       
   644         msg = MIMEText('')
       
   645         # It'd be great if we could use add_header() here, but that doesn't
       
   646         # guarantee an order of the parameters.
       
   647         msg['X-Foobar-Spoink-Defrobnit'] = (
       
   648             'wasnipoop; giraffes="very-long-necked-animals"; '
       
   649             'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
       
   650         sfp = StringIO()
       
   651         g = Generator(sfp)
       
   652         g.flatten(msg)
       
   653         eq(sfp.getvalue(), '''\
       
   654 Content-Type: text/plain; charset="us-ascii"
       
   655 MIME-Version: 1.0
       
   656 Content-Transfer-Encoding: 7bit
       
   657 X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
       
   658 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
       
   659 
       
   660 ''')
       
   661 
       
   662     def test_no_semis_header_splitter(self):
       
   663         eq = self.ndiffAssertEqual
       
   664         msg = Message()
       
   665         msg['From'] = 'test@dom.ain'
       
   666         msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
       
   667         msg.set_payload('Test')
       
   668         sfp = StringIO()
       
   669         g = Generator(sfp)
       
   670         g.flatten(msg)
       
   671         eq(sfp.getvalue(), """\
       
   672 From: test@dom.ain
       
   673 References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
       
   674 \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
       
   675 
       
   676 Test""")
       
   677 
       
   678     def test_no_split_long_header(self):
       
   679         eq = self.ndiffAssertEqual
       
   680         hstr = 'References: ' + 'x' * 80
       
   681         h = Header(hstr, continuation_ws='\t')
       
   682         eq(h.encode(), """\
       
   683 References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
       
   684 
       
   685     def test_splitting_multiple_long_lines(self):
       
   686         eq = self.ndiffAssertEqual
       
   687         hstr = """\
       
   688 from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
       
   689 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
       
   690 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
       
   691 """
       
   692         h = Header(hstr, continuation_ws='\t')
       
   693         eq(h.encode(), """\
       
   694 from babylon.socal-raves.org (localhost [127.0.0.1]);
       
   695 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
       
   696 \tfor <mailman-admin@babylon.socal-raves.org>;
       
   697 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
       
   698 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
       
   699 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
       
   700 \tfor <mailman-admin@babylon.socal-raves.org>;
       
   701 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
       
   702 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
       
   703 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
       
   704 \tfor <mailman-admin@babylon.socal-raves.org>;
       
   705 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
       
   706 
       
   707     def test_splitting_first_line_only_is_long(self):
       
   708         eq = self.ndiffAssertEqual
       
   709         hstr = """\
       
   710 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
       
   711 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
       
   712 \tid 17k4h5-00034i-00
       
   713 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
       
   714         h = Header(hstr, maxlinelen=78, header_name='Received',
       
   715                    continuation_ws='\t')
       
   716         eq(h.encode(), """\
       
   717 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
       
   718 \thelo=cthulhu.gerg.ca)
       
   719 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
       
   720 \tid 17k4h5-00034i-00
       
   721 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
       
   722 
       
   723     def test_long_8bit_header(self):
       
   724         eq = self.ndiffAssertEqual
       
   725         msg = Message()
       
   726         h = Header('Britische Regierung gibt', 'iso-8859-1',
       
   727                     header_name='Subject')
       
   728         h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
       
   729         msg['Subject'] = h
       
   730         eq(msg.as_string(), """\
       
   731 Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
       
   732  =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
       
   733 
       
   734 """)
       
   735 
       
   736     def test_long_8bit_header_no_charset(self):
       
   737         eq = self.ndiffAssertEqual
       
   738         msg = Message()
       
   739         msg['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
       
   740         eq(msg.as_string(), """\
       
   741 Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
       
   742 
       
   743 """)
       
   744 
       
   745     def test_long_to_header(self):
       
   746         eq = self.ndiffAssertEqual
       
   747         to = '"Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,"Someone Test #B" <someone@umich.edu>, "Someone Test #C" <someone@eecs.umich.edu>, "Someone Test #D" <someone@eecs.umich.edu>'
       
   748         msg = Message()
       
   749         msg['To'] = to
       
   750         eq(msg.as_string(0), '''\
       
   751 To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
       
   752 \t"Someone Test #B" <someone@umich.edu>,
       
   753 \t"Someone Test #C" <someone@eecs.umich.edu>,
       
   754 \t"Someone Test #D" <someone@eecs.umich.edu>
       
   755 
       
   756 ''')
       
   757 
       
   758     def test_long_line_after_append(self):
       
   759         eq = self.ndiffAssertEqual
       
   760         s = 'This is an example of string which has almost the limit of header length.'
       
   761         h = Header(s)
       
   762         h.append('Add another line.')
       
   763         eq(h.encode(), """\
       
   764 This is an example of string which has almost the limit of header length.
       
   765  Add another line.""")
       
   766 
       
   767     def test_shorter_line_with_append(self):
       
   768         eq = self.ndiffAssertEqual
       
   769         s = 'This is a shorter line.'
       
   770         h = Header(s)
       
   771         h.append('Add another sentence. (Surprise?)')
       
   772         eq(h.encode(),
       
   773            'This is a shorter line. Add another sentence. (Surprise?)')
       
   774 
       
   775     def test_long_field_name(self):
       
   776         eq = self.ndiffAssertEqual
       
   777         fn = 'X-Very-Very-Very-Long-Header-Name'
       
   778         gs = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
       
   779         h = Header(gs, 'iso-8859-1', header_name=fn)
       
   780         # BAW: this seems broken because the first line is too long
       
   781         eq(h.encode(), """\
       
   782 =?iso-8859-1?q?Die_Mieter_treten_hier_?=
       
   783  =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
       
   784  =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
       
   785  =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
       
   786 
       
   787     def test_long_received_header(self):
       
   788         h = 'from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; Wed, 05 Mar 2003 18:10:18 -0700'
       
   789         msg = Message()
       
   790         msg['Received-1'] = Header(h, continuation_ws='\t')
       
   791         msg['Received-2'] = h
       
   792         self.assertEqual(msg.as_string(), """\
       
   793 Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
       
   794 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
       
   795 \tWed, 05 Mar 2003 18:10:18 -0700
       
   796 Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
       
   797 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
       
   798 \tWed, 05 Mar 2003 18:10:18 -0700
       
   799 
       
   800 """)
       
   801 
       
   802     def test_string_headerinst_eq(self):
       
   803         h = '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
       
   804         msg = Message()
       
   805         msg['Received-1'] = Header(h, header_name='Received-1',
       
   806                                    continuation_ws='\t')
       
   807         msg['Received-2'] = h
       
   808         self.assertEqual(msg.as_string(), """\
       
   809 Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
       
   810 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
       
   811 Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
       
   812 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
       
   813 
       
   814 """)
       
   815 
       
   816     def test_long_unbreakable_lines_with_continuation(self):
       
   817         eq = self.ndiffAssertEqual
       
   818         msg = Message()
       
   819         t = """\
       
   820  iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
       
   821  locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
       
   822         msg['Face-1'] = t
       
   823         msg['Face-2'] = Header(t, header_name='Face-2')
       
   824         eq(msg.as_string(), """\
       
   825 Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
       
   826 \tlocQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
       
   827 Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
       
   828  locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
       
   829 
       
   830 """)
       
   831 
       
   832     def test_another_long_multiline_header(self):
       
   833         eq = self.ndiffAssertEqual
       
   834         m = '''\
       
   835 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
       
   836 \tWed, 16 Oct 2002 07:41:11 -0700'''
       
   837         msg = email.message_from_string(m)
       
   838         eq(msg.as_string(), '''\
       
   839 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
       
   840 \tMicrosoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
       
   841 
       
   842 ''')
       
   843 
       
   844     def test_long_lines_with_different_header(self):
       
   845         eq = self.ndiffAssertEqual
       
   846         h = """\
       
   847 List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
       
   848         <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
       
   849         msg = Message()
       
   850         msg['List'] = h
       
   851         msg['List'] = Header(h, header_name='List')
       
   852         eq(msg.as_string(), """\
       
   853 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
       
   854 \t<mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
       
   855 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
       
   856  <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
       
   857 
       
   858 """)
       
   859 
       
   860 
       
   861 
       
   862 # Test mangling of "From " lines in the body of a message
       
   863 class TestFromMangling(unittest.TestCase):
       
   864     def setUp(self):
       
   865         self.msg = Message()
       
   866         self.msg['From'] = 'aaa@bbb.org'
       
   867         self.msg.set_payload("""\
       
   868 From the desk of A.A.A.:
       
   869 Blah blah blah
       
   870 """)
       
   871 
       
   872     def test_mangled_from(self):
       
   873         s = StringIO()
       
   874         g = Generator(s, mangle_from_=True)
       
   875         g.flatten(self.msg)
       
   876         self.assertEqual(s.getvalue(), """\
       
   877 From: aaa@bbb.org
       
   878 
       
   879 >From the desk of A.A.A.:
       
   880 Blah blah blah
       
   881 """)
       
   882 
       
   883     def test_dont_mangle_from(self):
       
   884         s = StringIO()
       
   885         g = Generator(s, mangle_from_=False)
       
   886         g.flatten(self.msg)
       
   887         self.assertEqual(s.getvalue(), """\
       
   888 From: aaa@bbb.org
       
   889 
       
   890 From the desk of A.A.A.:
       
   891 Blah blah blah
       
   892 """)
       
   893 
       
   894 
       
   895 
       
   896 # Test the basic MIMEAudio class
       
   897 class TestMIMEAudio(unittest.TestCase):
       
   898     def setUp(self):
       
   899         # Make sure we pick up the audiotest.au that lives in email/test/data.
       
   900         # In Python, there's an audiotest.au living in Lib/test but that isn't
       
   901         # included in some binary distros that don't include the test
       
   902         # package.  The trailing empty string on the .join() is significant
       
   903         # since findfile() will do a dirname().
       
   904         datadir = os.path.join(os.path.dirname(landmark), 'data', '')
       
   905         fp = open(findfile('audiotest.au', datadir), 'rb')
       
   906         try:
       
   907             self._audiodata = fp.read()
       
   908         finally:
       
   909             fp.close()
       
   910         self._au = MIMEAudio(self._audiodata)
       
   911 
       
   912     def test_guess_minor_type(self):
       
   913         self.assertEqual(self._au.get_content_type(), 'audio/basic')
       
   914 
       
   915     def test_encoding(self):
       
   916         payload = self._au.get_payload()
       
   917         self.assertEqual(base64.decodestring(payload), self._audiodata)
       
   918 
       
   919     def test_checkSetMinor(self):
       
   920         au = MIMEAudio(self._audiodata, 'fish')
       
   921         self.assertEqual(au.get_content_type(), 'audio/fish')
       
   922 
       
   923     def test_add_header(self):
       
   924         eq = self.assertEqual
       
   925         unless = self.failUnless
       
   926         self._au.add_header('Content-Disposition', 'attachment',
       
   927                             filename='audiotest.au')
       
   928         eq(self._au['content-disposition'],
       
   929            'attachment; filename="audiotest.au"')
       
   930         eq(self._au.get_params(header='content-disposition'),
       
   931            [('attachment', ''), ('filename', 'audiotest.au')])
       
   932         eq(self._au.get_param('filename', header='content-disposition'),
       
   933            'audiotest.au')
       
   934         missing = []
       
   935         eq(self._au.get_param('attachment', header='content-disposition'), '')
       
   936         unless(self._au.get_param('foo', failobj=missing,
       
   937                                   header='content-disposition') is missing)
       
   938         # Try some missing stuff
       
   939         unless(self._au.get_param('foobar', missing) is missing)
       
   940         unless(self._au.get_param('attachment', missing,
       
   941                                   header='foobar') is missing)
       
   942 
       
   943 
       
   944 
       
   945 # Test the basic MIMEImage class
       
   946 class TestMIMEImage(unittest.TestCase):
       
   947     def setUp(self):
       
   948         fp = openfile('PyBanner048.gif')
       
   949         try:
       
   950             self._imgdata = fp.read()
       
   951         finally:
       
   952             fp.close()
       
   953         self._im = MIMEImage(self._imgdata)
       
   954 
       
   955     def test_guess_minor_type(self):
       
   956         self.assertEqual(self._im.get_content_type(), 'image/gif')
       
   957 
       
   958     def test_encoding(self):
       
   959         payload = self._im.get_payload()
       
   960         self.assertEqual(base64.decodestring(payload), self._imgdata)
       
   961 
       
   962     def test_checkSetMinor(self):
       
   963         im = MIMEImage(self._imgdata, 'fish')
       
   964         self.assertEqual(im.get_content_type(), 'image/fish')
       
   965 
       
   966     def test_add_header(self):
       
   967         eq = self.assertEqual
       
   968         unless = self.failUnless
       
   969         self._im.add_header('Content-Disposition', 'attachment',
       
   970                             filename='dingusfish.gif')
       
   971         eq(self._im['content-disposition'],
       
   972            'attachment; filename="dingusfish.gif"')
       
   973         eq(self._im.get_params(header='content-disposition'),
       
   974            [('attachment', ''), ('filename', 'dingusfish.gif')])
       
   975         eq(self._im.get_param('filename', header='content-disposition'),
       
   976            'dingusfish.gif')
       
   977         missing = []
       
   978         eq(self._im.get_param('attachment', header='content-disposition'), '')
       
   979         unless(self._im.get_param('foo', failobj=missing,
       
   980                                   header='content-disposition') is missing)
       
   981         # Try some missing stuff
       
   982         unless(self._im.get_param('foobar', missing) is missing)
       
   983         unless(self._im.get_param('attachment', missing,
       
   984                                   header='foobar') is missing)
       
   985 
       
   986 
       
   987 
       
   988 # Test the basic MIMEText class
       
   989 class TestMIMEText(unittest.TestCase):
       
   990     def setUp(self):
       
   991         self._msg = MIMEText('hello there')
       
   992 
       
   993     def test_types(self):
       
   994         eq = self.assertEqual
       
   995         unless = self.failUnless
       
   996         eq(self._msg.get_content_type(), 'text/plain')
       
   997         eq(self._msg.get_param('charset'), 'us-ascii')
       
   998         missing = []
       
   999         unless(self._msg.get_param('foobar', missing) is missing)
       
  1000         unless(self._msg.get_param('charset', missing, header='foobar')
       
  1001                is missing)
       
  1002 
       
  1003     def test_payload(self):
       
  1004         self.assertEqual(self._msg.get_payload(), 'hello there')
       
  1005         self.failUnless(not self._msg.is_multipart())
       
  1006 
       
  1007     def test_charset(self):
       
  1008         eq = self.assertEqual
       
  1009         msg = MIMEText('hello there', _charset='us-ascii')
       
  1010         eq(msg.get_charset().input_charset, 'us-ascii')
       
  1011         eq(msg['content-type'], 'text/plain; charset="us-ascii"')
       
  1012 
       
  1013 
       
  1014 
       
  1015 # Test complicated multipart/* messages
       
  1016 class TestMultipart(TestEmailBase):
       
  1017     def setUp(self):
       
  1018         fp = openfile('PyBanner048.gif')
       
  1019         try:
       
  1020             data = fp.read()
       
  1021         finally:
       
  1022             fp.close()
       
  1023 
       
  1024         container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
       
  1025         image = MIMEImage(data, name='dingusfish.gif')
       
  1026         image.add_header('content-disposition', 'attachment',
       
  1027                          filename='dingusfish.gif')
       
  1028         intro = MIMEText('''\
       
  1029 Hi there,
       
  1030 
       
  1031 This is the dingus fish.
       
  1032 ''')
       
  1033         container.attach(intro)
       
  1034         container.attach(image)
       
  1035         container['From'] = 'Barry <barry@digicool.com>'
       
  1036         container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
       
  1037         container['Subject'] = 'Here is your dingus fish'
       
  1038 
       
  1039         now = 987809702.54848599
       
  1040         timetuple = time.localtime(now)
       
  1041         if timetuple[-1] == 0:
       
  1042             tzsecs = time.timezone
       
  1043         else:
       
  1044             tzsecs = time.altzone
       
  1045         if tzsecs > 0:
       
  1046             sign = '-'
       
  1047         else:
       
  1048             sign = '+'
       
  1049         tzoffset = ' %s%04d' % (sign, tzsecs / 36)
       
  1050         container['Date'] = time.strftime(
       
  1051             '%a, %d %b %Y %H:%M:%S',
       
  1052             time.localtime(now)) + tzoffset
       
  1053         self._msg = container
       
  1054         self._im = image
       
  1055         self._txt = intro
       
  1056 
       
  1057     def test_hierarchy(self):
       
  1058         # convenience
       
  1059         eq = self.assertEqual
       
  1060         unless = self.failUnless
       
  1061         raises = self.assertRaises
       
  1062         # tests
       
  1063         m = self._msg
       
  1064         unless(m.is_multipart())
       
  1065         eq(m.get_content_type(), 'multipart/mixed')
       
  1066         eq(len(m.get_payload()), 2)
       
  1067         raises(IndexError, m.get_payload, 2)
       
  1068         m0 = m.get_payload(0)
       
  1069         m1 = m.get_payload(1)
       
  1070         unless(m0 is self._txt)
       
  1071         unless(m1 is self._im)
       
  1072         eq(m.get_payload(), [m0, m1])
       
  1073         unless(not m0.is_multipart())
       
  1074         unless(not m1.is_multipart())
       
  1075 
       
  1076     def test_empty_multipart_idempotent(self):
       
  1077         text = """\
       
  1078 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1079 MIME-Version: 1.0
       
  1080 Subject: A subject
       
  1081 To: aperson@dom.ain
       
  1082 From: bperson@dom.ain
       
  1083 
       
  1084 
       
  1085 --BOUNDARY
       
  1086 
       
  1087 
       
  1088 --BOUNDARY--
       
  1089 """
       
  1090         msg = Parser().parsestr(text)
       
  1091         self.ndiffAssertEqual(text, msg.as_string())
       
  1092 
       
  1093     def test_no_parts_in_a_multipart_with_none_epilogue(self):
       
  1094         outer = MIMEBase('multipart', 'mixed')
       
  1095         outer['Subject'] = 'A subject'
       
  1096         outer['To'] = 'aperson@dom.ain'
       
  1097         outer['From'] = 'bperson@dom.ain'
       
  1098         outer.set_boundary('BOUNDARY')
       
  1099         self.ndiffAssertEqual(outer.as_string(), '''\
       
  1100 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1101 MIME-Version: 1.0
       
  1102 Subject: A subject
       
  1103 To: aperson@dom.ain
       
  1104 From: bperson@dom.ain
       
  1105 
       
  1106 --BOUNDARY
       
  1107 
       
  1108 --BOUNDARY--''')
       
  1109 
       
  1110     def test_no_parts_in_a_multipart_with_empty_epilogue(self):
       
  1111         outer = MIMEBase('multipart', 'mixed')
       
  1112         outer['Subject'] = 'A subject'
       
  1113         outer['To'] = 'aperson@dom.ain'
       
  1114         outer['From'] = 'bperson@dom.ain'
       
  1115         outer.preamble = ''
       
  1116         outer.epilogue = ''
       
  1117         outer.set_boundary('BOUNDARY')
       
  1118         self.ndiffAssertEqual(outer.as_string(), '''\
       
  1119 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1120 MIME-Version: 1.0
       
  1121 Subject: A subject
       
  1122 To: aperson@dom.ain
       
  1123 From: bperson@dom.ain
       
  1124 
       
  1125 
       
  1126 --BOUNDARY
       
  1127 
       
  1128 --BOUNDARY--
       
  1129 ''')
       
  1130 
       
  1131     def test_one_part_in_a_multipart(self):
       
  1132         eq = self.ndiffAssertEqual
       
  1133         outer = MIMEBase('multipart', 'mixed')
       
  1134         outer['Subject'] = 'A subject'
       
  1135         outer['To'] = 'aperson@dom.ain'
       
  1136         outer['From'] = 'bperson@dom.ain'
       
  1137         outer.set_boundary('BOUNDARY')
       
  1138         msg = MIMEText('hello world')
       
  1139         outer.attach(msg)
       
  1140         eq(outer.as_string(), '''\
       
  1141 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1142 MIME-Version: 1.0
       
  1143 Subject: A subject
       
  1144 To: aperson@dom.ain
       
  1145 From: bperson@dom.ain
       
  1146 
       
  1147 --BOUNDARY
       
  1148 Content-Type: text/plain; charset="us-ascii"
       
  1149 MIME-Version: 1.0
       
  1150 Content-Transfer-Encoding: 7bit
       
  1151 
       
  1152 hello world
       
  1153 --BOUNDARY--''')
       
  1154 
       
  1155     def test_seq_parts_in_a_multipart_with_empty_preamble(self):
       
  1156         eq = self.ndiffAssertEqual
       
  1157         outer = MIMEBase('multipart', 'mixed')
       
  1158         outer['Subject'] = 'A subject'
       
  1159         outer['To'] = 'aperson@dom.ain'
       
  1160         outer['From'] = 'bperson@dom.ain'
       
  1161         outer.preamble = ''
       
  1162         msg = MIMEText('hello world')
       
  1163         outer.attach(msg)
       
  1164         outer.set_boundary('BOUNDARY')
       
  1165         eq(outer.as_string(), '''\
       
  1166 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1167 MIME-Version: 1.0
       
  1168 Subject: A subject
       
  1169 To: aperson@dom.ain
       
  1170 From: bperson@dom.ain
       
  1171 
       
  1172 
       
  1173 --BOUNDARY
       
  1174 Content-Type: text/plain; charset="us-ascii"
       
  1175 MIME-Version: 1.0
       
  1176 Content-Transfer-Encoding: 7bit
       
  1177 
       
  1178 hello world
       
  1179 --BOUNDARY--''')
       
  1180 
       
  1181 
       
  1182     def test_seq_parts_in_a_multipart_with_none_preamble(self):
       
  1183         eq = self.ndiffAssertEqual
       
  1184         outer = MIMEBase('multipart', 'mixed')
       
  1185         outer['Subject'] = 'A subject'
       
  1186         outer['To'] = 'aperson@dom.ain'
       
  1187         outer['From'] = 'bperson@dom.ain'
       
  1188         outer.preamble = None
       
  1189         msg = MIMEText('hello world')
       
  1190         outer.attach(msg)
       
  1191         outer.set_boundary('BOUNDARY')
       
  1192         eq(outer.as_string(), '''\
       
  1193 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1194 MIME-Version: 1.0
       
  1195 Subject: A subject
       
  1196 To: aperson@dom.ain
       
  1197 From: bperson@dom.ain
       
  1198 
       
  1199 --BOUNDARY
       
  1200 Content-Type: text/plain; charset="us-ascii"
       
  1201 MIME-Version: 1.0
       
  1202 Content-Transfer-Encoding: 7bit
       
  1203 
       
  1204 hello world
       
  1205 --BOUNDARY--''')
       
  1206 
       
  1207 
       
  1208     def test_seq_parts_in_a_multipart_with_none_epilogue(self):
       
  1209         eq = self.ndiffAssertEqual
       
  1210         outer = MIMEBase('multipart', 'mixed')
       
  1211         outer['Subject'] = 'A subject'
       
  1212         outer['To'] = 'aperson@dom.ain'
       
  1213         outer['From'] = 'bperson@dom.ain'
       
  1214         outer.epilogue = None
       
  1215         msg = MIMEText('hello world')
       
  1216         outer.attach(msg)
       
  1217         outer.set_boundary('BOUNDARY')
       
  1218         eq(outer.as_string(), '''\
       
  1219 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1220 MIME-Version: 1.0
       
  1221 Subject: A subject
       
  1222 To: aperson@dom.ain
       
  1223 From: bperson@dom.ain
       
  1224 
       
  1225 --BOUNDARY
       
  1226 Content-Type: text/plain; charset="us-ascii"
       
  1227 MIME-Version: 1.0
       
  1228 Content-Transfer-Encoding: 7bit
       
  1229 
       
  1230 hello world
       
  1231 --BOUNDARY--''')
       
  1232 
       
  1233 
       
  1234     def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
       
  1235         eq = self.ndiffAssertEqual
       
  1236         outer = MIMEBase('multipart', 'mixed')
       
  1237         outer['Subject'] = 'A subject'
       
  1238         outer['To'] = 'aperson@dom.ain'
       
  1239         outer['From'] = 'bperson@dom.ain'
       
  1240         outer.epilogue = ''
       
  1241         msg = MIMEText('hello world')
       
  1242         outer.attach(msg)
       
  1243         outer.set_boundary('BOUNDARY')
       
  1244         eq(outer.as_string(), '''\
       
  1245 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1246 MIME-Version: 1.0
       
  1247 Subject: A subject
       
  1248 To: aperson@dom.ain
       
  1249 From: bperson@dom.ain
       
  1250 
       
  1251 --BOUNDARY
       
  1252 Content-Type: text/plain; charset="us-ascii"
       
  1253 MIME-Version: 1.0
       
  1254 Content-Transfer-Encoding: 7bit
       
  1255 
       
  1256 hello world
       
  1257 --BOUNDARY--
       
  1258 ''')
       
  1259 
       
  1260 
       
  1261     def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
       
  1262         eq = self.ndiffAssertEqual
       
  1263         outer = MIMEBase('multipart', 'mixed')
       
  1264         outer['Subject'] = 'A subject'
       
  1265         outer['To'] = 'aperson@dom.ain'
       
  1266         outer['From'] = 'bperson@dom.ain'
       
  1267         outer.epilogue = '\n'
       
  1268         msg = MIMEText('hello world')
       
  1269         outer.attach(msg)
       
  1270         outer.set_boundary('BOUNDARY')
       
  1271         eq(outer.as_string(), '''\
       
  1272 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1273 MIME-Version: 1.0
       
  1274 Subject: A subject
       
  1275 To: aperson@dom.ain
       
  1276 From: bperson@dom.ain
       
  1277 
       
  1278 --BOUNDARY
       
  1279 Content-Type: text/plain; charset="us-ascii"
       
  1280 MIME-Version: 1.0
       
  1281 Content-Transfer-Encoding: 7bit
       
  1282 
       
  1283 hello world
       
  1284 --BOUNDARY--
       
  1285 
       
  1286 ''')
       
  1287 
       
  1288     def test_message_external_body(self):
       
  1289         eq = self.assertEqual
       
  1290         msg = self._msgobj('msg_36.txt')
       
  1291         eq(len(msg.get_payload()), 2)
       
  1292         msg1 = msg.get_payload(1)
       
  1293         eq(msg1.get_content_type(), 'multipart/alternative')
       
  1294         eq(len(msg1.get_payload()), 2)
       
  1295         for subpart in msg1.get_payload():
       
  1296             eq(subpart.get_content_type(), 'message/external-body')
       
  1297             eq(len(subpart.get_payload()), 1)
       
  1298             subsubpart = subpart.get_payload(0)
       
  1299             eq(subsubpart.get_content_type(), 'text/plain')
       
  1300 
       
  1301     def test_double_boundary(self):
       
  1302         # msg_37.txt is a multipart that contains two dash-boundary's in a
       
  1303         # row.  Our interpretation of RFC 2046 calls for ignoring the second
       
  1304         # and subsequent boundaries.
       
  1305         msg = self._msgobj('msg_37.txt')
       
  1306         self.assertEqual(len(msg.get_payload()), 3)
       
  1307 
       
  1308     def test_nested_inner_contains_outer_boundary(self):
       
  1309         eq = self.ndiffAssertEqual
       
  1310         # msg_38.txt has an inner part that contains outer boundaries.  My
       
  1311         # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
       
  1312         # these are illegal and should be interpreted as unterminated inner
       
  1313         # parts.
       
  1314         msg = self._msgobj('msg_38.txt')
       
  1315         sfp = StringIO()
       
  1316         Iterators._structure(msg, sfp)
       
  1317         eq(sfp.getvalue(), """\
       
  1318 multipart/mixed
       
  1319     multipart/mixed
       
  1320         multipart/alternative
       
  1321             text/plain
       
  1322         text/plain
       
  1323     text/plain
       
  1324     text/plain
       
  1325 """)
       
  1326 
       
  1327     def test_nested_with_same_boundary(self):
       
  1328         eq = self.ndiffAssertEqual
       
  1329         # msg 39.txt is similarly evil in that it's got inner parts that use
       
  1330         # the same boundary as outer parts.  Again, I believe the way this is
       
  1331         # parsed is closest to the spirit of RFC 2046
       
  1332         msg = self._msgobj('msg_39.txt')
       
  1333         sfp = StringIO()
       
  1334         Iterators._structure(msg, sfp)
       
  1335         eq(sfp.getvalue(), """\
       
  1336 multipart/mixed
       
  1337     multipart/mixed
       
  1338         multipart/alternative
       
  1339         application/octet-stream
       
  1340         application/octet-stream
       
  1341     text/plain
       
  1342 """)
       
  1343 
       
  1344     def test_boundary_in_non_multipart(self):
       
  1345         msg = self._msgobj('msg_40.txt')
       
  1346         self.assertEqual(msg.as_string(), '''\
       
  1347 MIME-Version: 1.0
       
  1348 Content-Type: text/html; boundary="--961284236552522269"
       
  1349 
       
  1350 ----961284236552522269
       
  1351 Content-Type: text/html;
       
  1352 Content-Transfer-Encoding: 7Bit
       
  1353 
       
  1354 <html></html>
       
  1355 
       
  1356 ----961284236552522269--
       
  1357 ''')
       
  1358 
       
  1359     def test_boundary_with_leading_space(self):
       
  1360         eq = self.assertEqual
       
  1361         msg = email.message_from_string('''\
       
  1362 MIME-Version: 1.0
       
  1363 Content-Type: multipart/mixed; boundary="    XXXX"
       
  1364 
       
  1365 --    XXXX
       
  1366 Content-Type: text/plain
       
  1367 
       
  1368 
       
  1369 --    XXXX
       
  1370 Content-Type: text/plain
       
  1371 
       
  1372 --    XXXX--
       
  1373 ''')
       
  1374         self.failUnless(msg.is_multipart())
       
  1375         eq(msg.get_boundary(), '    XXXX')
       
  1376         eq(len(msg.get_payload()), 2)
       
  1377 
       
  1378     def test_boundary_without_trailing_newline(self):
       
  1379         m = Parser().parsestr("""\
       
  1380 Content-Type: multipart/mixed; boundary="===============0012394164=="
       
  1381 MIME-Version: 1.0
       
  1382 
       
  1383 --===============0012394164==
       
  1384 Content-Type: image/file1.jpg
       
  1385 MIME-Version: 1.0
       
  1386 Content-Transfer-Encoding: base64
       
  1387 
       
  1388 YXNkZg==
       
  1389 --===============0012394164==--""")
       
  1390         self.assertEquals(m.get_payload(0).get_payload(), 'YXNkZg==')
       
  1391 
       
  1392 
       
  1393 
       
  1394 # Test some badly formatted messages
       
  1395 class TestNonConformant(TestEmailBase):
       
  1396     def test_parse_missing_minor_type(self):
       
  1397         eq = self.assertEqual
       
  1398         msg = self._msgobj('msg_14.txt')
       
  1399         eq(msg.get_content_type(), 'text/plain')
       
  1400         eq(msg.get_content_maintype(), 'text')
       
  1401         eq(msg.get_content_subtype(), 'plain')
       
  1402 
       
  1403     def test_same_boundary_inner_outer(self):
       
  1404         unless = self.failUnless
       
  1405         msg = self._msgobj('msg_15.txt')
       
  1406         # XXX We can probably eventually do better
       
  1407         inner = msg.get_payload(0)
       
  1408         unless(hasattr(inner, 'defects'))
       
  1409         self.assertEqual(len(inner.defects), 1)
       
  1410         unless(isinstance(inner.defects[0],
       
  1411                           Errors.StartBoundaryNotFoundDefect))
       
  1412 
       
  1413     def test_multipart_no_boundary(self):
       
  1414         unless = self.failUnless
       
  1415         msg = self._msgobj('msg_25.txt')
       
  1416         unless(isinstance(msg.get_payload(), str))
       
  1417         self.assertEqual(len(msg.defects), 2)
       
  1418         unless(isinstance(msg.defects[0], Errors.NoBoundaryInMultipartDefect))
       
  1419         unless(isinstance(msg.defects[1],
       
  1420                           Errors.MultipartInvariantViolationDefect))
       
  1421 
       
  1422     def test_invalid_content_type(self):
       
  1423         eq = self.assertEqual
       
  1424         neq = self.ndiffAssertEqual
       
  1425         msg = Message()
       
  1426         # RFC 2045, $5.2 says invalid yields text/plain
       
  1427         msg['Content-Type'] = 'text'
       
  1428         eq(msg.get_content_maintype(), 'text')
       
  1429         eq(msg.get_content_subtype(), 'plain')
       
  1430         eq(msg.get_content_type(), 'text/plain')
       
  1431         # Clear the old value and try something /really/ invalid
       
  1432         del msg['content-type']
       
  1433         msg['Content-Type'] = 'foo'
       
  1434         eq(msg.get_content_maintype(), 'text')
       
  1435         eq(msg.get_content_subtype(), 'plain')
       
  1436         eq(msg.get_content_type(), 'text/plain')
       
  1437         # Still, make sure that the message is idempotently generated
       
  1438         s = StringIO()
       
  1439         g = Generator(s)
       
  1440         g.flatten(msg)
       
  1441         neq(s.getvalue(), 'Content-Type: foo\n\n')
       
  1442 
       
  1443     def test_no_start_boundary(self):
       
  1444         eq = self.ndiffAssertEqual
       
  1445         msg = self._msgobj('msg_31.txt')
       
  1446         eq(msg.get_payload(), """\
       
  1447 --BOUNDARY
       
  1448 Content-Type: text/plain
       
  1449 
       
  1450 message 1
       
  1451 
       
  1452 --BOUNDARY
       
  1453 Content-Type: text/plain
       
  1454 
       
  1455 message 2
       
  1456 
       
  1457 --BOUNDARY--
       
  1458 """)
       
  1459 
       
  1460     def test_no_separating_blank_line(self):
       
  1461         eq = self.ndiffAssertEqual
       
  1462         msg = self._msgobj('msg_35.txt')
       
  1463         eq(msg.as_string(), """\
       
  1464 From: aperson@dom.ain
       
  1465 To: bperson@dom.ain
       
  1466 Subject: here's something interesting
       
  1467 
       
  1468 counter to RFC 2822, there's no separating newline here
       
  1469 """)
       
  1470 
       
  1471     def test_lying_multipart(self):
       
  1472         unless = self.failUnless
       
  1473         msg = self._msgobj('msg_41.txt')
       
  1474         unless(hasattr(msg, 'defects'))
       
  1475         self.assertEqual(len(msg.defects), 2)
       
  1476         unless(isinstance(msg.defects[0], Errors.NoBoundaryInMultipartDefect))
       
  1477         unless(isinstance(msg.defects[1],
       
  1478                           Errors.MultipartInvariantViolationDefect))
       
  1479 
       
  1480     def test_missing_start_boundary(self):
       
  1481         outer = self._msgobj('msg_42.txt')
       
  1482         # The message structure is:
       
  1483         #
       
  1484         # multipart/mixed
       
  1485         #    text/plain
       
  1486         #    message/rfc822
       
  1487         #        multipart/mixed [*]
       
  1488         #
       
  1489         # [*] This message is missing its start boundary
       
  1490         bad = outer.get_payload(1).get_payload(0)
       
  1491         self.assertEqual(len(bad.defects), 1)
       
  1492         self.failUnless(isinstance(bad.defects[0],
       
  1493                                    Errors.StartBoundaryNotFoundDefect))
       
  1494 
       
  1495     def test_first_line_is_continuation_header(self):
       
  1496         eq = self.assertEqual
       
  1497         m = ' Line 1\nLine 2\nLine 3'
       
  1498         msg = email.message_from_string(m)
       
  1499         eq(msg.keys(), [])
       
  1500         eq(msg.get_payload(), 'Line 2\nLine 3')
       
  1501         eq(len(msg.defects), 1)
       
  1502         self.failUnless(isinstance(msg.defects[0],
       
  1503                                    Errors.FirstHeaderLineIsContinuationDefect))
       
  1504         eq(msg.defects[0].line, ' Line 1\n')
       
  1505 
       
  1506 
       
  1507 
       
  1508 
       
  1509 # Test RFC 2047 header encoding and decoding
       
  1510 class TestRFC2047(unittest.TestCase):
       
  1511     def test_rfc2047_multiline(self):
       
  1512         eq = self.assertEqual
       
  1513         s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
       
  1514  foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
       
  1515         dh = decode_header(s)
       
  1516         eq(dh, [
       
  1517             ('Re:', None),
       
  1518             ('r\x8aksm\x9arg\x8cs', 'mac-iceland'),
       
  1519             ('baz foo bar', None),
       
  1520             ('r\x8aksm\x9arg\x8cs', 'mac-iceland')])
       
  1521         eq(str(make_header(dh)),
       
  1522            """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar
       
  1523  =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""")
       
  1524 
       
  1525     def test_whitespace_eater_unicode(self):
       
  1526         eq = self.assertEqual
       
  1527         s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
       
  1528         dh = decode_header(s)
       
  1529         eq(dh, [('Andr\xe9', 'iso-8859-1'), ('Pirard <pirard@dom.ain>', None)])
       
  1530         hu = unicode(make_header(dh)).encode('latin-1')
       
  1531         eq(hu, 'Andr\xe9 Pirard <pirard@dom.ain>')
       
  1532 
       
  1533     def test_whitespace_eater_unicode_2(self):
       
  1534         eq = self.assertEqual
       
  1535         s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
       
  1536         dh = decode_header(s)
       
  1537         eq(dh, [('The', None), ('quick brown fox', 'iso-8859-1'),
       
  1538                 ('jumped over the', None), ('lazy dog', 'iso-8859-1')])
       
  1539         hu = make_header(dh).__unicode__()
       
  1540         eq(hu, u'The quick brown fox jumped over the lazy dog')
       
  1541 
       
  1542     def test_rfc2047_without_whitespace(self):
       
  1543         s = 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
       
  1544         dh = decode_header(s)
       
  1545         self.assertEqual(dh, [(s, None)])
       
  1546 
       
  1547     def test_rfc2047_with_whitespace(self):
       
  1548         s = 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
       
  1549         dh = decode_header(s)
       
  1550         self.assertEqual(dh, [('Sm', None), ('\xf6', 'iso-8859-1'),
       
  1551                               ('rg', None), ('\xe5', 'iso-8859-1'),
       
  1552                               ('sbord', None)])
       
  1553 
       
  1554 
       
  1555 
       
  1556 # Test the MIMEMessage class
       
  1557 class TestMIMEMessage(TestEmailBase):
       
  1558     def setUp(self):
       
  1559         fp = openfile('msg_11.txt')
       
  1560         try:
       
  1561             self._text = fp.read()
       
  1562         finally:
       
  1563             fp.close()
       
  1564 
       
  1565     def test_type_error(self):
       
  1566         self.assertRaises(TypeError, MIMEMessage, 'a plain string')
       
  1567 
       
  1568     def test_valid_argument(self):
       
  1569         eq = self.assertEqual
       
  1570         unless = self.failUnless
       
  1571         subject = 'A sub-message'
       
  1572         m = Message()
       
  1573         m['Subject'] = subject
       
  1574         r = MIMEMessage(m)
       
  1575         eq(r.get_content_type(), 'message/rfc822')
       
  1576         payload = r.get_payload()
       
  1577         unless(isinstance(payload, list))
       
  1578         eq(len(payload), 1)
       
  1579         subpart = payload[0]
       
  1580         unless(subpart is m)
       
  1581         eq(subpart['subject'], subject)
       
  1582 
       
  1583     def test_bad_multipart(self):
       
  1584         eq = self.assertEqual
       
  1585         msg1 = Message()
       
  1586         msg1['Subject'] = 'subpart 1'
       
  1587         msg2 = Message()
       
  1588         msg2['Subject'] = 'subpart 2'
       
  1589         r = MIMEMessage(msg1)
       
  1590         self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
       
  1591 
       
  1592     def test_generate(self):
       
  1593         # First craft the message to be encapsulated
       
  1594         m = Message()
       
  1595         m['Subject'] = 'An enclosed message'
       
  1596         m.set_payload('Here is the body of the message.\n')
       
  1597         r = MIMEMessage(m)
       
  1598         r['Subject'] = 'The enclosing message'
       
  1599         s = StringIO()
       
  1600         g = Generator(s)
       
  1601         g.flatten(r)
       
  1602         self.assertEqual(s.getvalue(), """\
       
  1603 Content-Type: message/rfc822
       
  1604 MIME-Version: 1.0
       
  1605 Subject: The enclosing message
       
  1606 
       
  1607 Subject: An enclosed message
       
  1608 
       
  1609 Here is the body of the message.
       
  1610 """)
       
  1611 
       
  1612     def test_parse_message_rfc822(self):
       
  1613         eq = self.assertEqual
       
  1614         unless = self.failUnless
       
  1615         msg = self._msgobj('msg_11.txt')
       
  1616         eq(msg.get_content_type(), 'message/rfc822')
       
  1617         payload = msg.get_payload()
       
  1618         unless(isinstance(payload, list))
       
  1619         eq(len(payload), 1)
       
  1620         submsg = payload[0]
       
  1621         self.failUnless(isinstance(submsg, Message))
       
  1622         eq(submsg['subject'], 'An enclosed message')
       
  1623         eq(submsg.get_payload(), 'Here is the body of the message.\n')
       
  1624 
       
  1625     def test_dsn(self):
       
  1626         eq = self.assertEqual
       
  1627         unless = self.failUnless
       
  1628         # msg 16 is a Delivery Status Notification, see RFC 1894
       
  1629         msg = self._msgobj('msg_16.txt')
       
  1630         eq(msg.get_content_type(), 'multipart/report')
       
  1631         unless(msg.is_multipart())
       
  1632         eq(len(msg.get_payload()), 3)
       
  1633         # Subpart 1 is a text/plain, human readable section
       
  1634         subpart = msg.get_payload(0)
       
  1635         eq(subpart.get_content_type(), 'text/plain')
       
  1636         eq(subpart.get_payload(), """\
       
  1637 This report relates to a message you sent with the following header fields:
       
  1638 
       
  1639   Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
       
  1640   Date: Sun, 23 Sep 2001 20:10:55 -0700
       
  1641   From: "Ian T. Henry" <henryi@oxy.edu>
       
  1642   To: SoCal Raves <scr@socal-raves.org>
       
  1643   Subject: [scr] yeah for Ians!!
       
  1644 
       
  1645 Your message cannot be delivered to the following recipients:
       
  1646 
       
  1647   Recipient address: jangel1@cougar.noc.ucla.edu
       
  1648   Reason: recipient reached disk quota
       
  1649 
       
  1650 """)
       
  1651         # Subpart 2 contains the machine parsable DSN information.  It
       
  1652         # consists of two blocks of headers, represented by two nested Message
       
  1653         # objects.
       
  1654         subpart = msg.get_payload(1)
       
  1655         eq(subpart.get_content_type(), 'message/delivery-status')
       
  1656         eq(len(subpart.get_payload()), 2)
       
  1657         # message/delivery-status should treat each block as a bunch of
       
  1658         # headers, i.e. a bunch of Message objects.
       
  1659         dsn1 = subpart.get_payload(0)
       
  1660         unless(isinstance(dsn1, Message))
       
  1661         eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
       
  1662         eq(dsn1.get_param('dns', header='reporting-mta'), '')
       
  1663         # Try a missing one <wink>
       
  1664         eq(dsn1.get_param('nsd', header='reporting-mta'), None)
       
  1665         dsn2 = subpart.get_payload(1)
       
  1666         unless(isinstance(dsn2, Message))
       
  1667         eq(dsn2['action'], 'failed')
       
  1668         eq(dsn2.get_params(header='original-recipient'),
       
  1669            [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
       
  1670         eq(dsn2.get_param('rfc822', header='final-recipient'), '')
       
  1671         # Subpart 3 is the original message
       
  1672         subpart = msg.get_payload(2)
       
  1673         eq(subpart.get_content_type(), 'message/rfc822')
       
  1674         payload = subpart.get_payload()
       
  1675         unless(isinstance(payload, list))
       
  1676         eq(len(payload), 1)
       
  1677         subsubpart = payload[0]
       
  1678         unless(isinstance(subsubpart, Message))
       
  1679         eq(subsubpart.get_content_type(), 'text/plain')
       
  1680         eq(subsubpart['message-id'],
       
  1681            '<002001c144a6$8752e060$56104586@oxy.edu>')
       
  1682 
       
  1683     def test_epilogue(self):
       
  1684         eq = self.ndiffAssertEqual
       
  1685         fp = openfile('msg_21.txt')
       
  1686         try:
       
  1687             text = fp.read()
       
  1688         finally:
       
  1689             fp.close()
       
  1690         msg = Message()
       
  1691         msg['From'] = 'aperson@dom.ain'
       
  1692         msg['To'] = 'bperson@dom.ain'
       
  1693         msg['Subject'] = 'Test'
       
  1694         msg.preamble = 'MIME message'
       
  1695         msg.epilogue = 'End of MIME message\n'
       
  1696         msg1 = MIMEText('One')
       
  1697         msg2 = MIMEText('Two')
       
  1698         msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
       
  1699         msg.attach(msg1)
       
  1700         msg.attach(msg2)
       
  1701         sfp = StringIO()
       
  1702         g = Generator(sfp)
       
  1703         g.flatten(msg)
       
  1704         eq(sfp.getvalue(), text)
       
  1705 
       
  1706     def test_no_nl_preamble(self):
       
  1707         eq = self.ndiffAssertEqual
       
  1708         msg = Message()
       
  1709         msg['From'] = 'aperson@dom.ain'
       
  1710         msg['To'] = 'bperson@dom.ain'
       
  1711         msg['Subject'] = 'Test'
       
  1712         msg.preamble = 'MIME message'
       
  1713         msg.epilogue = ''
       
  1714         msg1 = MIMEText('One')
       
  1715         msg2 = MIMEText('Two')
       
  1716         msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
       
  1717         msg.attach(msg1)
       
  1718         msg.attach(msg2)
       
  1719         eq(msg.as_string(), """\
       
  1720 From: aperson@dom.ain
       
  1721 To: bperson@dom.ain
       
  1722 Subject: Test
       
  1723 Content-Type: multipart/mixed; boundary="BOUNDARY"
       
  1724 
       
  1725 MIME message
       
  1726 --BOUNDARY
       
  1727 Content-Type: text/plain; charset="us-ascii"
       
  1728 MIME-Version: 1.0
       
  1729 Content-Transfer-Encoding: 7bit
       
  1730 
       
  1731 One
       
  1732 --BOUNDARY
       
  1733 Content-Type: text/plain; charset="us-ascii"
       
  1734 MIME-Version: 1.0
       
  1735 Content-Transfer-Encoding: 7bit
       
  1736 
       
  1737 Two
       
  1738 --BOUNDARY--
       
  1739 """)
       
  1740 
       
  1741     def test_default_type(self):
       
  1742         eq = self.assertEqual
       
  1743         fp = openfile('msg_30.txt')
       
  1744         try:
       
  1745             msg = email.message_from_file(fp)
       
  1746         finally:
       
  1747             fp.close()
       
  1748         container1 = msg.get_payload(0)
       
  1749         eq(container1.get_default_type(), 'message/rfc822')
       
  1750         eq(container1.get_content_type(), 'message/rfc822')
       
  1751         container2 = msg.get_payload(1)
       
  1752         eq(container2.get_default_type(), 'message/rfc822')
       
  1753         eq(container2.get_content_type(), 'message/rfc822')
       
  1754         container1a = container1.get_payload(0)
       
  1755         eq(container1a.get_default_type(), 'text/plain')
       
  1756         eq(container1a.get_content_type(), 'text/plain')
       
  1757         container2a = container2.get_payload(0)
       
  1758         eq(container2a.get_default_type(), 'text/plain')
       
  1759         eq(container2a.get_content_type(), 'text/plain')
       
  1760 
       
  1761     def test_default_type_with_explicit_container_type(self):
       
  1762         eq = self.assertEqual
       
  1763         fp = openfile('msg_28.txt')
       
  1764         try:
       
  1765             msg = email.message_from_file(fp)
       
  1766         finally:
       
  1767             fp.close()
       
  1768         container1 = msg.get_payload(0)
       
  1769         eq(container1.get_default_type(), 'message/rfc822')
       
  1770         eq(container1.get_content_type(), 'message/rfc822')
       
  1771         container2 = msg.get_payload(1)
       
  1772         eq(container2.get_default_type(), 'message/rfc822')
       
  1773         eq(container2.get_content_type(), 'message/rfc822')
       
  1774         container1a = container1.get_payload(0)
       
  1775         eq(container1a.get_default_type(), 'text/plain')
       
  1776         eq(container1a.get_content_type(), 'text/plain')
       
  1777         container2a = container2.get_payload(0)
       
  1778         eq(container2a.get_default_type(), 'text/plain')
       
  1779         eq(container2a.get_content_type(), 'text/plain')
       
  1780 
       
  1781     def test_default_type_non_parsed(self):
       
  1782         eq = self.assertEqual
       
  1783         neq = self.ndiffAssertEqual
       
  1784         # Set up container
       
  1785         container = MIMEMultipart('digest', 'BOUNDARY')
       
  1786         container.epilogue = ''
       
  1787         # Set up subparts
       
  1788         subpart1a = MIMEText('message 1\n')
       
  1789         subpart2a = MIMEText('message 2\n')
       
  1790         subpart1 = MIMEMessage(subpart1a)
       
  1791         subpart2 = MIMEMessage(subpart2a)
       
  1792         container.attach(subpart1)
       
  1793         container.attach(subpart2)
       
  1794         eq(subpart1.get_content_type(), 'message/rfc822')
       
  1795         eq(subpart1.get_default_type(), 'message/rfc822')
       
  1796         eq(subpart2.get_content_type(), 'message/rfc822')
       
  1797         eq(subpart2.get_default_type(), 'message/rfc822')
       
  1798         neq(container.as_string(0), '''\
       
  1799 Content-Type: multipart/digest; boundary="BOUNDARY"
       
  1800 MIME-Version: 1.0
       
  1801 
       
  1802 --BOUNDARY
       
  1803 Content-Type: message/rfc822
       
  1804 MIME-Version: 1.0
       
  1805 
       
  1806 Content-Type: text/plain; charset="us-ascii"
       
  1807 MIME-Version: 1.0
       
  1808 Content-Transfer-Encoding: 7bit
       
  1809 
       
  1810 message 1
       
  1811 
       
  1812 --BOUNDARY
       
  1813 Content-Type: message/rfc822
       
  1814 MIME-Version: 1.0
       
  1815 
       
  1816 Content-Type: text/plain; charset="us-ascii"
       
  1817 MIME-Version: 1.0
       
  1818 Content-Transfer-Encoding: 7bit
       
  1819 
       
  1820 message 2
       
  1821 
       
  1822 --BOUNDARY--
       
  1823 ''')
       
  1824         del subpart1['content-type']
       
  1825         del subpart1['mime-version']
       
  1826         del subpart2['content-type']
       
  1827         del subpart2['mime-version']
       
  1828         eq(subpart1.get_content_type(), 'message/rfc822')
       
  1829         eq(subpart1.get_default_type(), 'message/rfc822')
       
  1830         eq(subpart2.get_content_type(), 'message/rfc822')
       
  1831         eq(subpart2.get_default_type(), 'message/rfc822')
       
  1832         neq(container.as_string(0), '''\
       
  1833 Content-Type: multipart/digest; boundary="BOUNDARY"
       
  1834 MIME-Version: 1.0
       
  1835 
       
  1836 --BOUNDARY
       
  1837 
       
  1838 Content-Type: text/plain; charset="us-ascii"
       
  1839 MIME-Version: 1.0
       
  1840 Content-Transfer-Encoding: 7bit
       
  1841 
       
  1842 message 1
       
  1843 
       
  1844 --BOUNDARY
       
  1845 
       
  1846 Content-Type: text/plain; charset="us-ascii"
       
  1847 MIME-Version: 1.0
       
  1848 Content-Transfer-Encoding: 7bit
       
  1849 
       
  1850 message 2
       
  1851 
       
  1852 --BOUNDARY--
       
  1853 ''')
       
  1854 
       
  1855     def test_mime_attachments_in_constructor(self):
       
  1856         eq = self.assertEqual
       
  1857         text1 = MIMEText('')
       
  1858         text2 = MIMEText('')
       
  1859         msg = MIMEMultipart(_subparts=(text1, text2))
       
  1860         eq(len(msg.get_payload()), 2)
       
  1861         eq(msg.get_payload(0), text1)
       
  1862         eq(msg.get_payload(1), text2)
       
  1863 
       
  1864     def test_default_multipart_constructor(self):
       
  1865         msg = MIMEMultipart()
       
  1866         self.assertTrue(msg.is_multipart())
       
  1867 
       
  1868 
       
  1869 # A general test of parser->model->generator idempotency.  IOW, read a message
       
  1870 # in, parse it into a message object tree, then without touching the tree,
       
  1871 # regenerate the plain text.  The original text and the transformed text
       
  1872 # should be identical.  Note: that we ignore the Unix-From since that may
       
  1873 # contain a changed date.
       
  1874 class TestIdempotent(TestEmailBase):
       
  1875     def _msgobj(self, filename):
       
  1876         fp = openfile(filename)
       
  1877         try:
       
  1878             data = fp.read()
       
  1879         finally:
       
  1880             fp.close()
       
  1881         msg = email.message_from_string(data)
       
  1882         return msg, data
       
  1883 
       
  1884     def _idempotent(self, msg, text):
       
  1885         eq = self.ndiffAssertEqual
       
  1886         s = StringIO()
       
  1887         g = Generator(s, maxheaderlen=0)
       
  1888         g.flatten(msg)
       
  1889         eq(text, s.getvalue())
       
  1890 
       
  1891     def test_parse_text_message(self):
       
  1892         eq = self.assertEquals
       
  1893         msg, text = self._msgobj('msg_01.txt')
       
  1894         eq(msg.get_content_type(), 'text/plain')
       
  1895         eq(msg.get_content_maintype(), 'text')
       
  1896         eq(msg.get_content_subtype(), 'plain')
       
  1897         eq(msg.get_params()[1], ('charset', 'us-ascii'))
       
  1898         eq(msg.get_param('charset'), 'us-ascii')
       
  1899         eq(msg.preamble, None)
       
  1900         eq(msg.epilogue, None)
       
  1901         self._idempotent(msg, text)
       
  1902 
       
  1903     def test_parse_untyped_message(self):
       
  1904         eq = self.assertEquals
       
  1905         msg, text = self._msgobj('msg_03.txt')
       
  1906         eq(msg.get_content_type(), 'text/plain')
       
  1907         eq(msg.get_params(), None)
       
  1908         eq(msg.get_param('charset'), None)
       
  1909         self._idempotent(msg, text)
       
  1910 
       
  1911     def test_simple_multipart(self):
       
  1912         msg, text = self._msgobj('msg_04.txt')
       
  1913         self._idempotent(msg, text)
       
  1914 
       
  1915     def test_MIME_digest(self):
       
  1916         msg, text = self._msgobj('msg_02.txt')
       
  1917         self._idempotent(msg, text)
       
  1918 
       
  1919     def test_long_header(self):
       
  1920         msg, text = self._msgobj('msg_27.txt')
       
  1921         self._idempotent(msg, text)
       
  1922 
       
  1923     def test_MIME_digest_with_part_headers(self):
       
  1924         msg, text = self._msgobj('msg_28.txt')
       
  1925         self._idempotent(msg, text)
       
  1926 
       
  1927     def test_mixed_with_image(self):
       
  1928         msg, text = self._msgobj('msg_06.txt')
       
  1929         self._idempotent(msg, text)
       
  1930 
       
  1931     def test_multipart_report(self):
       
  1932         msg, text = self._msgobj('msg_05.txt')
       
  1933         self._idempotent(msg, text)
       
  1934 
       
  1935     def test_dsn(self):
       
  1936         msg, text = self._msgobj('msg_16.txt')
       
  1937         self._idempotent(msg, text)
       
  1938 
       
  1939     def test_preamble_epilogue(self):
       
  1940         msg, text = self._msgobj('msg_21.txt')
       
  1941         self._idempotent(msg, text)
       
  1942 
       
  1943     def test_multipart_one_part(self):
       
  1944         msg, text = self._msgobj('msg_23.txt')
       
  1945         self._idempotent(msg, text)
       
  1946 
       
  1947     def test_multipart_no_parts(self):
       
  1948         msg, text = self._msgobj('msg_24.txt')
       
  1949         self._idempotent(msg, text)
       
  1950 
       
  1951     def test_no_start_boundary(self):
       
  1952         msg, text = self._msgobj('msg_31.txt')
       
  1953         self._idempotent(msg, text)
       
  1954 
       
  1955     def test_rfc2231_charset(self):
       
  1956         msg, text = self._msgobj('msg_32.txt')
       
  1957         self._idempotent(msg, text)
       
  1958 
       
  1959     def test_more_rfc2231_parameters(self):
       
  1960         msg, text = self._msgobj('msg_33.txt')
       
  1961         self._idempotent(msg, text)
       
  1962 
       
  1963     def test_text_plain_in_a_multipart_digest(self):
       
  1964         msg, text = self._msgobj('msg_34.txt')
       
  1965         self._idempotent(msg, text)
       
  1966 
       
  1967     def test_nested_multipart_mixeds(self):
       
  1968         msg, text = self._msgobj('msg_12a.txt')
       
  1969         self._idempotent(msg, text)
       
  1970 
       
  1971     def test_message_external_body_idempotent(self):
       
  1972         msg, text = self._msgobj('msg_36.txt')
       
  1973         self._idempotent(msg, text)
       
  1974 
       
  1975     def test_content_type(self):
       
  1976         eq = self.assertEquals
       
  1977         unless = self.failUnless
       
  1978         # Get a message object and reset the seek pointer for other tests
       
  1979         msg, text = self._msgobj('msg_05.txt')
       
  1980         eq(msg.get_content_type(), 'multipart/report')
       
  1981         # Test the Content-Type: parameters
       
  1982         params = {}
       
  1983         for pk, pv in msg.get_params():
       
  1984             params[pk] = pv
       
  1985         eq(params['report-type'], 'delivery-status')
       
  1986         eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
       
  1987         eq(msg.preamble, 'This is a MIME-encapsulated message.\n')
       
  1988         eq(msg.epilogue, '\n')
       
  1989         eq(len(msg.get_payload()), 3)
       
  1990         # Make sure the subparts are what we expect
       
  1991         msg1 = msg.get_payload(0)
       
  1992         eq(msg1.get_content_type(), 'text/plain')
       
  1993         eq(msg1.get_payload(), 'Yadda yadda yadda\n')
       
  1994         msg2 = msg.get_payload(1)
       
  1995         eq(msg2.get_content_type(), 'text/plain')
       
  1996         eq(msg2.get_payload(), 'Yadda yadda yadda\n')
       
  1997         msg3 = msg.get_payload(2)
       
  1998         eq(msg3.get_content_type(), 'message/rfc822')
       
  1999         self.failUnless(isinstance(msg3, Message))
       
  2000         payload = msg3.get_payload()
       
  2001         unless(isinstance(payload, list))
       
  2002         eq(len(payload), 1)
       
  2003         msg4 = payload[0]
       
  2004         unless(isinstance(msg4, Message))
       
  2005         eq(msg4.get_payload(), 'Yadda yadda yadda\n')
       
  2006 
       
  2007     def test_parser(self):
       
  2008         eq = self.assertEquals
       
  2009         unless = self.failUnless
       
  2010         msg, text = self._msgobj('msg_06.txt')
       
  2011         # Check some of the outer headers
       
  2012         eq(msg.get_content_type(), 'message/rfc822')
       
  2013         # Make sure the payload is a list of exactly one sub-Message, and that
       
  2014         # that submessage has a type of text/plain
       
  2015         payload = msg.get_payload()
       
  2016         unless(isinstance(payload, list))
       
  2017         eq(len(payload), 1)
       
  2018         msg1 = payload[0]
       
  2019         self.failUnless(isinstance(msg1, Message))
       
  2020         eq(msg1.get_content_type(), 'text/plain')
       
  2021         self.failUnless(isinstance(msg1.get_payload(), str))
       
  2022         eq(msg1.get_payload(), '\n')
       
  2023 
       
  2024 
       
  2025 
       
  2026 # Test various other bits of the package's functionality
       
  2027 class TestMiscellaneous(TestEmailBase):
       
  2028     def test_message_from_string(self):
       
  2029         fp = openfile('msg_01.txt')
       
  2030         try:
       
  2031             text = fp.read()
       
  2032         finally:
       
  2033             fp.close()
       
  2034         msg = email.message_from_string(text)
       
  2035         s = StringIO()
       
  2036         # Don't wrap/continue long headers since we're trying to test
       
  2037         # idempotency.
       
  2038         g = Generator(s, maxheaderlen=0)
       
  2039         g.flatten(msg)
       
  2040         self.assertEqual(text, s.getvalue())
       
  2041 
       
  2042     def test_message_from_file(self):
       
  2043         fp = openfile('msg_01.txt')
       
  2044         try:
       
  2045             text = fp.read()
       
  2046             fp.seek(0)
       
  2047             msg = email.message_from_file(fp)
       
  2048             s = StringIO()
       
  2049             # Don't wrap/continue long headers since we're trying to test
       
  2050             # idempotency.
       
  2051             g = Generator(s, maxheaderlen=0)
       
  2052             g.flatten(msg)
       
  2053             self.assertEqual(text, s.getvalue())
       
  2054         finally:
       
  2055             fp.close()
       
  2056 
       
  2057     def test_message_from_string_with_class(self):
       
  2058         unless = self.failUnless
       
  2059         fp = openfile('msg_01.txt')
       
  2060         try:
       
  2061             text = fp.read()
       
  2062         finally:
       
  2063             fp.close()
       
  2064         # Create a subclass
       
  2065         class MyMessage(Message):
       
  2066             pass
       
  2067 
       
  2068         msg = email.message_from_string(text, MyMessage)
       
  2069         unless(isinstance(msg, MyMessage))
       
  2070         # Try something more complicated
       
  2071         fp = openfile('msg_02.txt')
       
  2072         try:
       
  2073             text = fp.read()
       
  2074         finally:
       
  2075             fp.close()
       
  2076         msg = email.message_from_string(text, MyMessage)
       
  2077         for subpart in msg.walk():
       
  2078             unless(isinstance(subpart, MyMessage))
       
  2079 
       
  2080     def test_message_from_file_with_class(self):
       
  2081         unless = self.failUnless
       
  2082         # Create a subclass
       
  2083         class MyMessage(Message):
       
  2084             pass
       
  2085 
       
  2086         fp = openfile('msg_01.txt')
       
  2087         try:
       
  2088             msg = email.message_from_file(fp, MyMessage)
       
  2089         finally:
       
  2090             fp.close()
       
  2091         unless(isinstance(msg, MyMessage))
       
  2092         # Try something more complicated
       
  2093         fp = openfile('msg_02.txt')
       
  2094         try:
       
  2095             msg = email.message_from_file(fp, MyMessage)
       
  2096         finally:
       
  2097             fp.close()
       
  2098         for subpart in msg.walk():
       
  2099             unless(isinstance(subpart, MyMessage))
       
  2100 
       
  2101     def test__all__(self):
       
  2102         module = __import__('email')
       
  2103         all = module.__all__
       
  2104         all.sort()
       
  2105         self.assertEqual(all, [
       
  2106             # Old names
       
  2107             'Charset', 'Encoders', 'Errors', 'Generator',
       
  2108             'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
       
  2109             'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
       
  2110             'MIMENonMultipart', 'MIMEText', 'Message',
       
  2111             'Parser', 'Utils', 'base64MIME',
       
  2112             # new names
       
  2113             'base64mime', 'charset', 'encoders', 'errors', 'generator',
       
  2114             'header', 'iterators', 'message', 'message_from_file',
       
  2115             'message_from_string', 'mime', 'parser',
       
  2116             'quopriMIME', 'quoprimime', 'utils',
       
  2117             ])
       
  2118 
       
  2119     def test_formatdate(self):
       
  2120         now = time.time()
       
  2121         self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
       
  2122                          time.gmtime(now)[:6])
       
  2123 
       
  2124     def test_formatdate_localtime(self):
       
  2125         now = time.time()
       
  2126         self.assertEqual(
       
  2127             Utils.parsedate(Utils.formatdate(now, localtime=True))[:6],
       
  2128             time.localtime(now)[:6])
       
  2129 
       
  2130     def test_formatdate_usegmt(self):
       
  2131         now = time.time()
       
  2132         self.assertEqual(
       
  2133             Utils.formatdate(now, localtime=False),
       
  2134             time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
       
  2135         self.assertEqual(
       
  2136             Utils.formatdate(now, localtime=False, usegmt=True),
       
  2137             time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
       
  2138 
       
  2139     def test_parsedate_none(self):
       
  2140         self.assertEqual(Utils.parsedate(''), None)
       
  2141 
       
  2142     def test_parsedate_compact(self):
       
  2143         # The FWS after the comma is optional
       
  2144         self.assertEqual(Utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
       
  2145                          Utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
       
  2146 
       
  2147     def test_parsedate_no_dayofweek(self):
       
  2148         eq = self.assertEqual
       
  2149         eq(Utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
       
  2150            (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
       
  2151 
       
  2152     def test_parsedate_compact_no_dayofweek(self):
       
  2153         eq = self.assertEqual
       
  2154         eq(Utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
       
  2155            (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
       
  2156 
       
  2157     def test_parsedate_acceptable_to_time_functions(self):
       
  2158         eq = self.assertEqual
       
  2159         timetup = Utils.parsedate('5 Feb 2003 13:47:26 -0800')
       
  2160         t = int(time.mktime(timetup))
       
  2161         eq(time.localtime(t)[:6], timetup[:6])
       
  2162         eq(int(time.strftime('%Y', timetup)), 2003)
       
  2163         timetup = Utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
       
  2164         t = int(time.mktime(timetup[:9]))
       
  2165         eq(time.localtime(t)[:6], timetup[:6])
       
  2166         eq(int(time.strftime('%Y', timetup[:9])), 2003)
       
  2167 
       
  2168     def test_parseaddr_empty(self):
       
  2169         self.assertEqual(Utils.parseaddr('<>'), ('', ''))
       
  2170         self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
       
  2171 
       
  2172     def test_noquote_dump(self):
       
  2173         self.assertEqual(
       
  2174             Utils.formataddr(('A Silly Person', 'person@dom.ain')),
       
  2175             'A Silly Person <person@dom.ain>')
       
  2176 
       
  2177     def test_escape_dump(self):
       
  2178         self.assertEqual(
       
  2179             Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
       
  2180             r'"A \(Very\) Silly Person" <person@dom.ain>')
       
  2181         a = r'A \(Special\) Person'
       
  2182         b = 'person@dom.ain'
       
  2183         self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
       
  2184 
       
  2185     def test_escape_backslashes(self):
       
  2186         self.assertEqual(
       
  2187             Utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
       
  2188             r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
       
  2189         a = r'Arthur \Backslash\ Foobar'
       
  2190         b = 'person@dom.ain'
       
  2191         self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
       
  2192 
       
  2193     def test_name_with_dot(self):
       
  2194         x = 'John X. Doe <jxd@example.com>'
       
  2195         y = '"John X. Doe" <jxd@example.com>'
       
  2196         a, b = ('John X. Doe', 'jxd@example.com')
       
  2197         self.assertEqual(Utils.parseaddr(x), (a, b))
       
  2198         self.assertEqual(Utils.parseaddr(y), (a, b))
       
  2199         # formataddr() quotes the name if there's a dot in it
       
  2200         self.assertEqual(Utils.formataddr((a, b)), y)
       
  2201 
       
  2202     def test_multiline_from_comment(self):
       
  2203         x = """\
       
  2204 Foo
       
  2205 \tBar <foo@example.com>"""
       
  2206         self.assertEqual(Utils.parseaddr(x), ('Foo Bar', 'foo@example.com'))
       
  2207 
       
  2208     def test_quote_dump(self):
       
  2209         self.assertEqual(
       
  2210             Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
       
  2211             r'"A Silly; Person" <person@dom.ain>')
       
  2212 
       
  2213     def test_fix_eols(self):
       
  2214         eq = self.assertEqual
       
  2215         eq(Utils.fix_eols('hello'), 'hello')
       
  2216         eq(Utils.fix_eols('hello\n'), 'hello\r\n')
       
  2217         eq(Utils.fix_eols('hello\r'), 'hello\r\n')
       
  2218         eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
       
  2219         eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
       
  2220 
       
  2221     def test_charset_richcomparisons(self):
       
  2222         eq = self.assertEqual
       
  2223         ne = self.failIfEqual
       
  2224         cset1 = Charset()
       
  2225         cset2 = Charset()
       
  2226         eq(cset1, 'us-ascii')
       
  2227         eq(cset1, 'US-ASCII')
       
  2228         eq(cset1, 'Us-AsCiI')
       
  2229         eq('us-ascii', cset1)
       
  2230         eq('US-ASCII', cset1)
       
  2231         eq('Us-AsCiI', cset1)
       
  2232         ne(cset1, 'usascii')
       
  2233         ne(cset1, 'USASCII')
       
  2234         ne(cset1, 'UsAsCiI')
       
  2235         ne('usascii', cset1)
       
  2236         ne('USASCII', cset1)
       
  2237         ne('UsAsCiI', cset1)
       
  2238         eq(cset1, cset2)
       
  2239         eq(cset2, cset1)
       
  2240 
       
  2241     def test_getaddresses(self):
       
  2242         eq = self.assertEqual
       
  2243         eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
       
  2244                                'Bud Person <bperson@dom.ain>']),
       
  2245            [('Al Person', 'aperson@dom.ain'),
       
  2246             ('Bud Person', 'bperson@dom.ain')])
       
  2247 
       
  2248     def test_getaddresses_nasty(self):
       
  2249         eq = self.assertEqual
       
  2250         eq(Utils.getaddresses(['foo: ;']), [('', '')])
       
  2251         eq(Utils.getaddresses(
       
  2252            ['[]*-- =~$']),
       
  2253            [('', ''), ('', ''), ('', '*--')])
       
  2254         eq(Utils.getaddresses(
       
  2255            ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
       
  2256            [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
       
  2257 
       
  2258     def test_getaddresses_embedded_comment(self):
       
  2259         """Test proper handling of a nested comment"""
       
  2260         eq = self.assertEqual
       
  2261         addrs = Utils.getaddresses(['User ((nested comment)) <foo@bar.com>'])
       
  2262         eq(addrs[0][1], 'foo@bar.com')
       
  2263 
       
  2264     def test_utils_quote_unquote(self):
       
  2265         eq = self.assertEqual
       
  2266         msg = Message()
       
  2267         msg.add_header('content-disposition', 'attachment',
       
  2268                        filename='foo\\wacky"name')
       
  2269         eq(msg.get_filename(), 'foo\\wacky"name')
       
  2270 
       
  2271     def test_get_body_encoding_with_bogus_charset(self):
       
  2272         charset = Charset('not a charset')
       
  2273         self.assertEqual(charset.get_body_encoding(), 'base64')
       
  2274 
       
  2275     def test_get_body_encoding_with_uppercase_charset(self):
       
  2276         eq = self.assertEqual
       
  2277         msg = Message()
       
  2278         msg['Content-Type'] = 'text/plain; charset=UTF-8'
       
  2279         eq(msg['content-type'], 'text/plain; charset=UTF-8')
       
  2280         charsets = msg.get_charsets()
       
  2281         eq(len(charsets), 1)
       
  2282         eq(charsets[0], 'utf-8')
       
  2283         charset = Charset(charsets[0])
       
  2284         eq(charset.get_body_encoding(), 'base64')
       
  2285         msg.set_payload('hello world', charset=charset)
       
  2286         eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n')
       
  2287         eq(msg.get_payload(decode=True), 'hello world')
       
  2288         eq(msg['content-transfer-encoding'], 'base64')
       
  2289         # Try another one
       
  2290         msg = Message()
       
  2291         msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
       
  2292         charsets = msg.get_charsets()
       
  2293         eq(len(charsets), 1)
       
  2294         eq(charsets[0], 'us-ascii')
       
  2295         charset = Charset(charsets[0])
       
  2296         eq(charset.get_body_encoding(), Encoders.encode_7or8bit)
       
  2297         msg.set_payload('hello world', charset=charset)
       
  2298         eq(msg.get_payload(), 'hello world')
       
  2299         eq(msg['content-transfer-encoding'], '7bit')
       
  2300 
       
  2301     def test_charsets_case_insensitive(self):
       
  2302         lc = Charset('us-ascii')
       
  2303         uc = Charset('US-ASCII')
       
  2304         self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
       
  2305 
       
  2306     def test_partial_falls_inside_message_delivery_status(self):
       
  2307         eq = self.ndiffAssertEqual
       
  2308         # The Parser interface provides chunks of data to FeedParser in 8192
       
  2309         # byte gulps.  SF bug #1076485 found one of those chunks inside
       
  2310         # message/delivery-status header block, which triggered an
       
  2311         # unreadline() of NeedMoreData.
       
  2312         msg = self._msgobj('msg_43.txt')
       
  2313         sfp = StringIO()
       
  2314         Iterators._structure(msg, sfp)
       
  2315         eq(sfp.getvalue(), """\
       
  2316 multipart/report
       
  2317     text/plain
       
  2318     message/delivery-status
       
  2319         text/plain
       
  2320         text/plain
       
  2321         text/plain
       
  2322         text/plain
       
  2323         text/plain
       
  2324         text/plain
       
  2325         text/plain
       
  2326         text/plain
       
  2327         text/plain
       
  2328         text/plain
       
  2329         text/plain
       
  2330         text/plain
       
  2331         text/plain
       
  2332         text/plain
       
  2333         text/plain
       
  2334         text/plain
       
  2335         text/plain
       
  2336         text/plain
       
  2337         text/plain
       
  2338         text/plain
       
  2339         text/plain
       
  2340         text/plain
       
  2341         text/plain
       
  2342         text/plain
       
  2343         text/plain
       
  2344         text/plain
       
  2345     text/rfc822-headers
       
  2346 """)
       
  2347 
       
  2348 
       
  2349 
       
  2350 # Test the iterator/generators
       
  2351 class TestIterators(TestEmailBase):
       
  2352     def test_body_line_iterator(self):
       
  2353         eq = self.assertEqual
       
  2354         neq = self.ndiffAssertEqual
       
  2355         # First a simple non-multipart message
       
  2356         msg = self._msgobj('msg_01.txt')
       
  2357         it = Iterators.body_line_iterator(msg)
       
  2358         lines = list(it)
       
  2359         eq(len(lines), 6)
       
  2360         neq(EMPTYSTRING.join(lines), msg.get_payload())
       
  2361         # Now a more complicated multipart
       
  2362         msg = self._msgobj('msg_02.txt')
       
  2363         it = Iterators.body_line_iterator(msg)
       
  2364         lines = list(it)
       
  2365         eq(len(lines), 43)
       
  2366         fp = openfile('msg_19.txt')
       
  2367         try:
       
  2368             neq(EMPTYSTRING.join(lines), fp.read())
       
  2369         finally:
       
  2370             fp.close()
       
  2371 
       
  2372     def test_typed_subpart_iterator(self):
       
  2373         eq = self.assertEqual
       
  2374         msg = self._msgobj('msg_04.txt')
       
  2375         it = Iterators.typed_subpart_iterator(msg, 'text')
       
  2376         lines = []
       
  2377         subparts = 0
       
  2378         for subpart in it:
       
  2379             subparts += 1
       
  2380             lines.append(subpart.get_payload())
       
  2381         eq(subparts, 2)
       
  2382         eq(EMPTYSTRING.join(lines), """\
       
  2383 a simple kind of mirror
       
  2384 to reflect upon our own
       
  2385 a simple kind of mirror
       
  2386 to reflect upon our own
       
  2387 """)
       
  2388 
       
  2389     def test_typed_subpart_iterator_default_type(self):
       
  2390         eq = self.assertEqual
       
  2391         msg = self._msgobj('msg_03.txt')
       
  2392         it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
       
  2393         lines = []
       
  2394         subparts = 0
       
  2395         for subpart in it:
       
  2396             subparts += 1
       
  2397             lines.append(subpart.get_payload())
       
  2398         eq(subparts, 1)
       
  2399         eq(EMPTYSTRING.join(lines), """\
       
  2400 
       
  2401 Hi,
       
  2402 
       
  2403 Do you like this message?
       
  2404 
       
  2405 -Me
       
  2406 """)
       
  2407 
       
  2408 
       
  2409 
       
  2410 class TestParsers(TestEmailBase):
       
  2411     def test_header_parser(self):
       
  2412         eq = self.assertEqual
       
  2413         # Parse only the headers of a complex multipart MIME document
       
  2414         fp = openfile('msg_02.txt')
       
  2415         try:
       
  2416             msg = HeaderParser().parse(fp)
       
  2417         finally:
       
  2418             fp.close()
       
  2419         eq(msg['from'], 'ppp-request@zzz.org')
       
  2420         eq(msg['to'], 'ppp@zzz.org')
       
  2421         eq(msg.get_content_type(), 'multipart/mixed')
       
  2422         self.failIf(msg.is_multipart())
       
  2423         self.failUnless(isinstance(msg.get_payload(), str))
       
  2424 
       
  2425     def test_whitespace_continuation(self):
       
  2426         eq = self.assertEqual
       
  2427         # This message contains a line after the Subject: header that has only
       
  2428         # whitespace, but it is not empty!
       
  2429         msg = email.message_from_string("""\
       
  2430 From: aperson@dom.ain
       
  2431 To: bperson@dom.ain
       
  2432 Subject: the next line has a space on it
       
  2433 \x20
       
  2434 Date: Mon, 8 Apr 2002 15:09:19 -0400
       
  2435 Message-ID: spam
       
  2436 
       
  2437 Here's the message body
       
  2438 """)
       
  2439         eq(msg['subject'], 'the next line has a space on it\n ')
       
  2440         eq(msg['message-id'], 'spam')
       
  2441         eq(msg.get_payload(), "Here's the message body\n")
       
  2442 
       
  2443     def test_whitespace_continuation_last_header(self):
       
  2444         eq = self.assertEqual
       
  2445         # Like the previous test, but the subject line is the last
       
  2446         # header.
       
  2447         msg = email.message_from_string("""\
       
  2448 From: aperson@dom.ain
       
  2449 To: bperson@dom.ain
       
  2450 Date: Mon, 8 Apr 2002 15:09:19 -0400
       
  2451 Message-ID: spam
       
  2452 Subject: the next line has a space on it
       
  2453 \x20
       
  2454 
       
  2455 Here's the message body
       
  2456 """)
       
  2457         eq(msg['subject'], 'the next line has a space on it\n ')
       
  2458         eq(msg['message-id'], 'spam')
       
  2459         eq(msg.get_payload(), "Here's the message body\n")
       
  2460 
       
  2461     def test_crlf_separation(self):
       
  2462         eq = self.assertEqual
       
  2463         fp = openfile('msg_26.txt', mode='rb')
       
  2464         try:
       
  2465             msg = Parser().parse(fp)
       
  2466         finally:
       
  2467             fp.close()
       
  2468         eq(len(msg.get_payload()), 2)
       
  2469         part1 = msg.get_payload(0)
       
  2470         eq(part1.get_content_type(), 'text/plain')
       
  2471         eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
       
  2472         part2 = msg.get_payload(1)
       
  2473         eq(part2.get_content_type(), 'application/riscos')
       
  2474 
       
  2475     def test_multipart_digest_with_extra_mime_headers(self):
       
  2476         eq = self.assertEqual
       
  2477         neq = self.ndiffAssertEqual
       
  2478         fp = openfile('msg_28.txt')
       
  2479         try:
       
  2480             msg = email.message_from_file(fp)
       
  2481         finally:
       
  2482             fp.close()
       
  2483         # Structure is:
       
  2484         # multipart/digest
       
  2485         #   message/rfc822
       
  2486         #     text/plain
       
  2487         #   message/rfc822
       
  2488         #     text/plain
       
  2489         eq(msg.is_multipart(), 1)
       
  2490         eq(len(msg.get_payload()), 2)
       
  2491         part1 = msg.get_payload(0)
       
  2492         eq(part1.get_content_type(), 'message/rfc822')
       
  2493         eq(part1.is_multipart(), 1)
       
  2494         eq(len(part1.get_payload()), 1)
       
  2495         part1a = part1.get_payload(0)
       
  2496         eq(part1a.is_multipart(), 0)
       
  2497         eq(part1a.get_content_type(), 'text/plain')
       
  2498         neq(part1a.get_payload(), 'message 1\n')
       
  2499         # next message/rfc822
       
  2500         part2 = msg.get_payload(1)
       
  2501         eq(part2.get_content_type(), 'message/rfc822')
       
  2502         eq(part2.is_multipart(), 1)
       
  2503         eq(len(part2.get_payload()), 1)
       
  2504         part2a = part2.get_payload(0)
       
  2505         eq(part2a.is_multipart(), 0)
       
  2506         eq(part2a.get_content_type(), 'text/plain')
       
  2507         neq(part2a.get_payload(), 'message 2\n')
       
  2508 
       
  2509     def test_three_lines(self):
       
  2510         # A bug report by Andrew McNamara
       
  2511         lines = ['From: Andrew Person <aperson@dom.ain',
       
  2512                  'Subject: Test',
       
  2513                  'Date: Tue, 20 Aug 2002 16:43:45 +1000']
       
  2514         msg = email.message_from_string(NL.join(lines))
       
  2515         self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
       
  2516 
       
  2517     def test_strip_line_feed_and_carriage_return_in_headers(self):
       
  2518         eq = self.assertEqual
       
  2519         # For [ 1002475 ] email message parser doesn't handle \r\n correctly
       
  2520         value1 = 'text'
       
  2521         value2 = 'more text'
       
  2522         m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
       
  2523             value1, value2)
       
  2524         msg = email.message_from_string(m)
       
  2525         eq(msg.get('Header'), value1)
       
  2526         eq(msg.get('Next-Header'), value2)
       
  2527 
       
  2528     def test_rfc2822_header_syntax(self):
       
  2529         eq = self.assertEqual
       
  2530         m = '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
       
  2531         msg = email.message_from_string(m)
       
  2532         eq(len(msg.keys()), 3)
       
  2533         keys = msg.keys()
       
  2534         keys.sort()
       
  2535         eq(keys, ['!"#QUX;~', '>From', 'From'])
       
  2536         eq(msg.get_payload(), 'body')
       
  2537 
       
  2538     def test_rfc2822_space_not_allowed_in_header(self):
       
  2539         eq = self.assertEqual
       
  2540         m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
       
  2541         msg = email.message_from_string(m)
       
  2542         eq(len(msg.keys()), 0)
       
  2543 
       
  2544     def test_rfc2822_one_character_header(self):
       
  2545         eq = self.assertEqual
       
  2546         m = 'A: first header\nB: second header\nCC: third header\n\nbody'
       
  2547         msg = email.message_from_string(m)
       
  2548         headers = msg.keys()
       
  2549         headers.sort()
       
  2550         eq(headers, ['A', 'B', 'CC'])
       
  2551         eq(msg.get_payload(), 'body')
       
  2552 
       
  2553 
       
  2554 
       
  2555 class TestBase64(unittest.TestCase):
       
  2556     def test_len(self):
       
  2557         eq = self.assertEqual
       
  2558         eq(base64MIME.base64_len('hello'),
       
  2559            len(base64MIME.encode('hello', eol='')))
       
  2560         for size in range(15):
       
  2561             if   size == 0 : bsize = 0
       
  2562             elif size <= 3 : bsize = 4
       
  2563             elif size <= 6 : bsize = 8
       
  2564             elif size <= 9 : bsize = 12
       
  2565             elif size <= 12: bsize = 16
       
  2566             else           : bsize = 20
       
  2567             eq(base64MIME.base64_len('x'*size), bsize)
       
  2568 
       
  2569     def test_decode(self):
       
  2570         eq = self.assertEqual
       
  2571         eq(base64MIME.decode(''), '')
       
  2572         eq(base64MIME.decode('aGVsbG8='), 'hello')
       
  2573         eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
       
  2574         eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
       
  2575 
       
  2576     def test_encode(self):
       
  2577         eq = self.assertEqual
       
  2578         eq(base64MIME.encode(''), '')
       
  2579         eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
       
  2580         # Test the binary flag
       
  2581         eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
       
  2582         eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
       
  2583         # Test the maxlinelen arg
       
  2584         eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
       
  2585 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
       
  2586 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
       
  2587 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
       
  2588 eHh4eCB4eHh4IA==
       
  2589 """)
       
  2590         # Test the eol argument
       
  2591         eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
       
  2592 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
       
  2593 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
       
  2594 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
       
  2595 eHh4eCB4eHh4IA==\r
       
  2596 """)
       
  2597 
       
  2598     def test_header_encode(self):
       
  2599         eq = self.assertEqual
       
  2600         he = base64MIME.header_encode
       
  2601         eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
       
  2602         eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
       
  2603         # Test the charset option
       
  2604         eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
       
  2605         # Test the keep_eols flag
       
  2606         eq(he('hello\nworld', keep_eols=True),
       
  2607            '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
       
  2608         # Test the maxlinelen argument
       
  2609         eq(he('xxxx ' * 20, maxlinelen=40), """\
       
  2610 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
       
  2611  =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
       
  2612  =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
       
  2613  =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
       
  2614  =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
       
  2615  =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
       
  2616         # Test the eol argument
       
  2617         eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
       
  2618 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
       
  2619  =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
       
  2620  =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
       
  2621  =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
       
  2622  =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
       
  2623  =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
       
  2624 
       
  2625 
       
  2626 
       
  2627 class TestQuopri(unittest.TestCase):
       
  2628     def setUp(self):
       
  2629         self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
       
  2630                     [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
       
  2631                     [chr(x) for x in range(ord('0'), ord('9')+1)] + \
       
  2632                     ['!', '*', '+', '-', '/', ' ']
       
  2633         self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
       
  2634         assert len(self.hlit) + len(self.hnon) == 256
       
  2635         self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
       
  2636         self.blit.remove('=')
       
  2637         self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
       
  2638         assert len(self.blit) + len(self.bnon) == 256
       
  2639 
       
  2640     def test_header_quopri_check(self):
       
  2641         for c in self.hlit:
       
  2642             self.failIf(quopriMIME.header_quopri_check(c))
       
  2643         for c in self.hnon:
       
  2644             self.failUnless(quopriMIME.header_quopri_check(c))
       
  2645 
       
  2646     def test_body_quopri_check(self):
       
  2647         for c in self.blit:
       
  2648             self.failIf(quopriMIME.body_quopri_check(c))
       
  2649         for c in self.bnon:
       
  2650             self.failUnless(quopriMIME.body_quopri_check(c))
       
  2651 
       
  2652     def test_header_quopri_len(self):
       
  2653         eq = self.assertEqual
       
  2654         hql = quopriMIME.header_quopri_len
       
  2655         enc = quopriMIME.header_encode
       
  2656         for s in ('hello', 'h@e@l@l@o@'):
       
  2657             # Empty charset and no line-endings.  7 == RFC chrome
       
  2658             eq(hql(s), len(enc(s, charset='', eol=''))-7)
       
  2659         for c in self.hlit:
       
  2660             eq(hql(c), 1)
       
  2661         for c in self.hnon:
       
  2662             eq(hql(c), 3)
       
  2663 
       
  2664     def test_body_quopri_len(self):
       
  2665         eq = self.assertEqual
       
  2666         bql = quopriMIME.body_quopri_len
       
  2667         for c in self.blit:
       
  2668             eq(bql(c), 1)
       
  2669         for c in self.bnon:
       
  2670             eq(bql(c), 3)
       
  2671 
       
  2672     def test_quote_unquote_idempotent(self):
       
  2673         for x in range(256):
       
  2674             c = chr(x)
       
  2675             self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
       
  2676 
       
  2677     def test_header_encode(self):
       
  2678         eq = self.assertEqual
       
  2679         he = quopriMIME.header_encode
       
  2680         eq(he('hello'), '=?iso-8859-1?q?hello?=')
       
  2681         eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
       
  2682         # Test the charset option
       
  2683         eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
       
  2684         # Test the keep_eols flag
       
  2685         eq(he('hello\nworld', keep_eols=True), '=?iso-8859-1?q?hello=0Aworld?=')
       
  2686         # Test a non-ASCII character
       
  2687         eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
       
  2688         # Test the maxlinelen argument
       
  2689         eq(he('xxxx ' * 20, maxlinelen=40), """\
       
  2690 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
       
  2691  =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
       
  2692  =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
       
  2693  =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
       
  2694  =?iso-8859-1?q?x_xxxx_xxxx_?=""")
       
  2695         # Test the eol argument
       
  2696         eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
       
  2697 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
       
  2698  =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
       
  2699  =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
       
  2700  =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
       
  2701  =?iso-8859-1?q?x_xxxx_xxxx_?=""")
       
  2702 
       
  2703     def test_decode(self):
       
  2704         eq = self.assertEqual
       
  2705         eq(quopriMIME.decode(''), '')
       
  2706         eq(quopriMIME.decode('hello'), 'hello')
       
  2707         eq(quopriMIME.decode('hello', 'X'), 'hello')
       
  2708         eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
       
  2709 
       
  2710     def test_encode(self):
       
  2711         eq = self.assertEqual
       
  2712         eq(quopriMIME.encode(''), '')
       
  2713         eq(quopriMIME.encode('hello'), 'hello')
       
  2714         # Test the binary flag
       
  2715         eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
       
  2716         eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
       
  2717         # Test the maxlinelen arg
       
  2718         eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
       
  2719 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
       
  2720  xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
       
  2721 x xxxx xxxx xxxx xxxx=20""")
       
  2722         # Test the eol argument
       
  2723         eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
       
  2724 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
       
  2725  xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
       
  2726 x xxxx xxxx xxxx xxxx=20""")
       
  2727         eq(quopriMIME.encode("""\
       
  2728 one line
       
  2729 
       
  2730 two line"""), """\
       
  2731 one line
       
  2732 
       
  2733 two line""")
       
  2734 
       
  2735 
       
  2736 
       
  2737 # Test the Charset class
       
  2738 class TestCharset(unittest.TestCase):
       
  2739     def tearDown(self):
       
  2740         from email import Charset as CharsetModule
       
  2741         try:
       
  2742             del CharsetModule.CHARSETS['fake']
       
  2743         except KeyError:
       
  2744             pass
       
  2745 
       
  2746     def test_idempotent(self):
       
  2747         eq = self.assertEqual
       
  2748         # Make sure us-ascii = no Unicode conversion
       
  2749         c = Charset('us-ascii')
       
  2750         s = 'Hello World!'
       
  2751         sp = c.to_splittable(s)
       
  2752         eq(s, c.from_splittable(sp))
       
  2753         # test 8-bit idempotency with us-ascii
       
  2754         s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
       
  2755         sp = c.to_splittable(s)
       
  2756         eq(s, c.from_splittable(sp))
       
  2757 
       
  2758     def test_body_encode(self):
       
  2759         eq = self.assertEqual
       
  2760         # Try a charset with QP body encoding
       
  2761         c = Charset('iso-8859-1')
       
  2762         eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
       
  2763         # Try a charset with Base64 body encoding
       
  2764         c = Charset('utf-8')
       
  2765         eq('aGVsbG8gd29ybGQ=\n', c.body_encode('hello world'))
       
  2766         # Try a charset with None body encoding
       
  2767         c = Charset('us-ascii')
       
  2768         eq('hello world', c.body_encode('hello world'))
       
  2769         # Try the convert argument, where input codec <> output codec
       
  2770         c = Charset('euc-jp')
       
  2771         # With apologies to Tokio Kikuchi ;)
       
  2772         try:
       
  2773             eq('\x1b$B5FCO;~IW\x1b(B',
       
  2774                c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
       
  2775             eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
       
  2776                c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
       
  2777         except LookupError:
       
  2778             # We probably don't have the Japanese codecs installed
       
  2779             pass
       
  2780         # Testing SF bug #625509, which we have to fake, since there are no
       
  2781         # built-in encodings where the header encoding is QP but the body
       
  2782         # encoding is not.
       
  2783         from email import Charset as CharsetModule
       
  2784         CharsetModule.add_charset('fake', CharsetModule.QP, None)
       
  2785         c = Charset('fake')
       
  2786         eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
       
  2787 
       
  2788     def test_unicode_charset_name(self):
       
  2789         charset = Charset(u'us-ascii')
       
  2790         self.assertEqual(str(charset), 'us-ascii')
       
  2791         self.assertRaises(Errors.CharsetError, Charset, 'asc\xffii')
       
  2792 
       
  2793 
       
  2794 
       
  2795 # Test multilingual MIME headers.
       
  2796 class TestHeader(TestEmailBase):
       
  2797     def test_simple(self):
       
  2798         eq = self.ndiffAssertEqual
       
  2799         h = Header('Hello World!')
       
  2800         eq(h.encode(), 'Hello World!')
       
  2801         h.append(' Goodbye World!')
       
  2802         eq(h.encode(), 'Hello World!  Goodbye World!')
       
  2803 
       
  2804     def test_simple_surprise(self):
       
  2805         eq = self.ndiffAssertEqual
       
  2806         h = Header('Hello World!')
       
  2807         eq(h.encode(), 'Hello World!')
       
  2808         h.append('Goodbye World!')
       
  2809         eq(h.encode(), 'Hello World! Goodbye World!')
       
  2810 
       
  2811     def test_header_needs_no_decoding(self):
       
  2812         h = 'no decoding needed'
       
  2813         self.assertEqual(decode_header(h), [(h, None)])
       
  2814 
       
  2815     def test_long(self):
       
  2816         h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
       
  2817                    maxlinelen=76)
       
  2818         for l in h.encode(splitchars=' ').split('\n '):
       
  2819             self.failUnless(len(l) <= 76)
       
  2820 
       
  2821     def test_multilingual(self):
       
  2822         eq = self.ndiffAssertEqual
       
  2823         g = Charset("iso-8859-1")
       
  2824         cz = Charset("iso-8859-2")
       
  2825         utf8 = Charset("utf-8")
       
  2826         g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
       
  2827         cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
       
  2828         utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
       
  2829         h = Header(g_head, g)
       
  2830         h.append(cz_head, cz)
       
  2831         h.append(utf8_head, utf8)
       
  2832         enc = h.encode()
       
  2833         eq(enc, """\
       
  2834 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_ko?=
       
  2835  =?iso-8859-1?q?mfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wan?=
       
  2836  =?iso-8859-1?q?dgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6?=
       
  2837  =?iso-8859-1?q?rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
       
  2838  =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
       
  2839  =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
       
  2840  =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
       
  2841  =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
       
  2842  =?utf-8?q?_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das_Oder_die_Fl?=
       
  2843  =?utf-8?b?aXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBo+OBpuOBhOOBvuOBmQ==?=
       
  2844  =?utf-8?b?44CC?=""")
       
  2845         eq(decode_header(enc),
       
  2846            [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
       
  2847             (utf8_head, "utf-8")])
       
  2848         ustr = unicode(h)
       
  2849         eq(ustr.encode('utf-8'),
       
  2850            'Die Mieter treten hier ein werden mit einem Foerderband '
       
  2851            'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
       
  2852            'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
       
  2853            'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
       
  2854            'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
       
  2855            '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
       
  2856            '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
       
  2857            '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
       
  2858            '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
       
  2859            '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
       
  2860            '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
       
  2861            '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
       
  2862            '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
       
  2863            'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
       
  2864            'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
       
  2865            '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
       
  2866         # Test make_header()
       
  2867         newh = make_header(decode_header(enc))
       
  2868         eq(newh, enc)
       
  2869 
       
  2870     def test_header_ctor_default_args(self):
       
  2871         eq = self.ndiffAssertEqual
       
  2872         h = Header()
       
  2873         eq(h, '')
       
  2874         h.append('foo', Charset('iso-8859-1'))
       
  2875         eq(h, '=?iso-8859-1?q?foo?=')
       
  2876 
       
  2877     def test_explicit_maxlinelen(self):
       
  2878         eq = self.ndiffAssertEqual
       
  2879         hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
       
  2880         h = Header(hstr)
       
  2881         eq(h.encode(), '''\
       
  2882 A very long line that must get split to something other than at the 76th
       
  2883  character boundary to test the non-default behavior''')
       
  2884         h = Header(hstr, header_name='Subject')
       
  2885         eq(h.encode(), '''\
       
  2886 A very long line that must get split to something other than at the
       
  2887  76th character boundary to test the non-default behavior''')
       
  2888         h = Header(hstr, maxlinelen=1024, header_name='Subject')
       
  2889         eq(h.encode(), hstr)
       
  2890 
       
  2891     def test_us_ascii_header(self):
       
  2892         eq = self.assertEqual
       
  2893         s = 'hello'
       
  2894         x = decode_header(s)
       
  2895         eq(x, [('hello', None)])
       
  2896         h = make_header(x)
       
  2897         eq(s, h.encode())
       
  2898 
       
  2899     def test_string_charset(self):
       
  2900         eq = self.assertEqual
       
  2901         h = Header()
       
  2902         h.append('hello', 'iso-8859-1')
       
  2903         eq(h, '=?iso-8859-1?q?hello?=')
       
  2904 
       
  2905 ##    def test_unicode_error(self):
       
  2906 ##        raises = self.assertRaises
       
  2907 ##        raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
       
  2908 ##        raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
       
  2909 ##        h = Header()
       
  2910 ##        raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
       
  2911 ##        raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
       
  2912 ##        raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
       
  2913 
       
  2914     def test_utf8_shortest(self):
       
  2915         eq = self.assertEqual
       
  2916         h = Header(u'p\xf6stal', 'utf-8')
       
  2917         eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
       
  2918         h = Header(u'\u83ca\u5730\u6642\u592b', 'utf-8')
       
  2919         eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
       
  2920 
       
  2921     def test_bad_8bit_header(self):
       
  2922         raises = self.assertRaises
       
  2923         eq = self.assertEqual
       
  2924         x = 'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
       
  2925         raises(UnicodeError, Header, x)
       
  2926         h = Header()
       
  2927         raises(UnicodeError, h.append, x)
       
  2928         eq(str(Header(x, errors='replace')), x)
       
  2929         h.append(x, errors='replace')
       
  2930         eq(str(h), x)
       
  2931 
       
  2932     def test_encoded_adjacent_nonencoded(self):
       
  2933         eq = self.assertEqual
       
  2934         h = Header()
       
  2935         h.append('hello', 'iso-8859-1')
       
  2936         h.append('world')
       
  2937         s = h.encode()
       
  2938         eq(s, '=?iso-8859-1?q?hello?= world')
       
  2939         h = make_header(decode_header(s))
       
  2940         eq(h.encode(), s)
       
  2941 
       
  2942     def test_whitespace_eater(self):
       
  2943         eq = self.assertEqual
       
  2944         s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
       
  2945         parts = decode_header(s)
       
  2946         eq(parts, [('Subject:', None), ('\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), ('zz.', None)])
       
  2947         hdr = make_header(parts)
       
  2948         eq(hdr.encode(),
       
  2949            'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
       
  2950 
       
  2951     def test_broken_base64_header(self):
       
  2952         raises = self.assertRaises
       
  2953         s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ?='
       
  2954         raises(Errors.HeaderParseError, decode_header, s)
       
  2955 
       
  2956 
       
  2957 
       
  2958 # Test RFC 2231 header parameters (en/de)coding
       
  2959 class TestRFC2231(TestEmailBase):
       
  2960     def test_get_param(self):
       
  2961         eq = self.assertEqual
       
  2962         msg = self._msgobj('msg_29.txt')
       
  2963         eq(msg.get_param('title'),
       
  2964            ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
       
  2965         eq(msg.get_param('title', unquote=False),
       
  2966            ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
       
  2967 
       
  2968     def test_set_param(self):
       
  2969         eq = self.assertEqual
       
  2970         msg = Message()
       
  2971         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
       
  2972                       charset='us-ascii')
       
  2973         eq(msg.get_param('title'),
       
  2974            ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
       
  2975         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
       
  2976                       charset='us-ascii', language='en')
       
  2977         eq(msg.get_param('title'),
       
  2978            ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
       
  2979         msg = self._msgobj('msg_01.txt')
       
  2980         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
       
  2981                       charset='us-ascii', language='en')
       
  2982         eq(msg.as_string(), """\
       
  2983 Return-Path: <bbb@zzz.org>
       
  2984 Delivered-To: bbb@zzz.org
       
  2985 Received: by mail.zzz.org (Postfix, from userid 889)
       
  2986 \tid 27CEAD38CC; Fri,  4 May 2001 14:05:44 -0400 (EDT)
       
  2987 MIME-Version: 1.0
       
  2988 Content-Transfer-Encoding: 7bit
       
  2989 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
       
  2990 From: bbb@ddd.com (John X. Doe)
       
  2991 To: bbb@zzz.org
       
  2992 Subject: This is a test message
       
  2993 Date: Fri, 4 May 2001 14:05:44 -0400
       
  2994 Content-Type: text/plain; charset=us-ascii;
       
  2995 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
       
  2996 
       
  2997 
       
  2998 Hi,
       
  2999 
       
  3000 Do you like this message?
       
  3001 
       
  3002 -Me
       
  3003 """)
       
  3004 
       
  3005     def test_del_param(self):
       
  3006         eq = self.ndiffAssertEqual
       
  3007         msg = self._msgobj('msg_01.txt')
       
  3008         msg.set_param('foo', 'bar', charset='us-ascii', language='en')
       
  3009         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
       
  3010             charset='us-ascii', language='en')
       
  3011         msg.del_param('foo', header='Content-Type')
       
  3012         eq(msg.as_string(), """\
       
  3013 Return-Path: <bbb@zzz.org>
       
  3014 Delivered-To: bbb@zzz.org
       
  3015 Received: by mail.zzz.org (Postfix, from userid 889)
       
  3016 \tid 27CEAD38CC; Fri,  4 May 2001 14:05:44 -0400 (EDT)
       
  3017 MIME-Version: 1.0
       
  3018 Content-Transfer-Encoding: 7bit
       
  3019 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
       
  3020 From: bbb@ddd.com (John X. Doe)
       
  3021 To: bbb@zzz.org
       
  3022 Subject: This is a test message
       
  3023 Date: Fri, 4 May 2001 14:05:44 -0400
       
  3024 Content-Type: text/plain; charset="us-ascii";
       
  3025 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
       
  3026 
       
  3027 
       
  3028 Hi,
       
  3029 
       
  3030 Do you like this message?
       
  3031 
       
  3032 -Me
       
  3033 """)
       
  3034 
       
  3035     def test_rfc2231_get_content_charset(self):
       
  3036         eq = self.assertEqual
       
  3037         msg = self._msgobj('msg_32.txt')
       
  3038         eq(msg.get_content_charset(), 'us-ascii')
       
  3039 
       
  3040     def test_rfc2231_no_language_or_charset(self):
       
  3041         m = '''\
       
  3042 Content-Transfer-Encoding: 8bit
       
  3043 Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
       
  3044 Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
       
  3045 
       
  3046 '''
       
  3047         msg = email.message_from_string(m)
       
  3048         param = msg.get_param('NAME')
       
  3049         self.failIf(isinstance(param, tuple))
       
  3050         self.assertEqual(
       
  3051             param,
       
  3052             'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
       
  3053 
       
  3054     def test_rfc2231_no_language_or_charset_in_filename(self):
       
  3055         m = '''\
       
  3056 Content-Disposition: inline;
       
  3057 \tfilename*0*="''This%20is%20even%20more%20";
       
  3058 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3059 \tfilename*2="is it not.pdf"
       
  3060 
       
  3061 '''
       
  3062         msg = email.message_from_string(m)
       
  3063         self.assertEqual(msg.get_filename(),
       
  3064                          'This is even more ***fun*** is it not.pdf')
       
  3065 
       
  3066     def test_rfc2231_no_language_or_charset_in_filename_encoded(self):
       
  3067         m = '''\
       
  3068 Content-Disposition: inline;
       
  3069 \tfilename*0*="''This%20is%20even%20more%20";
       
  3070 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3071 \tfilename*2="is it not.pdf"
       
  3072 
       
  3073 '''
       
  3074         msg = email.message_from_string(m)
       
  3075         self.assertEqual(msg.get_filename(),
       
  3076                          'This is even more ***fun*** is it not.pdf')
       
  3077 
       
  3078     def test_rfc2231_partly_encoded(self):
       
  3079         m = '''\
       
  3080 Content-Disposition: inline;
       
  3081 \tfilename*0="''This%20is%20even%20more%20";
       
  3082 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3083 \tfilename*2="is it not.pdf"
       
  3084 
       
  3085 '''
       
  3086         msg = email.message_from_string(m)
       
  3087         self.assertEqual(
       
  3088             msg.get_filename(),
       
  3089             'This%20is%20even%20more%20***fun*** is it not.pdf')
       
  3090 
       
  3091     def test_rfc2231_partly_nonencoded(self):
       
  3092         m = '''\
       
  3093 Content-Disposition: inline;
       
  3094 \tfilename*0="This%20is%20even%20more%20";
       
  3095 \tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
       
  3096 \tfilename*2="is it not.pdf"
       
  3097 
       
  3098 '''
       
  3099         msg = email.message_from_string(m)
       
  3100         self.assertEqual(
       
  3101             msg.get_filename(),
       
  3102             'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
       
  3103 
       
  3104     def test_rfc2231_no_language_or_charset_in_boundary(self):
       
  3105         m = '''\
       
  3106 Content-Type: multipart/alternative;
       
  3107 \tboundary*0*="''This%20is%20even%20more%20";
       
  3108 \tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3109 \tboundary*2="is it not.pdf"
       
  3110 
       
  3111 '''
       
  3112         msg = email.message_from_string(m)
       
  3113         self.assertEqual(msg.get_boundary(),
       
  3114                          'This is even more ***fun*** is it not.pdf')
       
  3115 
       
  3116     def test_rfc2231_no_language_or_charset_in_charset(self):
       
  3117         # This is a nonsensical charset value, but tests the code anyway
       
  3118         m = '''\
       
  3119 Content-Type: text/plain;
       
  3120 \tcharset*0*="This%20is%20even%20more%20";
       
  3121 \tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3122 \tcharset*2="is it not.pdf"
       
  3123 
       
  3124 '''
       
  3125         msg = email.message_from_string(m)
       
  3126         self.assertEqual(msg.get_content_charset(),
       
  3127                          'this is even more ***fun*** is it not.pdf')
       
  3128 
       
  3129     def test_rfc2231_bad_encoding_in_filename(self):
       
  3130         m = '''\
       
  3131 Content-Disposition: inline;
       
  3132 \tfilename*0*="bogus'xx'This%20is%20even%20more%20";
       
  3133 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3134 \tfilename*2="is it not.pdf"
       
  3135 
       
  3136 '''
       
  3137         msg = email.message_from_string(m)
       
  3138         self.assertEqual(msg.get_filename(),
       
  3139                          'This is even more ***fun*** is it not.pdf')
       
  3140 
       
  3141     def test_rfc2231_bad_encoding_in_charset(self):
       
  3142         m = """\
       
  3143 Content-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
       
  3144 
       
  3145 """
       
  3146         msg = email.message_from_string(m)
       
  3147         # This should return None because non-ascii characters in the charset
       
  3148         # are not allowed.
       
  3149         self.assertEqual(msg.get_content_charset(), None)
       
  3150 
       
  3151     def test_rfc2231_bad_character_in_charset(self):
       
  3152         m = """\
       
  3153 Content-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
       
  3154 
       
  3155 """
       
  3156         msg = email.message_from_string(m)
       
  3157         # This should return None because non-ascii characters in the charset
       
  3158         # are not allowed.
       
  3159         self.assertEqual(msg.get_content_charset(), None)
       
  3160 
       
  3161     def test_rfc2231_bad_character_in_filename(self):
       
  3162         m = '''\
       
  3163 Content-Disposition: inline;
       
  3164 \tfilename*0*="ascii'xx'This%20is%20even%20more%20";
       
  3165 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
       
  3166 \tfilename*2*="is it not.pdf%E2"
       
  3167 
       
  3168 '''
       
  3169         msg = email.message_from_string(m)
       
  3170         self.assertEqual(msg.get_filename(),
       
  3171                          u'This is even more ***fun*** is it not.pdf\ufffd')
       
  3172 
       
  3173     def test_rfc2231_unknown_encoding(self):
       
  3174         m = """\
       
  3175 Content-Transfer-Encoding: 8bit
       
  3176 Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
       
  3177 
       
  3178 """
       
  3179         msg = email.message_from_string(m)
       
  3180         self.assertEqual(msg.get_filename(), 'myfile.txt')
       
  3181 
       
  3182     def test_rfc2231_single_tick_in_filename_extended(self):
       
  3183         eq = self.assertEqual
       
  3184         m = """\
       
  3185 Content-Type: application/x-foo;
       
  3186 \tname*0*=\"Frank's\"; name*1*=\" Document\"
       
  3187 
       
  3188 """
       
  3189         msg = email.message_from_string(m)
       
  3190         charset, language, s = msg.get_param('name')
       
  3191         eq(charset, None)
       
  3192         eq(language, None)
       
  3193         eq(s, "Frank's Document")
       
  3194 
       
  3195     def test_rfc2231_single_tick_in_filename(self):
       
  3196         m = """\
       
  3197 Content-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
       
  3198 
       
  3199 """
       
  3200         msg = email.message_from_string(m)
       
  3201         param = msg.get_param('name')
       
  3202         self.failIf(isinstance(param, tuple))
       
  3203         self.assertEqual(param, "Frank's Document")
       
  3204 
       
  3205     def test_rfc2231_tick_attack_extended(self):
       
  3206         eq = self.assertEqual
       
  3207         m = """\
       
  3208 Content-Type: application/x-foo;
       
  3209 \tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
       
  3210 
       
  3211 """
       
  3212         msg = email.message_from_string(m)
       
  3213         charset, language, s = msg.get_param('name')
       
  3214         eq(charset, 'us-ascii')
       
  3215         eq(language, 'en-us')
       
  3216         eq(s, "Frank's Document")
       
  3217 
       
  3218     def test_rfc2231_tick_attack(self):
       
  3219         m = """\
       
  3220 Content-Type: application/x-foo;
       
  3221 \tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
       
  3222 
       
  3223 """
       
  3224         msg = email.message_from_string(m)
       
  3225         param = msg.get_param('name')
       
  3226         self.failIf(isinstance(param, tuple))
       
  3227         self.assertEqual(param, "us-ascii'en-us'Frank's Document")
       
  3228 
       
  3229     def test_rfc2231_no_extended_values(self):
       
  3230         eq = self.assertEqual
       
  3231         m = """\
       
  3232 Content-Type: application/x-foo; name=\"Frank's Document\"
       
  3233 
       
  3234 """
       
  3235         msg = email.message_from_string(m)
       
  3236         eq(msg.get_param('name'), "Frank's Document")
       
  3237 
       
  3238     def test_rfc2231_encoded_then_unencoded_segments(self):
       
  3239         eq = self.assertEqual
       
  3240         m = """\
       
  3241 Content-Type: application/x-foo;
       
  3242 \tname*0*=\"us-ascii'en-us'My\";
       
  3243 \tname*1=\" Document\";
       
  3244 \tname*2*=\" For You\"
       
  3245 
       
  3246 """
       
  3247         msg = email.message_from_string(m)
       
  3248         charset, language, s = msg.get_param('name')
       
  3249         eq(charset, 'us-ascii')
       
  3250         eq(language, 'en-us')
       
  3251         eq(s, 'My Document For You')
       
  3252 
       
  3253     def test_rfc2231_unencoded_then_encoded_segments(self):
       
  3254         eq = self.assertEqual
       
  3255         m = """\
       
  3256 Content-Type: application/x-foo;
       
  3257 \tname*0=\"us-ascii'en-us'My\";
       
  3258 \tname*1*=\" Document\";
       
  3259 \tname*2*=\" For You\"
       
  3260 
       
  3261 """
       
  3262         msg = email.message_from_string(m)
       
  3263         charset, language, s = msg.get_param('name')
       
  3264         eq(charset, 'us-ascii')
       
  3265         eq(language, 'en-us')
       
  3266         eq(s, 'My Document For You')
       
  3267 
       
  3268 
       
  3269 
       
  3270 def _testclasses():
       
  3271     mod = sys.modules[__name__]
       
  3272     return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
       
  3273 
       
  3274 
       
  3275 def suite():
       
  3276     suite = unittest.TestSuite()
       
  3277     for testclass in _testclasses():
       
  3278         suite.addTest(unittest.makeSuite(testclass))
       
  3279     return suite
       
  3280 
       
  3281 
       
  3282 def test_main():
       
  3283     for testclass in _testclasses():
       
  3284         run_unittest(testclass)
       
  3285 
       
  3286 
       
  3287 
       
  3288 if __name__ == '__main__':
       
  3289     unittest.main(defaultTest='suite')