PageRenderTime 82ms CodeModel.GetById 121ms app.highlight 866ms RepoModel.GetById 119ms app.codeStats 2ms

/Lib/email/test/test_email.py

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