PageRenderTime 1321ms CodeModel.GetById 164ms app.highlight 742ms RepoModel.GetById 228ms app.codeStats 1ms

/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

Large files files are truncated, but you can click here to view the full 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 believ…

Large files files are truncated, but you can click here to view the full file