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