home *** CD-ROM | disk | FTP | other *** search
/ Freelog 33 / Freelog033.iso / Progr / Python-2.2.1.exe / TEST_EMAIL.PY < prev    next >
Encoding:
Python Source  |  2002-03-25  |  38.4 KB  |  1,133 lines

  1. # Copyright (C) 2001,2002 Python Software Foundation
  2. # email package unit tests
  3.  
  4. import os
  5. import time
  6. import unittest
  7. import base64
  8. from cStringIO import StringIO
  9. from types import StringType
  10.  
  11. import email
  12.  
  13. from email.Parser import Parser, HeaderParser
  14. from email.Generator import Generator, DecodedGenerator
  15. from email.Message import Message
  16. from email.MIMEAudio import MIMEAudio
  17. from email.MIMEText import MIMEText
  18. from email.MIMEImage import MIMEImage
  19. from email.MIMEBase import MIMEBase
  20. from email.MIMEMessage import MIMEMessage
  21. from email import Utils
  22. from email import Errors
  23. from email import Encoders
  24. from email import Iterators
  25.  
  26. from test_support import findfile, __file__ as test_support_file
  27.  
  28.  
  29. NL = '\n'
  30. EMPTYSTRING = ''
  31. SPACE = ' '
  32.  
  33.  
  34.  
  35. def openfile(filename):
  36.     path = os.path.join(os.path.dirname(test_support_file), 'data', filename)
  37.     return open(path)
  38.  
  39.  
  40.  
  41. # Base test class
  42. class TestEmailBase(unittest.TestCase):
  43.     def _msgobj(self, filename):
  44.         fp = openfile(filename)
  45.         try:
  46.             msg = email.message_from_file(fp)
  47.         finally:
  48.             fp.close()
  49.         return msg
  50.  
  51.  
  52.  
  53. # Test various aspects of the Message class's API
  54. class TestMessageAPI(TestEmailBase):
  55.     def test_get_all(self):
  56.         eq = self.assertEqual
  57.         msg = self._msgobj('msg_20.txt')
  58.         eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
  59.         eq(msg.get_all('xx', 'n/a'), 'n/a')
  60.  
  61.     def test_get_charsets(self):
  62.         eq = self.assertEqual
  63.  
  64.         msg = self._msgobj('msg_08.txt')
  65.         charsets = msg.get_charsets()
  66.         eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
  67.  
  68.         msg = self._msgobj('msg_09.txt')
  69.         charsets = msg.get_charsets('dingbat')
  70.         eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
  71.                       'koi8-r'])
  72.  
  73.         msg = self._msgobj('msg_12.txt')
  74.         charsets = msg.get_charsets()
  75.         eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
  76.                       'iso-8859-3', 'us-ascii', 'koi8-r'])
  77.  
  78.     def test_get_filename(self):
  79.         eq = self.assertEqual
  80.  
  81.         msg = self._msgobj('msg_04.txt')
  82.         filenames = [p.get_filename() for p in msg.get_payload()]
  83.         eq(filenames, ['msg.txt', 'msg.txt'])
  84.  
  85.         msg = self._msgobj('msg_07.txt')
  86.         subpart = msg.get_payload(1)
  87.         eq(subpart.get_filename(), 'dingusfish.gif')
  88.  
  89.     def test_get_boundary(self):
  90.         eq = self.assertEqual
  91.         msg = self._msgobj('msg_07.txt')
  92.         # No quotes!
  93.         eq(msg.get_boundary(), 'BOUNDARY')
  94.  
  95.     def test_set_boundary(self):
  96.         eq = self.assertEqual
  97.         # This one has no existing boundary parameter, but the Content-Type:
  98.         # header appears fifth.
  99.         msg = self._msgobj('msg_01.txt')
  100.         msg.set_boundary('BOUNDARY')
  101.         header, value = msg.items()[4]
  102.         eq(header.lower(), 'content-type')
  103.         eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
  104.         # This one has a Content-Type: header, with a boundary, stuck in the
  105.         # middle of its headers.  Make sure the order is preserved; it should
  106.         # be fifth.
  107.         msg = self._msgobj('msg_04.txt')
  108.         msg.set_boundary('BOUNDARY')
  109.         header, value = msg.items()[4]
  110.         eq(header.lower(), 'content-type')
  111.         eq(value, 'multipart/mixed; boundary="BOUNDARY"')
  112.         # And this one has no Content-Type: header at all.
  113.         msg = self._msgobj('msg_03.txt')
  114.         self.assertRaises(Errors.HeaderParseError,
  115.                           msg.set_boundary, 'BOUNDARY')
  116.  
  117.     def test_get_decoded_payload(self):
  118.         eq = self.assertEqual
  119.         msg = self._msgobj('msg_10.txt')
  120.         # The outer message is a multipart
  121.         eq(msg.get_payload(decode=1), None)
  122.         # Subpart 1 is 7bit encoded
  123.         eq(msg.get_payload(0).get_payload(decode=1),
  124.            'This is a 7bit encoded message.\n')
  125.         # Subpart 2 is quopri
  126.         eq(msg.get_payload(1).get_payload(decode=1),
  127.            '\xa1This is a Quoted Printable encoded message!\n')
  128.         # Subpart 3 is base64
  129.         eq(msg.get_payload(2).get_payload(decode=1),
  130.            'This is a Base64 encoded message.')
  131.         # Subpart 4 has no Content-Transfer-Encoding: header.
  132.         eq(msg.get_payload(3).get_payload(decode=1),
  133.            'This has no Content-Transfer-Encoding: header.\n')
  134.  
  135.     def test_decoded_generator(self):
  136.         eq = self.assertEqual
  137.         msg = self._msgobj('msg_07.txt')
  138.         fp = openfile('msg_17.txt')
  139.         try:
  140.             text = fp.read()
  141.         finally:
  142.             fp.close()
  143.         s = StringIO()
  144.         g = DecodedGenerator(s)
  145.         g(msg)
  146.         eq(s.getvalue(), text)
  147.  
  148.     def test__contains__(self):
  149.         msg = Message()
  150.         msg['From'] = 'Me'
  151.         msg['to'] = 'You'
  152.         # Check for case insensitivity
  153.         self.failUnless('from' in msg)
  154.         self.failUnless('From' in msg)
  155.         self.failUnless('FROM' in msg)
  156.         self.failUnless('to' in msg)
  157.         self.failUnless('To' in msg)
  158.         self.failUnless('TO' in msg)
  159.  
  160.     def test_as_string(self):
  161.         eq = self.assertEqual
  162.         msg = self._msgobj('msg_01.txt')
  163.         fp = openfile('msg_01.txt')
  164.         try:
  165.             text = fp.read()
  166.         finally:
  167.             fp.close()
  168.         eq(text, msg.as_string())
  169.         fullrepr = str(msg)
  170.         lines = fullrepr.split('\n')
  171.         self.failUnless(lines[0].startswith('From '))
  172.         eq(text, NL.join(lines[1:]))
  173.  
  174.     def test_bad_param(self):
  175.         msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
  176.         self.assertEqual(msg.get_param('baz'), '')
  177.  
  178.     def test_missing_filename(self):
  179.         msg = email.message_from_string("From: foo\n")
  180.         self.assertEqual(msg.get_filename(), None)
  181.  
  182.     def test_bogus_filename(self):
  183.         msg = email.message_from_string(
  184.         "Content-Disposition: blarg; filename\n")
  185.         self.assertEqual(msg.get_filename(), '')
  186.  
  187.     def test_missing_boundary(self):
  188.         msg = email.message_from_string("From: foo\n")
  189.         self.assertEqual(msg.get_boundary(), None)
  190.  
  191.     def test_get_params(self):
  192.         eq = self.assertEqual
  193.         msg = email.message_from_string(
  194.             'X-Header: foo=one; bar=two; baz=three\n')
  195.         eq(msg.get_params(header='x-header'),
  196.            [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
  197.         msg = email.message_from_string(
  198.             'X-Header: foo; bar=one; baz=two\n')
  199.         eq(msg.get_params(header='x-header'),
  200.            [('foo', ''), ('bar', 'one'), ('baz', 'two')])
  201.         eq(msg.get_params(), None)
  202.         msg = email.message_from_string(
  203.             'X-Header: foo; bar="one"; baz=two\n')
  204.         eq(msg.get_params(header='x-header'),
  205.            [('foo', ''), ('bar', 'one'), ('baz', 'two')])
  206.  
  207.     def test_get_param(self):
  208.         eq = self.assertEqual
  209.         msg = email.message_from_string(
  210.             "X-Header: foo=one; bar=two; baz=three\n")
  211.         eq(msg.get_param('bar', header='x-header'), 'two')
  212.         eq(msg.get_param('quuz', header='x-header'), None)
  213.         eq(msg.get_param('quuz'), None)
  214.         msg = email.message_from_string(
  215.             'X-Header: foo; bar="one"; baz=two\n')
  216.         eq(msg.get_param('foo', header='x-header'), '')
  217.         eq(msg.get_param('bar', header='x-header'), 'one')
  218.         eq(msg.get_param('baz', header='x-header'), 'two')
  219.  
  220.     def test_get_param_funky_continuation_lines(self):
  221.         msg = self._msgobj('msg_22.txt')
  222.         self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
  223.  
  224.     def test_has_key(self):
  225.         msg = email.message_from_string('Header: exists')
  226.         self.failUnless(msg.has_key('header'))
  227.         self.failUnless(msg.has_key('Header'))
  228.         self.failUnless(msg.has_key('HEADER'))
  229.         self.failIf(msg.has_key('headeri'))
  230.  
  231.  
  232.  
  233. # Test the email.Encoders module
  234. class TestEncoders(unittest.TestCase):
  235.     def test_encode_noop(self):
  236.         eq = self.assertEqual
  237.         msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
  238.         eq(msg.get_payload(), 'hello world\n')
  239.         eq(msg['content-transfer-encoding'], None)
  240.  
  241.     def test_encode_7bit(self):
  242.         eq = self.assertEqual
  243.         msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
  244.         eq(msg.get_payload(), 'hello world\n')
  245.         eq(msg['content-transfer-encoding'], '7bit')
  246.         msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
  247.         eq(msg.get_payload(), 'hello \x7f world\n')
  248.         eq(msg['content-transfer-encoding'], '7bit')
  249.  
  250.     def test_encode_8bit(self):
  251.         eq = self.assertEqual
  252.         msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
  253.         eq(msg.get_payload(), 'hello \x80 world\n')
  254.         eq(msg['content-transfer-encoding'], '8bit')
  255.  
  256.     def test_encode_base64(self):
  257.         eq = self.assertEqual
  258.         msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
  259.         eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
  260.         eq(msg['content-transfer-encoding'], 'base64')
  261.  
  262.     def test_encode_quoted_printable(self):
  263.         eq = self.assertEqual
  264.         msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
  265.         eq(msg.get_payload(), 'hello=20world\n')
  266.         eq(msg['content-transfer-encoding'], 'quoted-printable')
  267.  
  268.  
  269.  
  270. # Test long header wrapping
  271. class TestLongHeaders(unittest.TestCase):
  272.     def test_header_splitter(self):
  273.         msg = MIMEText('')
  274.         # It'd be great if we could use add_header() here, but that doesn't
  275.         # guarantee an order of the parameters.
  276.         msg['X-Foobar-Spoink-Defrobnit'] = (
  277.             'wasnipoop; giraffes="very-long-necked-animals"; '
  278.             'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
  279.         sfp = StringIO()
  280.         g = Generator(sfp)
  281.         g(msg)
  282.         self.assertEqual(sfp.getvalue(), openfile('msg_18.txt').read())
  283.  
  284.     def test_no_semis_header_splitter(self):
  285.         msg = Message()
  286.         msg['From'] = 'test@dom.ain'
  287.         refparts = []
  288.         for i in range(10):
  289.             refparts.append('<%d@dom.ain>' % i)
  290.         msg['References'] = SPACE.join(refparts)
  291.         msg.set_payload('Test')
  292.         sfp = StringIO()
  293.         g = Generator(sfp)
  294.         g(msg)
  295.         self.assertEqual(sfp.getvalue(), """\
  296. From: test@dom.ain
  297. References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
  298. \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
  299.  
  300. Test""")
  301.  
  302.     def test_no_split_long_header(self):
  303.         msg = Message()
  304.         msg['From'] = 'test@dom.ain'
  305.         refparts = []
  306.         msg['References'] = 'x' * 80
  307.         msg.set_payload('Test')
  308.         sfp = StringIO()
  309.         g = Generator(sfp)
  310.         g(msg)
  311.         self.assertEqual(sfp.getvalue(), """\
  312. From: test@dom.ain
  313. References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  314.  
  315. Test""")
  316.  
  317.  
  318.  
  319. # Test mangling of "From " lines in the body of a message
  320. class TestFromMangling(unittest.TestCase):
  321.     def setUp(self):
  322.         self.msg = Message()
  323.         self.msg['From'] = 'aaa@bbb.org'
  324.         self.msg.add_payload("""\
  325. From the desk of A.A.A.:
  326. Blah blah blah
  327. """)
  328.  
  329.     def test_mangled_from(self):
  330.         s = StringIO()
  331.         g = Generator(s, mangle_from_=1)
  332.         g(self.msg)
  333.         self.assertEqual(s.getvalue(), """\
  334. From: aaa@bbb.org
  335.  
  336. >From the desk of A.A.A.:
  337. Blah blah blah
  338. """)
  339.  
  340.     def test_dont_mangle_from(self):
  341.         s = StringIO()
  342.         g = Generator(s, mangle_from_=0)
  343.         g(self.msg)
  344.         self.assertEqual(s.getvalue(), """\
  345. From: aaa@bbb.org
  346.  
  347. From the desk of A.A.A.:
  348. Blah blah blah
  349. """)
  350.  
  351.  
  352.  
  353. # Test the basic MIMEAudio class
  354. class TestMIMEAudio(unittest.TestCase):
  355.     def setUp(self):
  356.         # In Python, audiotest.au lives in Lib/test not Lib/test/data
  357.         fp = open(findfile('audiotest.au'))
  358.         try:
  359.             self._audiodata = fp.read()
  360.         finally:
  361.             fp.close()
  362.         self._au = MIMEAudio(self._audiodata)
  363.  
  364.     def test_guess_minor_type(self):
  365.         self.assertEqual(self._au.get_type(), 'audio/basic')
  366.  
  367.     def test_encoding(self):
  368.         payload = self._au.get_payload()
  369.         self.assertEqual(base64.decodestring(payload), self._audiodata)
  370.  
  371.     def checkSetMinor(self):
  372.         au = MIMEAudio(self._audiodata, 'fish')
  373.         self.assertEqual(im.get_type(), 'audio/fish')
  374.  
  375.     def test_custom_encoder(self):
  376.         eq = self.assertEqual
  377.         def encoder(msg):
  378.             orig = msg.get_payload()
  379.             msg.set_payload(0)
  380.             msg['Content-Transfer-Encoding'] = 'broken64'
  381.         au = MIMEAudio(self._audiodata, _encoder=encoder)
  382.         eq(au.get_payload(), 0)
  383.         eq(au['content-transfer-encoding'], 'broken64')
  384.  
  385.     def test_add_header(self):
  386.         eq = self.assertEqual
  387.         unless = self.failUnless
  388.         self._au.add_header('Content-Disposition', 'attachment',
  389.                             filename='audiotest.au')
  390.         eq(self._au['content-disposition'],
  391.            'attachment; filename="audiotest.au"')
  392.         eq(self._au.get_params(header='content-disposition'),
  393.            [('attachment', ''), ('filename', 'audiotest.au')])
  394.         eq(self._au.get_param('filename', header='content-disposition'),
  395.            'audiotest.au')
  396.         missing = []
  397.         eq(self._au.get_param('attachment', header='content-disposition'), '')
  398.         unless(self._au.get_param('foo', failobj=missing,
  399.                                   header='content-disposition') is missing)
  400.         # Try some missing stuff
  401.         unless(self._au.get_param('foobar', missing) is missing)
  402.         unless(self._au.get_param('attachment', missing,
  403.                                   header='foobar') is missing)
  404.  
  405.  
  406.  
  407. # Test the basic MIMEImage class
  408. class TestMIMEImage(unittest.TestCase):
  409.     def setUp(self):
  410.         fp = openfile('PyBanner048.gif')
  411.         try:
  412.             self._imgdata = fp.read()
  413.         finally:
  414.             fp.close()
  415.         self._im = MIMEImage(self._imgdata)
  416.  
  417.     def test_guess_minor_type(self):
  418.         self.assertEqual(self._im.get_type(), 'image/gif')
  419.  
  420.     def test_encoding(self):
  421.         payload = self._im.get_payload()
  422.         self.assertEqual(base64.decodestring(payload), self._imgdata)
  423.  
  424.     def checkSetMinor(self):
  425.         im = MIMEImage(self._imgdata, 'fish')
  426.         self.assertEqual(im.get_type(), 'image/fish')
  427.  
  428.     def test_custom_encoder(self):
  429.         eq = self.assertEqual
  430.         def encoder(msg):
  431.             orig = msg.get_payload()
  432.             msg.set_payload(0)
  433.             msg['Content-Transfer-Encoding'] = 'broken64'
  434.         im = MIMEImage(self._imgdata, _encoder=encoder)
  435.         eq(im.get_payload(), 0)
  436.         eq(im['content-transfer-encoding'], 'broken64')
  437.  
  438.     def test_add_header(self):
  439.         eq = self.assertEqual
  440.         unless = self.failUnless
  441.         self._im.add_header('Content-Disposition', 'attachment',
  442.                             filename='dingusfish.gif')
  443.         eq(self._im['content-disposition'],
  444.            'attachment; filename="dingusfish.gif"')
  445.         eq(self._im.get_params(header='content-disposition'),
  446.            [('attachment', ''), ('filename', 'dingusfish.gif')])
  447.         eq(self._im.get_param('filename', header='content-disposition'),
  448.            'dingusfish.gif')
  449.         missing = []
  450.         eq(self._im.get_param('attachment', header='content-disposition'), '')
  451.         unless(self._im.get_param('foo', failobj=missing,
  452.                                   header='content-disposition') is missing)
  453.         # Try some missing stuff
  454.         unless(self._im.get_param('foobar', missing) is missing)
  455.         unless(self._im.get_param('attachment', missing,
  456.                                   header='foobar') is missing)
  457.  
  458.  
  459.  
  460. # Test the basic MIMEText class
  461. class TestMIMEText(unittest.TestCase):
  462.     def setUp(self):
  463.         self._msg = MIMEText('hello there')
  464.  
  465.     def test_types(self):
  466.         eq = self.assertEqual
  467.         unless = self.failUnless
  468.         eq(self._msg.get_type(), 'text/plain')
  469.         eq(self._msg.get_param('charset'), 'us-ascii')
  470.         missing = []
  471.         unless(self._msg.get_param('foobar', missing) is missing)
  472.         unless(self._msg.get_param('charset', missing, header='foobar')
  473.                is missing)
  474.  
  475.     def test_payload(self):
  476.         self.assertEqual(self._msg.get_payload(), 'hello there\n')
  477.         self.failUnless(not self._msg.is_multipart())
  478.  
  479.  
  480.  
  481. # Test a more complicated multipart/mixed type message
  482. class TestMultipartMixed(unittest.TestCase):
  483.     def setUp(self):
  484.         fp = openfile('PyBanner048.gif')
  485.         try:
  486.             data = fp.read()
  487.         finally:
  488.             fp.close()
  489.  
  490.         container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
  491.         image = MIMEImage(data, name='dingusfish.gif')
  492.         image.add_header('content-disposition', 'attachment',
  493.                          filename='dingusfish.gif')
  494.         intro = MIMEText('''\
  495. Hi there,
  496.  
  497. This is the dingus fish.
  498. ''')
  499.         container.add_payload(intro)
  500.         container.add_payload(image)
  501.         container['From'] = 'Barry <barry@digicool.com>'
  502.         container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
  503.         container['Subject'] = 'Here is your dingus fish'
  504.  
  505.         now = 987809702.54848599
  506.         timetuple = time.localtime(now)
  507.         if timetuple[-1] == 0:
  508.             tzsecs = time.timezone
  509.         else:
  510.             tzsecs = time.altzone
  511.         if tzsecs > 0:
  512.             sign = '-'
  513.         else:
  514.             sign = '+'
  515.         tzoffset = ' %s%04d' % (sign, tzsecs / 36)
  516.         container['Date'] = time.strftime(
  517.             '%a, %d %b %Y %H:%M:%S',
  518.             time.localtime(now)) + tzoffset
  519.         self._msg = container
  520.         self._im = image
  521.         self._txt = intro
  522.  
  523.     def test_hierarchy(self):
  524.         # convenience
  525.         eq = self.assertEqual
  526.         unless = self.failUnless
  527.         raises = self.assertRaises
  528.         # tests
  529.         m = self._msg
  530.         unless(m.is_multipart())
  531.         eq(m.get_type(), 'multipart/mixed')
  532.         eq(len(m.get_payload()), 2)
  533.         raises(IndexError, m.get_payload, 2)
  534.         m0 = m.get_payload(0)
  535.         m1 = m.get_payload(1)
  536.         unless(m0 is self._txt)
  537.         unless(m1 is self._im)
  538.         eq(m.get_payload(), [m0, m1])
  539.         unless(not m0.is_multipart())
  540.         unless(not m1.is_multipart())
  541.  
  542.     def test_no_parts_in_a_multipart(self):
  543.         outer = MIMEBase('multipart', 'mixed')
  544.         outer['Subject'] = 'A subject'
  545.         outer['To'] = 'aperson@dom.ain'
  546.         outer['From'] = 'bperson@dom.ain'
  547.         outer.preamble = ''
  548.         outer.epilogue = ''
  549.         outer.set_boundary('BOUNDARY')
  550.         msg = MIMEText('hello world')
  551.         self.assertEqual(outer.as_string(), '''\
  552. Content-Type: multipart/mixed; boundary="BOUNDARY"
  553. MIME-Version: 1.0
  554. Subject: A subject
  555. To: aperson@dom.ain
  556. From: bperson@dom.ain
  557.  
  558. --BOUNDARY
  559.  
  560.  
  561. --BOUNDARY--
  562. ''')        
  563.  
  564.     def test_one_part_in_a_multipart(self):
  565.         outer = MIMEBase('multipart', 'mixed')
  566.         outer['Subject'] = 'A subject'
  567.         outer['To'] = 'aperson@dom.ain'
  568.         outer['From'] = 'bperson@dom.ain'
  569.         outer.preamble = ''
  570.         outer.epilogue = ''
  571.         outer.set_boundary('BOUNDARY')
  572.         msg = MIMEText('hello world')
  573.         outer.attach(msg)
  574.         self.assertEqual(outer.as_string(), '''\
  575. Content-Type: multipart/mixed; boundary="BOUNDARY"
  576. MIME-Version: 1.0
  577. Subject: A subject
  578. To: aperson@dom.ain
  579. From: bperson@dom.ain
  580.  
  581. --BOUNDARY
  582. Content-Type: text/plain; charset="us-ascii"
  583. MIME-Version: 1.0
  584. Content-Transfer-Encoding: 7bit
  585.  
  586. hello world
  587.  
  588. --BOUNDARY--
  589. ''')        
  590.  
  591.     def test_seq_parts_in_a_multipart(self):
  592.         outer = MIMEBase('multipart', 'mixed')
  593.         outer['Subject'] = 'A subject'
  594.         outer['To'] = 'aperson@dom.ain'
  595.         outer['From'] = 'bperson@dom.ain'
  596.         outer.preamble = ''
  597.         outer.epilogue = ''
  598.         msg = MIMEText('hello world')
  599.         outer.attach([msg])
  600.         outer.set_boundary('BOUNDARY')
  601.         self.assertEqual(outer.as_string(), '''\
  602. Content-Type: multipart/mixed; boundary="BOUNDARY"
  603. MIME-Version: 1.0
  604. Subject: A subject
  605. To: aperson@dom.ain
  606. From: bperson@dom.ain
  607.  
  608. --BOUNDARY
  609. Content-Type: text/plain; charset="us-ascii"
  610. MIME-Version: 1.0
  611. Content-Transfer-Encoding: 7bit
  612.  
  613. hello world
  614.  
  615. --BOUNDARY--
  616. ''')        
  617.  
  618.  
  619.  
  620. # Test some badly formatted messages
  621. class TestNonConformant(TestEmailBase):
  622.     def test_parse_missing_minor_type(self):
  623.         eq = self.assertEqual
  624.         msg = self._msgobj('msg_14.txt')
  625.         eq(msg.get_type(), 'text')
  626.         eq(msg.get_main_type(), 'text')
  627.         self.failUnless(msg.get_subtype() is None)
  628.  
  629.     def test_bogus_boundary(self):
  630.         fp = openfile('msg_15.txt')
  631.         try:
  632.             data = fp.read()
  633.         finally:
  634.             fp.close()
  635.         p = Parser()
  636.         # Note, under a future non-strict parsing mode, this would parse the
  637.         # message into the intended message tree.
  638.         self.assertRaises(Errors.BoundaryError, p.parsestr, data)
  639.  
  640.  
  641.  
  642. # Test RFC 2047 header encoding and decoding
  643. class TestRFC2047(unittest.TestCase):
  644.     def test_iso_8859_1(self):
  645.         eq = self.assertEqual
  646.         s = '=?iso-8859-1?q?this=20is=20some=20text?='
  647.         eq(Utils.decode(s), 'this is some text')
  648.         s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
  649.         eq(Utils.decode(s), u'Keld_J\xf8rn_Simonsen')
  650.         s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
  651.             '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
  652.         eq(Utils.decode(s), 'If you can read this you understand the example.')
  653.         s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
  654.         eq(Utils.decode(s),
  655.            u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
  656.         s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
  657.         eq(Utils.decode(s), u'this is some text')
  658.  
  659.     def test_encode_header(self):
  660.         eq = self.assertEqual
  661.         s = 'this is some text'
  662.         eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
  663.         s = 'Keld_J\xf8rn_Simonsen'
  664.         eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
  665.         s1 = 'If you can read this yo'
  666.         s2 = 'u understand the example.'
  667.         eq(Utils.encode(s1, encoding='b'),
  668.            '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
  669.         eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
  670.            '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
  671.  
  672.  
  673.  
  674. # Test the MIMEMessage class
  675. class TestMIMEMessage(TestEmailBase):
  676.     def setUp(self):
  677.         fp = openfile('msg_11.txt')
  678.         self._text = fp.read()
  679.         fp.close()
  680.  
  681.     def test_type_error(self):
  682.         self.assertRaises(TypeError, MIMEMessage, 'a plain string')
  683.  
  684.     def test_valid_argument(self):
  685.         eq = self.assertEqual
  686.         subject = 'A sub-message'
  687.         m = Message()
  688.         m['Subject'] = subject
  689.         r = MIMEMessage(m)
  690.         eq(r.get_type(), 'message/rfc822')
  691.         self.failUnless(r.get_payload() is m)
  692.         eq(r.get_payload()['subject'], subject)
  693.  
  694.     def test_generate(self):
  695.         # First craft the message to be encapsulated
  696.         m = Message()
  697.         m['Subject'] = 'An enclosed message'
  698.         m.add_payload('Here is the body of the message.\n')
  699.         r = MIMEMessage(m)
  700.         r['Subject'] = 'The enclosing message'
  701.         s = StringIO()
  702.         g = Generator(s)
  703.         g(r)
  704.         self.assertEqual(s.getvalue(), """\
  705. Content-Type: message/rfc822
  706. MIME-Version: 1.0
  707. Subject: The enclosing message
  708.  
  709. Subject: An enclosed message
  710.  
  711. Here is the body of the message.
  712. """)
  713.  
  714.     def test_parse_message_rfc822(self):
  715.         eq = self.assertEqual
  716.         msg = self._msgobj('msg_11.txt')
  717.         eq(msg.get_type(), 'message/rfc822')
  718.         eq(len(msg.get_payload()), 1)
  719.         submsg = msg.get_payload()
  720.         self.failUnless(isinstance(submsg, Message))
  721.         eq(submsg['subject'], 'An enclosed message')
  722.         eq(submsg.get_payload(), 'Here is the body of the message.\n')
  723.  
  724.     def test_dsn(self):
  725.         eq = self.assertEqual
  726.         unless = self.failUnless
  727.         # msg 16 is a Delivery Status Notification, see RFC XXXX
  728.         msg = self._msgobj('msg_16.txt')
  729.         eq(msg.get_type(), 'multipart/report')
  730.         unless(msg.is_multipart())
  731.         eq(len(msg.get_payload()), 3)
  732.         # Subpart 1 is a text/plain, human readable section
  733.         subpart = msg.get_payload(0)
  734.         eq(subpart.get_type(), 'text/plain')
  735.         eq(subpart.get_payload(), """\
  736. This report relates to a message you sent with the following header fields:
  737.  
  738.   Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
  739.   Date: Sun, 23 Sep 2001 20:10:55 -0700
  740.   From: "Ian T. Henry" <henryi@oxy.edu>
  741.   To: SoCal Raves <scr@socal-raves.org>
  742.   Subject: [scr] yeah for Ians!!
  743.  
  744. Your message cannot be delivered to the following recipients:
  745.  
  746.   Recipient address: jangel1@cougar.noc.ucla.edu
  747.   Reason: recipient reached disk quota
  748.  
  749. """)
  750.         # Subpart 2 contains the machine parsable DSN information.  It
  751.         # consists of two blocks of headers, represented by two nested Message
  752.         # objects.
  753.         subpart = msg.get_payload(1)
  754.         eq(subpart.get_type(), 'message/delivery-status')
  755.         eq(len(subpart.get_payload()), 2)
  756.         # message/delivery-status should treat each block as a bunch of
  757.         # headers, i.e. a bunch of Message objects.
  758.         dsn1 = subpart.get_payload(0)
  759.         unless(isinstance(dsn1, Message))
  760.         eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
  761.         eq(dsn1.get_param('dns', header='reporting-mta'), '')
  762.         # Try a missing one <wink>
  763.         eq(dsn1.get_param('nsd', header='reporting-mta'), None)
  764.         dsn2 = subpart.get_payload(1)
  765.         unless(isinstance(dsn2, Message))
  766.         eq(dsn2['action'], 'failed')
  767.         eq(dsn2.get_params(header='original-recipient'),
  768.            [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
  769.         eq(dsn2.get_param('rfc822', header='final-recipient'), '')
  770.         # Subpart 3 is the original message
  771.         subpart = msg.get_payload(2)
  772.         eq(subpart.get_type(), 'message/rfc822')
  773.         subsubpart = subpart.get_payload()
  774.         unless(isinstance(subsubpart, Message))
  775.         eq(subsubpart.get_type(), 'text/plain')
  776.         eq(subsubpart['message-id'],
  777.            '<002001c144a6$8752e060$56104586@oxy.edu>')
  778.  
  779.     def test_epilogue(self):
  780.         fp = openfile('msg_21.txt')
  781.         try:
  782.             text = fp.read()
  783.         finally:
  784.             fp.close()
  785.         msg = Message()
  786.         msg['From'] = 'aperson@dom.ain'
  787.         msg['To'] = 'bperson@dom.ain'
  788.         msg['Subject'] = 'Test'
  789.         msg.preamble = 'MIME message\n'
  790.         msg.epilogue = 'End of MIME message\n'
  791.         msg1 = MIMEText('One')
  792.         msg2 = MIMEText('Two')
  793.         msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
  794.         msg.add_payload(msg1)
  795.         msg.add_payload(msg2)
  796.         sfp = StringIO()
  797.         g = Generator(sfp)
  798.         g(msg)
  799.         self.assertEqual(sfp.getvalue(), text)
  800.  
  801.  
  802.  
  803. # A general test of parser->model->generator idempotency.  IOW, read a message
  804. # in, parse it into a message object tree, then without touching the tree,
  805. # regenerate the plain text.  The original text and the transformed text
  806. # should be identical.  Note: that we ignore the Unix-From since that may
  807. # contain a changed date.
  808. class TestIdempotent(unittest.TestCase):
  809.     def _msgobj(self, filename):
  810.         fp = openfile(filename)
  811.         try:
  812.             data = fp.read()
  813.         finally:
  814.             fp.close()
  815.         msg = email.message_from_string(data)
  816.         return msg, data
  817.  
  818.     def _idempotent(self, msg, text):
  819.         eq = self.assertEquals
  820.         s = StringIO()
  821.         g = Generator(s, maxheaderlen=0)
  822.         g(msg)
  823.         eq(text, s.getvalue())
  824.  
  825.     def test_parse_text_message(self):
  826.         eq = self.assertEquals
  827.         msg, text = self._msgobj('msg_01.txt')
  828.         eq(msg.get_type(), 'text/plain')
  829.         eq(msg.get_main_type(), 'text')
  830.         eq(msg.get_subtype(), 'plain')
  831.         eq(msg.get_params()[1], ('charset', 'us-ascii'))
  832.         eq(msg.get_param('charset'), 'us-ascii')
  833.         eq(msg.preamble, None)
  834.         eq(msg.epilogue, None)
  835.         self._idempotent(msg, text)
  836.  
  837.     def test_parse_untyped_message(self):
  838.         eq = self.assertEquals
  839.         msg, text = self._msgobj('msg_03.txt')
  840.         eq(msg.get_type(), None)
  841.         eq(msg.get_params(), None)
  842.         eq(msg.get_param('charset'), None)
  843.         self._idempotent(msg, text)
  844.  
  845.     def test_simple_multipart(self):
  846.         msg, text = self._msgobj('msg_04.txt')
  847.         self._idempotent(msg, text)
  848.  
  849.     def test_MIME_digest(self):
  850.         msg, text = self._msgobj('msg_02.txt')
  851.         self._idempotent(msg, text)
  852.  
  853.     def test_mixed_with_image(self):
  854.         msg, text = self._msgobj('msg_06.txt')
  855.         self._idempotent(msg, text)
  856.  
  857.     def test_multipart_report(self):
  858.         msg, text = self._msgobj('msg_05.txt')
  859.         self._idempotent(msg, text)
  860.  
  861.     def test_dsn(self):
  862.         msg, text = self._msgobj('msg_16.txt')
  863.         self._idempotent(msg, text)
  864.  
  865.     def test_preamble_epilogue(self):
  866.         msg, text = self._msgobj('msg_21.txt')
  867.         self._idempotent(msg, text)
  868.  
  869.     def test_multipart_one_part(self):
  870.         msg, text = self._msgobj('msg_23.txt')
  871.         self._idempotent(msg, text)
  872.  
  873.     def test_content_type(self):
  874.         eq = self.assertEquals
  875.         # Get a message object and reset the seek pointer for other tests
  876.         msg, text = self._msgobj('msg_05.txt')
  877.         eq(msg.get_type(), 'multipart/report')
  878.         # Test the Content-Type: parameters
  879.         params = {}
  880.         for pk, pv in msg.get_params():
  881.             params[pk] = pv
  882.         eq(params['report-type'], 'delivery-status')
  883.         eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
  884.         eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
  885.         eq(msg.epilogue, '\n\n')
  886.         eq(len(msg.get_payload()), 3)
  887.         # Make sure the subparts are what we expect
  888.         msg1 = msg.get_payload(0)
  889.         eq(msg1.get_type(), 'text/plain')
  890.         eq(msg1.get_payload(), 'Yadda yadda yadda\n')
  891.         msg2 = msg.get_payload(1)
  892.         eq(msg2.get_type(), None)
  893.         eq(msg2.get_payload(), 'Yadda yadda yadda\n')
  894.         msg3 = msg.get_payload(2)
  895.         eq(msg3.get_type(), 'message/rfc822')
  896.         self.failUnless(isinstance(msg3, Message))
  897.         msg4 = msg3.get_payload()
  898.         self.failUnless(isinstance(msg4, Message))
  899.         eq(msg4.get_payload(), 'Yadda yadda yadda\n')
  900.  
  901.     def test_parser(self):
  902.         eq = self.assertEquals
  903.         msg, text = self._msgobj('msg_06.txt')
  904.         # Check some of the outer headers
  905.         eq(msg.get_type(), 'message/rfc822')
  906.         # Make sure there's exactly one thing in the payload and that's a
  907.         # sub-Message object of type text/plain
  908.         msg1 = msg.get_payload()
  909.         self.failUnless(isinstance(msg1, Message))
  910.         eq(msg1.get_type(), 'text/plain')
  911.         self.failUnless(isinstance(msg1.get_payload(), StringType))
  912.         eq(msg1.get_payload(), '\n')
  913.  
  914.  
  915.  
  916. # Test various other bits of the package's functionality
  917. class TestMiscellaneous(unittest.TestCase):
  918.     def test_message_from_string(self):
  919.         fp = openfile('msg_01.txt')
  920.         try:
  921.             text = fp.read()
  922.         finally:
  923.             fp.close()
  924.         msg = email.message_from_string(text)
  925.         s = StringIO()
  926.         # Don't wrap/continue long headers since we're trying to test
  927.         # idempotency.
  928.         g = Generator(s, maxheaderlen=0)
  929.         g(msg)
  930.         self.assertEqual(text, s.getvalue())
  931.  
  932.     def test_message_from_file(self):
  933.         fp = openfile('msg_01.txt')
  934.         try:
  935.             text = fp.read()
  936.             fp.seek(0)
  937.             msg = email.message_from_file(fp)
  938.             s = StringIO()
  939.             # Don't wrap/continue long headers since we're trying to test
  940.             # idempotency.
  941.             g = Generator(s, maxheaderlen=0)
  942.             g(msg)
  943.             self.assertEqual(text, s.getvalue())
  944.         finally:
  945.             fp.close()
  946.  
  947.     def test_message_from_string_with_class(self):
  948.         unless = self.failUnless
  949.         fp = openfile('msg_01.txt')
  950.         try:
  951.             text = fp.read()
  952.         finally:
  953.             fp.close()
  954.         # Create a subclass
  955.         class MyMessage(Message):
  956.             pass
  957.  
  958.         msg = email.message_from_string(text, MyMessage)
  959.         unless(isinstance(msg, MyMessage))
  960.         # Try something more complicated
  961.         fp = openfile('msg_02.txt')
  962.         try:
  963.             text = fp.read()
  964.         finally:
  965.             fp.close()
  966.         msg = email.message_from_string(text, MyMessage)
  967.         for subpart in msg.walk():
  968.             unless(isinstance(subpart, MyMessage))
  969.  
  970.     def test_message_from_file_with_class(self):
  971.         unless = self.failUnless
  972.         # Create a subclass
  973.         class MyMessage(Message):
  974.             pass
  975.  
  976.         fp = openfile('msg_01.txt')
  977.         try:
  978.             msg = email.message_from_file(fp, MyMessage)
  979.         finally:
  980.             fp.close()
  981.         unless(isinstance(msg, MyMessage))
  982.         # Try something more complicated
  983.         fp = openfile('msg_02.txt')
  984.         try:
  985.             msg = email.message_from_file(fp, MyMessage)
  986.         finally:
  987.             fp.close()
  988.         for subpart in msg.walk():
  989.             unless(isinstance(subpart, MyMessage))
  990.  
  991.     def test__all__(self):
  992.         module = __import__('email')
  993.         all = module.__all__
  994.         all.sort()
  995.         self.assertEqual(all, ['Encoders', 'Errors', 'Generator', 'Iterators',
  996.                                'MIMEAudio', 'MIMEBase', 'MIMEImage',
  997.                                'MIMEMessage', 'MIMEText', 'Message', 'Parser',
  998.                                'Utils',
  999.                                'message_from_file', 'message_from_string'])
  1000.  
  1001.     def test_formatdate(self):
  1002.         now = 1005327232.109884
  1003.         gm_epoch = time.gmtime(0)[0:3]
  1004.         loc_epoch = time.localtime(0)[0:3]
  1005.         # When does the epoch start?
  1006.         if gm_epoch == (1970, 1, 1):
  1007.             # traditional Unix epoch
  1008.             matchdate = 'Fri, 09 Nov 2001 17:33:52 -0000'
  1009.         elif loc_epoch == (1904, 1, 1):
  1010.             # Mac epoch
  1011.             matchdate = 'Sat, 09 Nov 1935 16:33:52 -0000'
  1012.         else:
  1013.             matchdate = "I don't understand your epoch"
  1014.         gdate = Utils.formatdate(now)
  1015.         self.assertEqual(gdate, matchdate)
  1016.  
  1017.     def test_formatdate_localtime(self):
  1018.         now = 1005327232.109884
  1019.         ldate = Utils.formatdate(now, localtime=1)
  1020.         zone = ldate.split()[5]
  1021.         offset = int(zone[1:3]) * 3600 + int(zone[-2:]) * 60
  1022.         # Remember offset is in seconds west of UTC, but the timezone is in
  1023.         # minutes east of UTC, so the signs differ.
  1024.         if zone[0] == '+':
  1025.             offset = -offset
  1026.         if time.daylight and time.localtime(now)[-1]:
  1027.             toff = time.altzone
  1028.         else:
  1029.             toff = time.timezone
  1030.         self.assertEqual(offset, toff)
  1031.  
  1032.     def test_parsedate_none(self):
  1033.         self.assertEqual(Utils.parsedate(''), None)
  1034.  
  1035.     def test_parseaddr_empty(self):
  1036.         self.assertEqual(Utils.parseaddr('<>'), ('', ''))
  1037.         self.assertEqual(Utils.dump_address_pair(Utils.parseaddr('<>')), '')
  1038.  
  1039.  
  1040.  
  1041. # Test the iterator/generators
  1042. class TestIterators(TestEmailBase):
  1043.     def test_body_line_iterator(self):
  1044.         eq = self.assertEqual
  1045.         # First a simple non-multipart message
  1046.         msg = self._msgobj('msg_01.txt')
  1047.         it = Iterators.body_line_iterator(msg)
  1048.         lines = list(it)
  1049.         eq(len(lines), 6)
  1050.         eq(EMPTYSTRING.join(lines), msg.get_payload())
  1051.         # Now a more complicated multipart
  1052.         msg = self._msgobj('msg_02.txt')
  1053.         it = Iterators.body_line_iterator(msg)
  1054.         lines = list(it)
  1055.         eq(len(lines), 43)
  1056.         eq(EMPTYSTRING.join(lines), openfile('msg_19.txt').read())
  1057.  
  1058.     def test_typed_subpart_iterator(self):
  1059.         eq = self.assertEqual
  1060.         msg = self._msgobj('msg_04.txt')
  1061.         it = Iterators.typed_subpart_iterator(msg, 'text')
  1062.         lines = [subpart.get_payload() for subpart in it]
  1063.         eq(len(lines), 2)
  1064.         eq(EMPTYSTRING.join(lines), """\
  1065. a simple kind of mirror
  1066. to reflect upon our own
  1067. a simple kind of mirror
  1068. to reflect upon our own
  1069. """)
  1070.  
  1071.     def test_typed_subpart_iterator_default_type(self):
  1072.         eq = self.assertEqual
  1073.         msg = self._msgobj('msg_03.txt')
  1074.         it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
  1075.         lines = []
  1076.         subparts = 0
  1077.         for subpart in it:
  1078.             subparts += 1
  1079.             lines.append(subpart.get_payload())
  1080.         eq(subparts, 1)
  1081.         eq(EMPTYSTRING.join(lines), """\
  1082.  
  1083. Hi,
  1084.  
  1085. Do you like this message?
  1086.  
  1087. -Me
  1088. """)
  1089.  
  1090.  
  1091. class TestParsers(unittest.TestCase):
  1092.     def test_header_parser(self):
  1093.         eq = self.assertEqual
  1094.         # Parse only the headers of a complex multipart MIME document
  1095.         p = HeaderParser()
  1096.         fp = openfile('msg_02.txt')
  1097.         msg = p.parse(fp)
  1098.         eq(msg['from'], 'ppp-request@zzz.org')
  1099.         eq(msg['to'], 'ppp@zzz.org')
  1100.         eq(msg.get_type(), 'multipart/mixed')
  1101.         eq(msg.is_multipart(), 0)
  1102.         self.failUnless(isinstance(msg.get_payload(), StringType))
  1103.  
  1104.  
  1105.  
  1106. def suite():
  1107.     suite = unittest.TestSuite()
  1108.     suite.addTest(unittest.makeSuite(TestMessageAPI))
  1109.     suite.addTest(unittest.makeSuite(TestEncoders))
  1110.     suite.addTest(unittest.makeSuite(TestLongHeaders))
  1111.     suite.addTest(unittest.makeSuite(TestFromMangling))
  1112.     suite.addTest(unittest.makeSuite(TestMIMEAudio))
  1113.     suite.addTest(unittest.makeSuite(TestMIMEImage))
  1114.     suite.addTest(unittest.makeSuite(TestMIMEText))
  1115.     suite.addTest(unittest.makeSuite(TestMultipartMixed))
  1116.     suite.addTest(unittest.makeSuite(TestNonConformant))
  1117.     suite.addTest(unittest.makeSuite(TestRFC2047))
  1118.     suite.addTest(unittest.makeSuite(TestMIMEMessage))
  1119.     suite.addTest(unittest.makeSuite(TestIdempotent))
  1120.     suite.addTest(unittest.makeSuite(TestMiscellaneous))
  1121.     suite.addTest(unittest.makeSuite(TestIterators))
  1122.     suite.addTest(unittest.makeSuite(TestParsers))
  1123.     return suite
  1124.  
  1125.  
  1126.  
  1127. def test_main():
  1128.     from test_support import run_suite
  1129.     run_suite(suite())
  1130.  
  1131. if __name__ == '__main__':
  1132.     test_main()
  1133.