PyXR

c:\python24\lib \ email \ test \ test_email.py



0001 # Copyright (C) 2001-2004 Python Software Foundation
0002 # Contact: email-sig@python.org
0003 # email package unit tests
0004 
0005 import os
0006 import sys
0007 import time
0008 import base64
0009 import difflib
0010 import unittest
0011 import warnings
0012 from cStringIO import StringIO
0013 
0014 import email
0015 
0016 from email.Charset import Charset
0017 from email.Header import Header, decode_header, make_header
0018 from email.Parser import Parser, HeaderParser
0019 from email.Generator import Generator, DecodedGenerator
0020 from email.Message import Message
0021 from email.MIMEAudio import MIMEAudio
0022 from email.MIMEText import MIMEText
0023 from email.MIMEImage import MIMEImage
0024 from email.MIMEBase import MIMEBase
0025 from email.MIMEMessage import MIMEMessage
0026 from email.MIMEMultipart import MIMEMultipart
0027 from email import Utils
0028 from email import Errors
0029 from email import Encoders
0030 from email import Iterators
0031 from email import base64MIME
0032 from email import quopriMIME
0033 
0034 from test.test_support import findfile, run_unittest
0035 from email.test import __file__ as landmark
0036 
0037 
0038 NL = '\n'
0039 EMPTYSTRING = ''
0040 SPACE = ' '
0041 
0042 # We don't care about DeprecationWarnings
0043 warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
0044 
0045 
0046 
0047 def openfile(filename, mode='r'):
0048     path = os.path.join(os.path.dirname(landmark), 'data', filename)
0049     return open(path, mode)
0050 
0051 
0052 
0053 # Base test class
0054 class TestEmailBase(unittest.TestCase):
0055     def ndiffAssertEqual(self, first, second):
0056         """Like failUnlessEqual except use ndiff for readable output."""
0057         if first <> second:
0058             sfirst = str(first)
0059             ssecond = str(second)
0060             diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
0061             fp = StringIO()
0062             print >> fp, NL, NL.join(diff)
0063             raise self.failureException, fp.getvalue()
0064 
0065     def _msgobj(self, filename):
0066         fp = openfile(findfile(filename))
0067         try:
0068             msg = email.message_from_file(fp)
0069         finally:
0070             fp.close()
0071         return msg
0072 
0073 
0074 
0075 # Test various aspects of the Message class's API
0076 class TestMessageAPI(TestEmailBase):
0077     def test_get_all(self):
0078         eq = self.assertEqual
0079         msg = self._msgobj('msg_20.txt')
0080         eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
0081         eq(msg.get_all('xx', 'n/a'), 'n/a')
0082 
0083     def test_getset_charset(self):
0084         eq = self.assertEqual
0085         msg = Message()
0086         eq(msg.get_charset(), None)
0087         charset = Charset('iso-8859-1')
0088         msg.set_charset(charset)
0089         eq(msg['mime-version'], '1.0')
0090         eq(msg.get_type(), 'text/plain')
0091         eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
0092         eq(msg.get_param('charset'), 'iso-8859-1')
0093         eq(msg['content-transfer-encoding'], 'quoted-printable')
0094         eq(msg.get_charset().input_charset, 'iso-8859-1')
0095         # Remove the charset
0096         msg.set_charset(None)
0097         eq(msg.get_charset(), None)
0098         eq(msg['content-type'], 'text/plain')
0099         # Try adding a charset when there's already MIME headers present
0100         msg = Message()
0101         msg['MIME-Version'] = '2.0'
0102         msg['Content-Type'] = 'text/x-weird'
0103         msg['Content-Transfer-Encoding'] = 'quinted-puntable'
0104         msg.set_charset(charset)
0105         eq(msg['mime-version'], '2.0')
0106         eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
0107         eq(msg['content-transfer-encoding'], 'quinted-puntable')
0108 
0109     def test_set_charset_from_string(self):
0110         eq = self.assertEqual
0111         msg = Message()
0112         msg.set_charset('us-ascii')
0113         eq(msg.get_charset().input_charset, 'us-ascii')
0114         eq(msg['content-type'], 'text/plain; charset="us-ascii"')
0115 
0116     def test_set_payload_with_charset(self):
0117         msg = Message()
0118         charset = Charset('iso-8859-1')
0119         msg.set_payload('This is a string payload', charset)
0120         self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
0121 
0122     def test_get_charsets(self):
0123         eq = self.assertEqual
0124 
0125         msg = self._msgobj('msg_08.txt')
0126         charsets = msg.get_charsets()
0127         eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
0128 
0129         msg = self._msgobj('msg_09.txt')
0130         charsets = msg.get_charsets('dingbat')
0131         eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
0132                       'koi8-r'])
0133 
0134         msg = self._msgobj('msg_12.txt')
0135         charsets = msg.get_charsets()
0136         eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
0137                       'iso-8859-3', 'us-ascii', 'koi8-r'])
0138 
0139     def test_get_filename(self):
0140         eq = self.assertEqual
0141 
0142         msg = self._msgobj('msg_04.txt')
0143         filenames = [p.get_filename() for p in msg.get_payload()]
0144         eq(filenames, ['msg.txt', 'msg.txt'])
0145 
0146         msg = self._msgobj('msg_07.txt')
0147         subpart = msg.get_payload(1)
0148         eq(subpart.get_filename(), 'dingusfish.gif')
0149 
0150     def test_get_boundary(self):
0151         eq = self.assertEqual
0152         msg = self._msgobj('msg_07.txt')
0153         # No quotes!
0154         eq(msg.get_boundary(), 'BOUNDARY')
0155 
0156     def test_set_boundary(self):
0157         eq = self.assertEqual
0158         # This one has no existing boundary parameter, but the Content-Type:
0159         # header appears fifth.
0160         msg = self._msgobj('msg_01.txt')
0161         msg.set_boundary('BOUNDARY')
0162         header, value = msg.items()[4]
0163         eq(header.lower(), 'content-type')
0164         eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
0165         # This one has a Content-Type: header, with a boundary, stuck in the
0166         # middle of its headers.  Make sure the order is preserved; it should
0167         # be fifth.
0168         msg = self._msgobj('msg_04.txt')
0169         msg.set_boundary('BOUNDARY')
0170         header, value = msg.items()[4]
0171         eq(header.lower(), 'content-type')
0172         eq(value, 'multipart/mixed; boundary="BOUNDARY"')
0173         # And this one has no Content-Type: header at all.
0174         msg = self._msgobj('msg_03.txt')
0175         self.assertRaises(Errors.HeaderParseError,
0176                           msg.set_boundary, 'BOUNDARY')
0177 
0178     def test_get_decoded_payload(self):
0179         eq = self.assertEqual
0180         msg = self._msgobj('msg_10.txt')
0181         # The outer message is a multipart
0182         eq(msg.get_payload(decode=True), None)
0183         # Subpart 1 is 7bit encoded
0184         eq(msg.get_payload(0).get_payload(decode=True),
0185            'This is a 7bit encoded message.\n')
0186         # Subpart 2 is quopri
0187         eq(msg.get_payload(1).get_payload(decode=True),
0188            '\xa1This is a Quoted Printable encoded message!\n')
0189         # Subpart 3 is base64
0190         eq(msg.get_payload(2).get_payload(decode=True),
0191            'This is a Base64 encoded message.')
0192         # Subpart 4 has no Content-Transfer-Encoding: header.
0193         eq(msg.get_payload(3).get_payload(decode=True),
0194            'This has no Content-Transfer-Encoding: header.\n')
0195 
0196     def test_get_decoded_uu_payload(self):
0197         eq = self.assertEqual
0198         msg = Message()
0199         msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
0200         for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
0201             msg['content-transfer-encoding'] = cte
0202             eq(msg.get_payload(decode=True), 'hello world')
0203         # Now try some bogus data
0204         msg.set_payload('foo')
0205         eq(msg.get_payload(decode=True), 'foo')
0206 
0207     def test_decoded_generator(self):
0208         eq = self.assertEqual
0209         msg = self._msgobj('msg_07.txt')
0210         fp = openfile('msg_17.txt')
0211         try:
0212             text = fp.read()
0213         finally:
0214             fp.close()
0215         s = StringIO()
0216         g = DecodedGenerator(s)
0217         g.flatten(msg)
0218         eq(s.getvalue(), text)
0219 
0220     def test__contains__(self):
0221         msg = Message()
0222         msg['From'] = 'Me'
0223         msg['to'] = 'You'
0224         # Check for case insensitivity
0225         self.failUnless('from' in msg)
0226         self.failUnless('From' in msg)
0227         self.failUnless('FROM' in msg)
0228         self.failUnless('to' in msg)
0229         self.failUnless('To' in msg)
0230         self.failUnless('TO' in msg)
0231 
0232     def test_as_string(self):
0233         eq = self.assertEqual
0234         msg = self._msgobj('msg_01.txt')
0235         fp = openfile('msg_01.txt')
0236         try:
0237             text = fp.read()
0238         finally:
0239             fp.close()
0240         eq(text, msg.as_string())
0241         fullrepr = str(msg)
0242         lines = fullrepr.split('\n')
0243         self.failUnless(lines[0].startswith('From '))
0244         eq(text, NL.join(lines[1:]))
0245 
0246     def test_bad_param(self):
0247         msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
0248         self.assertEqual(msg.get_param('baz'), '')
0249 
0250     def test_missing_filename(self):
0251         msg = email.message_from_string("From: foo\n")
0252         self.assertEqual(msg.get_filename(), None)
0253 
0254     def test_bogus_filename(self):
0255         msg = email.message_from_string(
0256         "Content-Disposition: blarg; filename\n")
0257         self.assertEqual(msg.get_filename(), '')
0258 
0259     def test_missing_boundary(self):
0260         msg = email.message_from_string("From: foo\n")
0261         self.assertEqual(msg.get_boundary(), None)
0262 
0263     def test_get_params(self):
0264         eq = self.assertEqual
0265         msg = email.message_from_string(
0266             'X-Header: foo=one; bar=two; baz=three\n')
0267         eq(msg.get_params(header='x-header'),
0268            [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
0269         msg = email.message_from_string(
0270             'X-Header: foo; bar=one; baz=two\n')
0271         eq(msg.get_params(header='x-header'),
0272            [('foo', ''), ('bar', 'one'), ('baz', 'two')])
0273         eq(msg.get_params(), None)
0274         msg = email.message_from_string(
0275             'X-Header: foo; bar="one"; baz=two\n')
0276         eq(msg.get_params(header='x-header'),
0277            [('foo', ''), ('bar', 'one'), ('baz', 'two')])
0278 
0279     def test_get_param_liberal(self):
0280         msg = Message()
0281         msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
0282         self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
0283 
0284     def test_get_param(self):
0285         eq = self.assertEqual
0286         msg = email.message_from_string(
0287             "X-Header: foo=one; bar=two; baz=three\n")
0288         eq(msg.get_param('bar', header='x-header'), 'two')
0289         eq(msg.get_param('quuz', header='x-header'), None)
0290         eq(msg.get_param('quuz'), None)
0291         msg = email.message_from_string(
0292             'X-Header: foo; bar="one"; baz=two\n')
0293         eq(msg.get_param('foo', header='x-header'), '')
0294         eq(msg.get_param('bar', header='x-header'), 'one')
0295         eq(msg.get_param('baz', header='x-header'), 'two')
0296         # XXX: We are not RFC-2045 compliant!  We cannot parse:
0297         # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
0298         # msg.get_param("weird")
0299         # yet.
0300 
0301     def test_get_param_funky_continuation_lines(self):
0302         msg = self._msgobj('msg_22.txt')
0303         self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
0304 
0305     def test_get_param_with_semis_in_quotes(self):
0306         msg = email.message_from_string(
0307             'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
0308         self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
0309         self.assertEqual(msg.get_param('name', unquote=False),
0310                          '"Jim&amp;&amp;Jill"')
0311 
0312     def test_has_key(self):
0313         msg = email.message_from_string('Header: exists')
0314         self.failUnless(msg.has_key('header'))
0315         self.failUnless(msg.has_key('Header'))
0316         self.failUnless(msg.has_key('HEADER'))
0317         self.failIf(msg.has_key('headeri'))
0318 
0319     def test_set_param(self):
0320         eq = self.assertEqual
0321         msg = Message()
0322         msg.set_param('charset', 'iso-2022-jp')
0323         eq(msg.get_param('charset'), 'iso-2022-jp')
0324         msg.set_param('importance', 'high value')
0325         eq(msg.get_param('importance'), 'high value')
0326         eq(msg.get_param('importance', unquote=False), '"high value"')
0327         eq(msg.get_params(), [('text/plain', ''),
0328                               ('charset', 'iso-2022-jp'),
0329                               ('importance', 'high value')])
0330         eq(msg.get_params(unquote=False), [('text/plain', ''),
0331                                        ('charset', '"iso-2022-jp"'),
0332                                        ('importance', '"high value"')])
0333         msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
0334         eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
0335 
0336     def test_del_param(self):
0337         eq = self.assertEqual
0338         msg = self._msgobj('msg_05.txt')
0339         eq(msg.get_params(),
0340            [('multipart/report', ''), ('report-type', 'delivery-status'),
0341             ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
0342         old_val = msg.get_param("report-type")
0343         msg.del_param("report-type")
0344         eq(msg.get_params(),
0345            [('multipart/report', ''),
0346             ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
0347         msg.set_param("report-type", old_val)
0348         eq(msg.get_params(),
0349            [('multipart/report', ''),
0350             ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
0351             ('report-type', old_val)])
0352 
0353     def test_del_param_on_other_header(self):
0354         msg = Message()
0355         msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
0356         msg.del_param('filename', 'content-disposition')
0357         self.assertEqual(msg['content-disposition'], 'attachment')
0358 
0359     def test_set_type(self):
0360         eq = self.assertEqual
0361         msg = Message()
0362         self.assertRaises(ValueError, msg.set_type, 'text')
0363         msg.set_type('text/plain')
0364         eq(msg['content-type'], 'text/plain')
0365         msg.set_param('charset', 'us-ascii')
0366         eq(msg['content-type'], 'text/plain; charset="us-ascii"')
0367         msg.set_type('text/html')
0368         eq(msg['content-type'], 'text/html; charset="us-ascii"')
0369 
0370     def test_set_type_on_other_header(self):
0371         msg = Message()
0372         msg['X-Content-Type'] = 'text/plain'
0373         msg.set_type('application/octet-stream', 'X-Content-Type')
0374         self.assertEqual(msg['x-content-type'], 'application/octet-stream')
0375 
0376     def test_get_content_type_missing(self):
0377         msg = Message()
0378         self.assertEqual(msg.get_content_type(), 'text/plain')
0379 
0380     def test_get_content_type_missing_with_default_type(self):
0381         msg = Message()
0382         msg.set_default_type('message/rfc822')
0383         self.assertEqual(msg.get_content_type(), 'message/rfc822')
0384 
0385     def test_get_content_type_from_message_implicit(self):
0386         msg = self._msgobj('msg_30.txt')
0387         self.assertEqual(msg.get_payload(0).get_content_type(),
0388                          'message/rfc822')
0389 
0390     def test_get_content_type_from_message_explicit(self):
0391         msg = self._msgobj('msg_28.txt')
0392         self.assertEqual(msg.get_payload(0).get_content_type(),
0393                          'message/rfc822')
0394 
0395     def test_get_content_type_from_message_text_plain_implicit(self):
0396         msg = self._msgobj('msg_03.txt')
0397         self.assertEqual(msg.get_content_type(), 'text/plain')
0398 
0399     def test_get_content_type_from_message_text_plain_explicit(self):
0400         msg = self._msgobj('msg_01.txt')
0401         self.assertEqual(msg.get_content_type(), 'text/plain')
0402 
0403     def test_get_content_maintype_missing(self):
0404         msg = Message()
0405         self.assertEqual(msg.get_content_maintype(), 'text')
0406 
0407     def test_get_content_maintype_missing_with_default_type(self):
0408         msg = Message()
0409         msg.set_default_type('message/rfc822')
0410         self.assertEqual(msg.get_content_maintype(), 'message')
0411 
0412     def test_get_content_maintype_from_message_implicit(self):
0413         msg = self._msgobj('msg_30.txt')
0414         self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
0415 
0416     def test_get_content_maintype_from_message_explicit(self):
0417         msg = self._msgobj('msg_28.txt')
0418         self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
0419 
0420     def test_get_content_maintype_from_message_text_plain_implicit(self):
0421         msg = self._msgobj('msg_03.txt')
0422         self.assertEqual(msg.get_content_maintype(), 'text')
0423 
0424     def test_get_content_maintype_from_message_text_plain_explicit(self):
0425         msg = self._msgobj('msg_01.txt')
0426         self.assertEqual(msg.get_content_maintype(), 'text')
0427 
0428     def test_get_content_subtype_missing(self):
0429         msg = Message()
0430         self.assertEqual(msg.get_content_subtype(), 'plain')
0431 
0432     def test_get_content_subtype_missing_with_default_type(self):
0433         msg = Message()
0434         msg.set_default_type('message/rfc822')
0435         self.assertEqual(msg.get_content_subtype(), 'rfc822')
0436 
0437     def test_get_content_subtype_from_message_implicit(self):
0438         msg = self._msgobj('msg_30.txt')
0439         self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
0440 
0441     def test_get_content_subtype_from_message_explicit(self):
0442         msg = self._msgobj('msg_28.txt')
0443         self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
0444 
0445     def test_get_content_subtype_from_message_text_plain_implicit(self):
0446         msg = self._msgobj('msg_03.txt')
0447         self.assertEqual(msg.get_content_subtype(), 'plain')
0448 
0449     def test_get_content_subtype_from_message_text_plain_explicit(self):
0450         msg = self._msgobj('msg_01.txt')
0451         self.assertEqual(msg.get_content_subtype(), 'plain')
0452 
0453     def test_get_content_maintype_error(self):
0454         msg = Message()
0455         msg['Content-Type'] = 'no-slash-in-this-string'
0456         self.assertEqual(msg.get_content_maintype(), 'text')
0457 
0458     def test_get_content_subtype_error(self):
0459         msg = Message()
0460         msg['Content-Type'] = 'no-slash-in-this-string'
0461         self.assertEqual(msg.get_content_subtype(), 'plain')
0462 
0463     def test_replace_header(self):
0464         eq = self.assertEqual
0465         msg = Message()
0466         msg.add_header('First', 'One')
0467         msg.add_header('Second', 'Two')
0468         msg.add_header('Third', 'Three')
0469         eq(msg.keys(), ['First', 'Second', 'Third'])
0470         eq(msg.values(), ['One', 'Two', 'Three'])
0471         msg.replace_header('Second', 'Twenty')
0472         eq(msg.keys(), ['First', 'Second', 'Third'])
0473         eq(msg.values(), ['One', 'Twenty', 'Three'])
0474         msg.add_header('First', 'Eleven')
0475         msg.replace_header('First', 'One Hundred')
0476         eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
0477         eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
0478         self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
0479 
0480     def test_broken_base64_payload(self):
0481         x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
0482         msg = Message()
0483         msg['content-type'] = 'audio/x-midi'
0484         msg['content-transfer-encoding'] = 'base64'
0485         msg.set_payload(x)
0486         self.assertEqual(msg.get_payload(decode=True), x)
0487 
0488 
0489 
0490 # Test the email.Encoders module
0491 class TestEncoders(unittest.TestCase):
0492     def test_encode_empty_payload(self):
0493         eq = self.assertEqual
0494         msg = Message()
0495         msg.set_charset('us-ascii')
0496         eq(msg['content-transfer-encoding'], '7bit')
0497 
0498     def test_default_cte(self):
0499         eq = self.assertEqual
0500         msg = MIMEText('hello world')
0501         eq(msg['content-transfer-encoding'], '7bit')
0502 
0503     def test_default_cte(self):
0504         eq = self.assertEqual
0505         # With no explicit _charset its us-ascii, and all are 7-bit
0506         msg = MIMEText('hello world')
0507         eq(msg['content-transfer-encoding'], '7bit')
0508         # Similar, but with 8-bit data
0509         msg = MIMEText('hello \xf8 world')
0510         eq(msg['content-transfer-encoding'], '8bit')
0511         # And now with a different charset
0512         msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
0513         eq(msg['content-transfer-encoding'], 'quoted-printable')
0514 
0515 
0516 
0517 # Test long header wrapping
0518 class TestLongHeaders(TestEmailBase):
0519     def test_split_long_continuation(self):
0520         eq = self.ndiffAssertEqual
0521         msg = email.message_from_string("""\
0522 Subject: bug demonstration
0523 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
0524 \tmore text
0525 
0526 test
0527 """)
0528         sfp = StringIO()
0529         g = Generator(sfp)
0530         g.flatten(msg)
0531         eq(sfp.getvalue(), """\
0532 Subject: bug demonstration
0533 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
0534 \tmore text
0535 
0536 test
0537 """)
0538 
0539     def test_another_long_almost_unsplittable_header(self):
0540         eq = self.ndiffAssertEqual
0541         hstr = """\
0542 bug demonstration
0543 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
0544 \tmore text"""
0545         h = Header(hstr, continuation_ws='\t')
0546         eq(h.encode(), """\
0547 bug demonstration
0548 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
0549 \tmore text""")
0550         h = Header(hstr)
0551         eq(h.encode(), """\
0552 bug demonstration
0553  12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
0554  more text""")
0555 
0556     def test_long_nonstring(self):
0557         eq = self.ndiffAssertEqual
0558         g = Charset("iso-8859-1")
0559         cz = Charset("iso-8859-2")
0560         utf8 = Charset("utf-8")
0561         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. "
0562         cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
0563         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")
0564         h = Header(g_head, g, header_name='Subject')
0565         h.append(cz_head, cz)
0566         h.append(utf8_head, utf8)
0567         msg = Message()
0568         msg['Subject'] = h
0569         sfp = StringIO()
0570         g = Generator(sfp)
0571         g.flatten(msg)
0572         eq(sfp.getvalue(), """\
0573 Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
0574  =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
0575  =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
0576  =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
0577  =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
0578  =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
0579  =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
0580  =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
0581  =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
0582  =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
0583  =?utf-8?b?44Gm44GE44G+44GZ44CC?=
0584 
0585 """)
0586         eq(h.encode(), """\
0587 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
0588  =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
0589  =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
0590  =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
0591  =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
0592  =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
0593  =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
0594  =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
0595  =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
0596  =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
0597  =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
0598 
0599     def test_long_header_encode(self):
0600         eq = self.ndiffAssertEqual
0601         h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
0602                    'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
0603                    header_name='X-Foobar-Spoink-Defrobnit')
0604         eq(h.encode(), '''\
0605 wasnipoop; giraffes="very-long-necked-animals";
0606  spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
0607 
0608     def test_long_header_encode_with_tab_continuation(self):
0609         eq = self.ndiffAssertEqual
0610         h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
0611                    'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
0612                    header_name='X-Foobar-Spoink-Defrobnit',
0613                    continuation_ws='\t')
0614         eq(h.encode(), '''\
0615 wasnipoop; giraffes="very-long-necked-animals";
0616 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
0617 
0618     def test_header_splitter(self):
0619         eq = self.ndiffAssertEqual
0620         msg = MIMEText('')
0621         # It'd be great if we could use add_header() here, but that doesn't
0622         # guarantee an order of the parameters.
0623         msg['X-Foobar-Spoink-Defrobnit'] = (
0624             'wasnipoop; giraffes="very-long-necked-animals"; '
0625             'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
0626         sfp = StringIO()
0627         g = Generator(sfp)
0628         g.flatten(msg)
0629         eq(sfp.getvalue(), '''\
0630 Content-Type: text/plain; charset="us-ascii"
0631 MIME-Version: 1.0
0632 Content-Transfer-Encoding: 7bit
0633 X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
0634 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
0635 
0636 ''')
0637 
0638     def test_no_semis_header_splitter(self):
0639         eq = self.ndiffAssertEqual
0640         msg = Message()
0641         msg['From'] = 'test@dom.ain'
0642         msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
0643         msg.set_payload('Test')
0644         sfp = StringIO()
0645         g = Generator(sfp)
0646         g.flatten(msg)
0647         eq(sfp.getvalue(), """\
0648 From: test@dom.ain
0649 References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
0650 \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
0651 
0652 Test""")
0653 
0654     def test_no_split_long_header(self):
0655         eq = self.ndiffAssertEqual
0656         hstr = 'References: ' + 'x' * 80
0657         h = Header(hstr, continuation_ws='\t')
0658         eq(h.encode(), """\
0659 References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
0660 
0661     def test_splitting_multiple_long_lines(self):
0662         eq = self.ndiffAssertEqual
0663         hstr = """\
0664 from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
0665 \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)
0666 \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)
0667 """
0668         h = Header(hstr, continuation_ws='\t')
0669         eq(h.encode(), """\
0670 from babylon.socal-raves.org (localhost [127.0.0.1]);
0671 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
0672 \tfor <mailman-admin@babylon.socal-raves.org>;
0673 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
0674 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
0675 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
0676 \tfor <mailman-admin@babylon.socal-raves.org>;
0677 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
0678 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
0679 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
0680 \tfor <mailman-admin@babylon.socal-raves.org>;
0681 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
0682 
0683     def test_splitting_first_line_only_is_long(self):
0684         eq = self.ndiffAssertEqual
0685         hstr = """\
0686 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
0687 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
0688 \tid 17k4h5-00034i-00
0689 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
0690         h = Header(hstr, maxlinelen=78, header_name='Received',
0691                    continuation_ws='\t')
0692         eq(h.encode(), """\
0693 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
0694 \thelo=cthulhu.gerg.ca)
0695 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
0696 \tid 17k4h5-00034i-00
0697 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
0698 
0699     def test_long_8bit_header(self):
0700         eq = self.ndiffAssertEqual
0701         msg = Message()
0702         h = Header('Britische Regierung gibt', 'iso-8859-1',
0703                     header_name='Subject')
0704         h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
0705         msg['Subject'] = h
0706         eq(msg.as_string(), """\
0707 Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
0708  =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
0709 
0710 """)
0711 
0712     def test_long_8bit_header_no_charset(self):
0713         eq = self.ndiffAssertEqual
0714         msg = Message()
0715         msg['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
0716         eq(msg.as_string(), """\
0717 Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
0718 
0719 """)
0720 
0721     def test_long_to_header(self):
0722         eq = self.ndiffAssertEqual
0723         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>'
0724         msg = Message()
0725         msg['To'] = to
0726         eq(msg.as_string(0), '''\
0727 To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
0728 \t"Someone Test #B" <someone@umich.edu>,
0729 \t"Someone Test #C" <someone@eecs.umich.edu>,
0730 \t"Someone Test #D" <someone@eecs.umich.edu>
0731 
0732 ''')
0733 
0734     def test_long_line_after_append(self):
0735         eq = self.ndiffAssertEqual
0736         s = 'This is an example of string which has almost the limit of header length.'
0737         h = Header(s)
0738         h.append('Add another line.')
0739         eq(h.encode(), """\
0740 This is an example of string which has almost the limit of header length.
0741  Add another line.""")
0742 
0743     def test_shorter_line_with_append(self):
0744         eq = self.ndiffAssertEqual
0745         s = 'This is a shorter line.'
0746         h = Header(s)
0747         h.append('Add another sentence. (Surprise?)')
0748         eq(h.encode(),
0749            'This is a shorter line. Add another sentence. (Surprise?)')
0750 
0751     def test_long_field_name(self):
0752         eq = self.ndiffAssertEqual
0753         fn = 'X-Very-Very-Very-Long-Header-Name'
0754         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. "
0755         h = Header(gs, 'iso-8859-1', header_name=fn)
0756         # BAW: this seems broken because the first line is too long
0757         eq(h.encode(), """\
0758 =?iso-8859-1?q?Die_Mieter_treten_hier_?=
0759  =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
0760  =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
0761  =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
0762 
0763     def test_long_received_header(self):
0764         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'
0765         msg = Message()
0766         msg['Received-1'] = Header(h, continuation_ws='\t')
0767         msg['Received-2'] = h
0768         self.assertEqual(msg.as_string(), """\
0769 Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
0770 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
0771 \tWed, 05 Mar 2003 18:10:18 -0700
0772 Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
0773 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
0774 \tWed, 05 Mar 2003 18:10:18 -0700
0775 
0776 """)
0777 
0778     def test_string_headerinst_eq(self):
0779         h = '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
0780         msg = Message()
0781         msg['Received-1'] = Header(h, header_name='Received-1',
0782                                    continuation_ws='\t')
0783         msg['Received-2'] = h
0784         self.assertEqual(msg.as_string(), """\
0785 Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
0786 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
0787 Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
0788 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
0789 
0790 """)
0791 
0792     def test_long_unbreakable_lines_with_continuation(self):
0793         eq = self.ndiffAssertEqual
0794         msg = Message()
0795         t = """\
0796  iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
0797  locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
0798         msg['Face-1'] = t
0799         msg['Face-2'] = Header(t, header_name='Face-2')
0800         eq(msg.as_string(), """\
0801 Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
0802 \tlocQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
0803 Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
0804  locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
0805 
0806 """)
0807 
0808     def test_another_long_multiline_header(self):
0809         eq = self.ndiffAssertEqual
0810         m = '''\
0811 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
0812 \tWed, 16 Oct 2002 07:41:11 -0700'''
0813         msg = email.message_from_string(m)
0814         eq(msg.as_string(), '''\
0815 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
0816 \tMicrosoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
0817 
0818 ''')
0819 
0820     def test_long_lines_with_different_header(self):
0821         eq = self.ndiffAssertEqual
0822         h = """\
0823 List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
0824         <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
0825         msg = Message()
0826         msg['List'] = h
0827         msg['List'] = Header(h, header_name='List')
0828         eq(msg.as_string(), """\
0829 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
0830 \t<mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
0831 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
0832  <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
0833 
0834 """)
0835 
0836 
0837 
0838 # Test mangling of "From " lines in the body of a message
0839 class TestFromMangling(unittest.TestCase):
0840     def setUp(self):
0841         self.msg = Message()
0842         self.msg['From'] = 'aaa@bbb.org'
0843         self.msg.set_payload("""\
0844 From the desk of A.A.A.:
0845 Blah blah blah
0846 """)
0847 
0848     def test_mangled_from(self):
0849         s = StringIO()
0850         g = Generator(s, mangle_from_=True)
0851         g.flatten(self.msg)
0852         self.assertEqual(s.getvalue(), """\
0853 From: aaa@bbb.org
0854 
0855 >From the desk of A.A.A.:
0856 Blah blah blah
0857 """)
0858 
0859     def test_dont_mangle_from(self):
0860         s = StringIO()
0861         g = Generator(s, mangle_from_=False)
0862         g.flatten(self.msg)
0863         self.assertEqual(s.getvalue(), """\
0864 From: aaa@bbb.org
0865 
0866 From the desk of A.A.A.:
0867 Blah blah blah
0868 """)
0869 
0870 
0871 
0872 # Test the basic MIMEAudio class
0873 class TestMIMEAudio(unittest.TestCase):
0874     def setUp(self):
0875         # Make sure we pick up the audiotest.au that lives in email/test/data.
0876         # In Python, there's an audiotest.au living in Lib/test but that isn't
0877         # included in some binary distros that don't include the test
0878         # package.  The trailing empty string on the .join() is significant
0879         # since findfile() will do a dirname().
0880         datadir = os.path.join(os.path.dirname(landmark), 'data', '')
0881         fp = open(findfile('audiotest.au', datadir), 'rb')
0882         try:
0883             self._audiodata = fp.read()
0884         finally:
0885             fp.close()
0886         self._au = MIMEAudio(self._audiodata)
0887 
0888     def test_guess_minor_type(self):
0889         self.assertEqual(self._au.get_type(), 'audio/basic')
0890 
0891     def test_encoding(self):
0892         payload = self._au.get_payload()
0893         self.assertEqual(base64.decodestring(payload), self._audiodata)
0894 
0895     def checkSetMinor(self):
0896         au = MIMEAudio(self._audiodata, 'fish')
0897         self.assertEqual(im.get_type(), 'audio/fish')
0898 
0899     def test_add_header(self):
0900         eq = self.assertEqual
0901         unless = self.failUnless
0902         self._au.add_header('Content-Disposition', 'attachment',
0903                             filename='audiotest.au')
0904         eq(self._au['content-disposition'],
0905            'attachment; filename="audiotest.au"')
0906         eq(self._au.get_params(header='content-disposition'),
0907            [('attachment', ''), ('filename', 'audiotest.au')])
0908         eq(self._au.get_param('filename', header='content-disposition'),
0909            'audiotest.au')
0910         missing = []
0911         eq(self._au.get_param('attachment', header='content-disposition'), '')
0912         unless(self._au.get_param('foo', failobj=missing,
0913                                   header='content-disposition') is missing)
0914         # Try some missing stuff
0915         unless(self._au.get_param('foobar', missing) is missing)
0916         unless(self._au.get_param('attachment', missing,
0917                                   header='foobar') is missing)
0918 
0919 
0920 
0921 # Test the basic MIMEImage class
0922 class TestMIMEImage(unittest.TestCase):
0923     def setUp(self):
0924         fp = openfile('PyBanner048.gif')
0925         try:
0926             self._imgdata = fp.read()
0927         finally:
0928             fp.close()
0929         self._im = MIMEImage(self._imgdata)
0930 
0931     def test_guess_minor_type(self):
0932         self.assertEqual(self._im.get_type(), 'image/gif')
0933 
0934     def test_encoding(self):
0935         payload = self._im.get_payload()
0936         self.assertEqual(base64.decodestring(payload), self._imgdata)
0937 
0938     def checkSetMinor(self):
0939         im = MIMEImage(self._imgdata, 'fish')
0940         self.assertEqual(im.get_type(), 'image/fish')
0941 
0942     def test_add_header(self):
0943         eq = self.assertEqual
0944         unless = self.failUnless
0945         self._im.add_header('Content-Disposition', 'attachment',
0946                             filename='dingusfish.gif')
0947         eq(self._im['content-disposition'],
0948            'attachment; filename="dingusfish.gif"')
0949         eq(self._im.get_params(header='content-disposition'),
0950            [('attachment', ''), ('filename', 'dingusfish.gif')])
0951         eq(self._im.get_param('filename', header='content-disposition'),
0952            'dingusfish.gif')
0953         missing = []
0954         eq(self._im.get_param('attachment', header='content-disposition'), '')
0955         unless(self._im.get_param('foo', failobj=missing,
0956                                   header='content-disposition') is missing)
0957         # Try some missing stuff
0958         unless(self._im.get_param('foobar', missing) is missing)
0959         unless(self._im.get_param('attachment', missing,
0960                                   header='foobar') is missing)
0961 
0962 
0963 
0964 # Test the basic MIMEText class
0965 class TestMIMEText(unittest.TestCase):
0966     def setUp(self):
0967         self._msg = MIMEText('hello there')
0968 
0969     def test_types(self):
0970         eq = self.assertEqual
0971         unless = self.failUnless
0972         eq(self._msg.get_type(), 'text/plain')
0973         eq(self._msg.get_param('charset'), 'us-ascii')
0974         missing = []
0975         unless(self._msg.get_param('foobar', missing) is missing)
0976         unless(self._msg.get_param('charset', missing, header='foobar')
0977                is missing)
0978 
0979     def test_payload(self):
0980         self.assertEqual(self._msg.get_payload(), 'hello there')
0981         self.failUnless(not self._msg.is_multipart())
0982 
0983     def test_charset(self):
0984         eq = self.assertEqual
0985         msg = MIMEText('hello there', _charset='us-ascii')
0986         eq(msg.get_charset().input_charset, 'us-ascii')
0987         eq(msg['content-type'], 'text/plain; charset="us-ascii"')
0988 
0989 
0990 
0991 # Test complicated multipart/* messages
0992 class TestMultipart(TestEmailBase):
0993     def setUp(self):
0994         fp = openfile('PyBanner048.gif')
0995         try:
0996             data = fp.read()
0997         finally:
0998             fp.close()
0999 
1000         container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
1001         image = MIMEImage(data, name='dingusfish.gif')
1002         image.add_header('content-disposition', 'attachment',
1003                          filename='dingusfish.gif')
1004         intro = MIMEText('''\
1005 Hi there,
1006 
1007 This is the dingus fish.
1008 ''')
1009         container.attach(intro)
1010         container.attach(image)
1011         container['From'] = 'Barry <barry@digicool.com>'
1012         container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1013         container['Subject'] = 'Here is your dingus fish'
1014 
1015         now = 987809702.54848599
1016         timetuple = time.localtime(now)
1017         if timetuple[-1] == 0:
1018             tzsecs = time.timezone
1019         else:
1020             tzsecs = time.altzone
1021         if tzsecs > 0:
1022             sign = '-'
1023         else:
1024             sign = '+'
1025         tzoffset = ' %s%04d' % (sign, tzsecs / 36)
1026         container['Date'] = time.strftime(
1027             '%a, %d %b %Y %H:%M:%S',
1028             time.localtime(now)) + tzoffset
1029         self._msg = container
1030         self._im = image
1031         self._txt = intro
1032 
1033     def test_hierarchy(self):
1034         # convenience
1035         eq = self.assertEqual
1036         unless = self.failUnless
1037         raises = self.assertRaises
1038         # tests
1039         m = self._msg
1040         unless(m.is_multipart())
1041         eq(m.get_type(), 'multipart/mixed')
1042         eq(len(m.get_payload()), 2)
1043         raises(IndexError, m.get_payload, 2)
1044         m0 = m.get_payload(0)
1045         m1 = m.get_payload(1)
1046         unless(m0 is self._txt)
1047         unless(m1 is self._im)
1048         eq(m.get_payload(), [m0, m1])
1049         unless(not m0.is_multipart())
1050         unless(not m1.is_multipart())
1051 
1052     def test_empty_multipart_idempotent(self):
1053         text = """\
1054 Content-Type: multipart/mixed; boundary="BOUNDARY"
1055 MIME-Version: 1.0
1056 Subject: A subject
1057 To: aperson@dom.ain
1058 From: bperson@dom.ain
1059 
1060 
1061 --BOUNDARY
1062 
1063 
1064 --BOUNDARY--
1065 """
1066         msg = Parser().parsestr(text)
1067         self.ndiffAssertEqual(text, msg.as_string())
1068 
1069     def test_no_parts_in_a_multipart_with_none_epilogue(self):
1070         outer = MIMEBase('multipart', 'mixed')
1071         outer['Subject'] = 'A subject'
1072         outer['To'] = 'aperson@dom.ain'
1073         outer['From'] = 'bperson@dom.ain'
1074         outer.set_boundary('BOUNDARY')
1075         self.ndiffAssertEqual(outer.as_string(), '''\
1076 Content-Type: multipart/mixed; boundary="BOUNDARY"
1077 MIME-Version: 1.0
1078 Subject: A subject
1079 To: aperson@dom.ain
1080 From: bperson@dom.ain
1081 
1082 --BOUNDARY
1083 
1084 --BOUNDARY--''')
1085 
1086     def test_no_parts_in_a_multipart_with_empty_epilogue(self):
1087         outer = MIMEBase('multipart', 'mixed')
1088         outer['Subject'] = 'A subject'
1089         outer['To'] = 'aperson@dom.ain'
1090         outer['From'] = 'bperson@dom.ain'
1091         outer.preamble = ''
1092         outer.epilogue = ''
1093         outer.set_boundary('BOUNDARY')
1094         self.ndiffAssertEqual(outer.as_string(), '''\
1095 Content-Type: multipart/mixed; boundary="BOUNDARY"
1096 MIME-Version: 1.0
1097 Subject: A subject
1098 To: aperson@dom.ain
1099 From: bperson@dom.ain
1100 
1101 
1102 --BOUNDARY
1103 
1104 --BOUNDARY--
1105 ''')
1106 
1107     def test_one_part_in_a_multipart(self):
1108         eq = self.ndiffAssertEqual
1109         outer = MIMEBase('multipart', 'mixed')
1110         outer['Subject'] = 'A subject'
1111         outer['To'] = 'aperson@dom.ain'
1112         outer['From'] = 'bperson@dom.ain'
1113         outer.set_boundary('BOUNDARY')
1114         msg = MIMEText('hello world')
1115         outer.attach(msg)
1116         eq(outer.as_string(), '''\
1117 Content-Type: multipart/mixed; boundary="BOUNDARY"
1118 MIME-Version: 1.0
1119 Subject: A subject
1120 To: aperson@dom.ain
1121 From: bperson@dom.ain
1122 
1123 --BOUNDARY
1124 Content-Type: text/plain; charset="us-ascii"
1125 MIME-Version: 1.0
1126 Content-Transfer-Encoding: 7bit
1127 
1128 hello world
1129 --BOUNDARY--''')
1130 
1131     def test_seq_parts_in_a_multipart_with_empty_preamble(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.preamble = ''
1138         msg = MIMEText('hello world')
1139         outer.attach(msg)
1140         outer.set_boundary('BOUNDARY')
1141         eq(outer.as_string(), '''\
1142 Content-Type: multipart/mixed; boundary="BOUNDARY"
1143 MIME-Version: 1.0
1144 Subject: A subject
1145 To: aperson@dom.ain
1146 From: bperson@dom.ain
1147 
1148 
1149 --BOUNDARY
1150 Content-Type: text/plain; charset="us-ascii"
1151 MIME-Version: 1.0
1152 Content-Transfer-Encoding: 7bit
1153 
1154 hello world
1155 --BOUNDARY--''')
1156 
1157 
1158     def test_seq_parts_in_a_multipart_with_none_preamble(self):
1159         eq = self.ndiffAssertEqual
1160         outer = MIMEBase('multipart', 'mixed')
1161         outer['Subject'] = 'A subject'
1162         outer['To'] = 'aperson@dom.ain'
1163         outer['From'] = 'bperson@dom.ain'
1164         outer.preamble = None
1165         msg = MIMEText('hello world')
1166         outer.attach(msg)
1167         outer.set_boundary('BOUNDARY')
1168         eq(outer.as_string(), '''\
1169 Content-Type: multipart/mixed; boundary="BOUNDARY"
1170 MIME-Version: 1.0
1171 Subject: A subject
1172 To: aperson@dom.ain
1173 From: bperson@dom.ain
1174 
1175 --BOUNDARY
1176 Content-Type: text/plain; charset="us-ascii"
1177 MIME-Version: 1.0
1178 Content-Transfer-Encoding: 7bit
1179 
1180 hello world
1181 --BOUNDARY--''')
1182 
1183 
1184     def test_seq_parts_in_a_multipart_with_none_epilogue(self):
1185         eq = self.ndiffAssertEqual
1186         outer = MIMEBase('multipart', 'mixed')
1187         outer['Subject'] = 'A subject'
1188         outer['To'] = 'aperson@dom.ain'
1189         outer['From'] = 'bperson@dom.ain'
1190         outer.epilogue = None
1191         msg = MIMEText('hello world')
1192         outer.attach(msg)
1193         outer.set_boundary('BOUNDARY')
1194         eq(outer.as_string(), '''\
1195 Content-Type: multipart/mixed; boundary="BOUNDARY"
1196 MIME-Version: 1.0
1197 Subject: A subject
1198 To: aperson@dom.ain
1199 From: bperson@dom.ain
1200 
1201 --BOUNDARY
1202 Content-Type: text/plain; charset="us-ascii"
1203 MIME-Version: 1.0
1204 Content-Transfer-Encoding: 7bit
1205 
1206 hello world
1207 --BOUNDARY--''')
1208 
1209 
1210     def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
1211         eq = self.ndiffAssertEqual
1212         outer = MIMEBase('multipart', 'mixed')
1213         outer['Subject'] = 'A subject'
1214         outer['To'] = 'aperson@dom.ain'
1215         outer['From'] = 'bperson@dom.ain'
1216         outer.epilogue = ''
1217         msg = MIMEText('hello world')
1218         outer.attach(msg)
1219         outer.set_boundary('BOUNDARY')
1220         eq(outer.as_string(), '''\
1221 Content-Type: multipart/mixed; boundary="BOUNDARY"
1222 MIME-Version: 1.0
1223 Subject: A subject
1224 To: aperson@dom.ain
1225 From: bperson@dom.ain
1226 
1227 --BOUNDARY
1228 Content-Type: text/plain; charset="us-ascii"
1229 MIME-Version: 1.0
1230 Content-Transfer-Encoding: 7bit
1231 
1232 hello world
1233 --BOUNDARY--
1234 ''')
1235 
1236 
1237     def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
1238         eq = self.ndiffAssertEqual
1239         outer = MIMEBase('multipart', 'mixed')
1240         outer['Subject'] = 'A subject'
1241         outer['To'] = 'aperson@dom.ain'
1242         outer['From'] = 'bperson@dom.ain'
1243         outer.epilogue = '\n'
1244         msg = MIMEText('hello world')
1245         outer.attach(msg)
1246         outer.set_boundary('BOUNDARY')
1247         eq(outer.as_string(), '''\
1248 Content-Type: multipart/mixed; boundary="BOUNDARY"
1249 MIME-Version: 1.0
1250 Subject: A subject
1251 To: aperson@dom.ain
1252 From: bperson@dom.ain
1253 
1254 --BOUNDARY
1255 Content-Type: text/plain; charset="us-ascii"
1256 MIME-Version: 1.0
1257 Content-Transfer-Encoding: 7bit
1258 
1259 hello world
1260 --BOUNDARY--
1261 
1262 ''')
1263 
1264     def test_message_external_body(self):
1265         eq = self.assertEqual
1266         msg = self._msgobj('msg_36.txt')
1267         eq(len(msg.get_payload()), 2)
1268         msg1 = msg.get_payload(1)
1269         eq(msg1.get_content_type(), 'multipart/alternative')
1270         eq(len(msg1.get_payload()), 2)
1271         for subpart in msg1.get_payload():
1272             eq(subpart.get_content_type(), 'message/external-body')
1273             eq(len(subpart.get_payload()), 1)
1274             subsubpart = subpart.get_payload(0)
1275             eq(subsubpart.get_content_type(), 'text/plain')
1276 
1277     def test_double_boundary(self):
1278         # msg_37.txt is a multipart that contains two dash-boundary's in a
1279         # row.  Our interpretation of RFC 2046 calls for ignoring the second
1280         # and subsequent boundaries.
1281         msg = self._msgobj('msg_37.txt')
1282         self.assertEqual(len(msg.get_payload()), 3)
1283 
1284     def test_nested_inner_contains_outer_boundary(self):
1285         eq = self.ndiffAssertEqual
1286         # msg_38.txt has an inner part that contains outer boundaries.  My
1287         # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
1288         # these are illegal and should be interpreted as unterminated inner
1289         # parts.
1290         msg = self._msgobj('msg_38.txt')
1291         sfp = StringIO()
1292         Iterators._structure(msg, sfp)
1293         eq(sfp.getvalue(), """\
1294 multipart/mixed
1295     multipart/mixed
1296         multipart/alternative
1297             text/plain
1298         text/plain
1299     text/plain
1300     text/plain
1301 """)
1302 
1303     def test_nested_with_same_boundary(self):
1304         eq = self.ndiffAssertEqual
1305         # msg 39.txt is similarly evil in that it's got inner parts that use
1306         # the same boundary as outer parts.  Again, I believe the way this is
1307         # parsed is closest to the spirit of RFC 2046
1308         msg = self._msgobj('msg_39.txt')
1309         sfp = StringIO()
1310         Iterators._structure(msg, sfp)
1311         eq(sfp.getvalue(), """\
1312 multipart/mixed
1313     multipart/mixed
1314         multipart/alternative
1315         application/octet-stream
1316         application/octet-stream
1317     text/plain
1318 """)
1319 
1320     def test_boundary_in_non_multipart(self):
1321         msg = self._msgobj('msg_40.txt')
1322         self.assertEqual(msg.as_string(), '''\
1323 MIME-Version: 1.0
1324 Content-Type: text/html; boundary="--961284236552522269"
1325 
1326 ----961284236552522269
1327 Content-Type: text/html;
1328 Content-Transfer-Encoding: 7Bit
1329 
1330 <html></html>
1331 
1332 ----961284236552522269--
1333 ''')
1334 
1335 
1336 
1337 # Test some badly formatted messages
1338 class TestNonConformant(TestEmailBase):
1339     def test_parse_missing_minor_type(self):
1340         eq = self.assertEqual
1341         msg = self._msgobj('msg_14.txt')
1342         eq(msg.get_type(), 'text')
1343         eq(msg.get_content_maintype(), 'text')
1344         eq(msg.get_content_subtype(), 'plain')
1345 
1346     def test_same_boundary_inner_outer(self):
1347         unless = self.failUnless
1348         msg = self._msgobj('msg_15.txt')
1349         # XXX We can probably eventually do better
1350         inner = msg.get_payload(0)
1351         unless(hasattr(inner, 'defects'))
1352         self.assertEqual(len(inner.defects), 1)
1353         unless(isinstance(inner.defects[0],
1354                           Errors.StartBoundaryNotFoundDefect))
1355 
1356     def test_multipart_no_boundary(self):
1357         unless = self.failUnless
1358         msg = self._msgobj('msg_25.txt')
1359         unless(isinstance(msg.get_payload(), str))
1360         self.assertEqual(len(msg.defects), 2)
1361         unless(isinstance(msg.defects[0], Errors.NoBoundaryInMultipartDefect))
1362         unless(isinstance(msg.defects[1],
1363                           Errors.MultipartInvariantViolationDefect))
1364 
1365     def test_invalid_content_type(self):
1366         eq = self.assertEqual
1367         neq = self.ndiffAssertEqual
1368         msg = Message()
1369         # RFC 2045, $5.2 says invalid yields text/plain
1370         msg['Content-Type'] = 'text'
1371         eq(msg.get_content_maintype(), 'text')
1372         eq(msg.get_content_subtype(), 'plain')
1373         eq(msg.get_content_type(), 'text/plain')
1374         # Clear the old value and try something /really/ invalid
1375         del msg['content-type']
1376         msg['Content-Type'] = 'foo'
1377         eq(msg.get_content_maintype(), 'text')
1378         eq(msg.get_content_subtype(), 'plain')
1379         eq(msg.get_content_type(), 'text/plain')
1380         # Still, make sure that the message is idempotently generated
1381         s = StringIO()
1382         g = Generator(s)
1383         g.flatten(msg)
1384         neq(s.getvalue(), 'Content-Type: foo\n\n')
1385 
1386     def test_no_start_boundary(self):
1387         eq = self.ndiffAssertEqual
1388         msg = self._msgobj('msg_31.txt')
1389         eq(msg.get_payload(), """\
1390 --BOUNDARY
1391 Content-Type: text/plain
1392 
1393 message 1
1394 
1395 --BOUNDARY
1396 Content-Type: text/plain
1397 
1398 message 2
1399 
1400 --BOUNDARY--
1401 """)
1402 
1403     def test_no_separating_blank_line(self):
1404         eq = self.ndiffAssertEqual
1405         msg = self._msgobj('msg_35.txt')
1406         eq(msg.as_string(), """\
1407 From: aperson@dom.ain
1408 To: bperson@dom.ain
1409 Subject: here's something interesting
1410 
1411 counter to RFC 2822, there's no separating newline here
1412 """)
1413 
1414     def test_lying_multipart(self):
1415         unless = self.failUnless
1416         msg = self._msgobj('msg_41.txt')
1417         unless(hasattr(msg, 'defects'))
1418         self.assertEqual(len(msg.defects), 2)
1419         unless(isinstance(msg.defects[0], Errors.NoBoundaryInMultipartDefect))
1420         unless(isinstance(msg.defects[1],
1421                           Errors.MultipartInvariantViolationDefect))
1422 
1423     def test_missing_start_boundary(self):
1424         outer = self._msgobj('msg_42.txt')
1425         # The message structure is:
1426         #
1427         # multipart/mixed
1428         #    text/plain
1429         #    message/rfc822
1430         #        multipart/mixed [*]
1431         #
1432         # [*] This message is missing its start boundary
1433         bad = outer.get_payload(1).get_payload(0)
1434         self.assertEqual(len(bad.defects), 1)
1435         self.failUnless(isinstance(bad.defects[0],
1436                                    Errors.StartBoundaryNotFoundDefect))
1437 
1438 
1439 
1440 # Test RFC 2047 header encoding and decoding
1441 class TestRFC2047(unittest.TestCase):
1442     def test_rfc2047_multiline(self):
1443         eq = self.assertEqual
1444         s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1445  foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1446         dh = decode_header(s)
1447         eq(dh, [
1448             ('Re:', None),
1449             ('r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1450             ('baz foo bar', None),
1451             ('r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1452         eq(str(make_header(dh)),
1453            """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar
1454  =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""")
1455 
1456     def test_whitespace_eater_unicode(self):
1457         eq = self.assertEqual
1458         s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1459         dh = decode_header(s)
1460         eq(dh, [('Andr\xe9', 'iso-8859-1'), ('Pirard <pirard@dom.ain>', None)])
1461         hu = unicode(make_header(dh)).encode('latin-1')
1462         eq(hu, 'Andr\xe9 Pirard <pirard@dom.ain>')
1463 
1464     def test_whitespace_eater_unicode_2(self):
1465         eq = self.assertEqual
1466         s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1467         dh = decode_header(s)
1468         eq(dh, [('The', None), ('quick brown fox', 'iso-8859-1'),
1469                 ('jumped over the', None), ('lazy dog', 'iso-8859-1')])
1470         hu = make_header(dh).__unicode__()
1471         eq(hu, u'The quick brown fox jumped over the lazy dog')
1472 
1473 
1474 
1475 # Test the MIMEMessage class
1476 class TestMIMEMessage(TestEmailBase):
1477     def setUp(self):
1478         fp = openfile('msg_11.txt')
1479         try:
1480             self._text = fp.read()
1481         finally:
1482             fp.close()
1483 
1484     def test_type_error(self):
1485         self.assertRaises(TypeError, MIMEMessage, 'a plain string')
1486 
1487     def test_valid_argument(self):
1488         eq = self.assertEqual
1489         unless = self.failUnless
1490         subject = 'A sub-message'
1491         m = Message()
1492         m['Subject'] = subject
1493         r = MIMEMessage(m)
1494         eq(r.get_type(), 'message/rfc822')
1495         payload = r.get_payload()
1496         unless(isinstance(payload, list))
1497         eq(len(payload), 1)
1498         subpart = payload[0]
1499         unless(subpart is m)
1500         eq(subpart['subject'], subject)
1501 
1502     def test_bad_multipart(self):
1503         eq = self.assertEqual
1504         msg1 = Message()
1505         msg1['Subject'] = 'subpart 1'
1506         msg2 = Message()
1507         msg2['Subject'] = 'subpart 2'
1508         r = MIMEMessage(msg1)
1509         self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
1510 
1511     def test_generate(self):
1512         # First craft the message to be encapsulated
1513         m = Message()
1514         m['Subject'] = 'An enclosed message'
1515         m.set_payload('Here is the body of the message.\n')
1516         r = MIMEMessage(m)
1517         r['Subject'] = 'The enclosing message'
1518         s = StringIO()
1519         g = Generator(s)
1520         g.flatten(r)
1521         self.assertEqual(s.getvalue(), """\
1522 Content-Type: message/rfc822
1523 MIME-Version: 1.0
1524 Subject: The enclosing message
1525 
1526 Subject: An enclosed message
1527 
1528 Here is the body of the message.
1529 """)
1530 
1531     def test_parse_message_rfc822(self):
1532         eq = self.assertEqual
1533         unless = self.failUnless
1534         msg = self._msgobj('msg_11.txt')
1535         eq(msg.get_type(), 'message/rfc822')
1536         payload = msg.get_payload()
1537         unless(isinstance(payload, list))
1538         eq(len(payload), 1)
1539         submsg = payload[0]
1540         self.failUnless(isinstance(submsg, Message))
1541         eq(submsg['subject'], 'An enclosed message')
1542         eq(submsg.get_payload(), 'Here is the body of the message.\n')
1543 
1544     def test_dsn(self):
1545         eq = self.assertEqual
1546         unless = self.failUnless
1547         # msg 16 is a Delivery Status Notification, see RFC 1894
1548         msg = self._msgobj('msg_16.txt')
1549         eq(msg.get_type(), 'multipart/report')
1550         unless(msg.is_multipart())
1551         eq(len(msg.get_payload()), 3)
1552         # Subpart 1 is a text/plain, human readable section
1553         subpart = msg.get_payload(0)
1554         eq(subpart.get_type(), 'text/plain')
1555         eq(subpart.get_payload(), """\
1556 This report relates to a message you sent with the following header fields:
1557 
1558   Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1559   Date: Sun, 23 Sep 2001 20:10:55 -0700
1560   From: "Ian T. Henry" <henryi@oxy.edu>
1561   To: SoCal Raves <scr@socal-raves.org>
1562   Subject: [scr] yeah for Ians!!
1563 
1564 Your message cannot be delivered to the following recipients:
1565 
1566   Recipient address: jangel1@cougar.noc.ucla.edu
1567   Reason: recipient reached disk quota
1568 
1569 """)
1570         # Subpart 2 contains the machine parsable DSN information.  It
1571         # consists of two blocks of headers, represented by two nested Message
1572         # objects.
1573         subpart = msg.get_payload(1)
1574         eq(subpart.get_type(), 'message/delivery-status')
1575         eq(len(subpart.get_payload()), 2)
1576         # message/delivery-status should treat each block as a bunch of
1577         # headers, i.e. a bunch of Message objects.
1578         dsn1 = subpart.get_payload(0)
1579         unless(isinstance(dsn1, Message))
1580         eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1581         eq(dsn1.get_param('dns', header='reporting-mta'), '')
1582         # Try a missing one <wink>
1583         eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1584         dsn2 = subpart.get_payload(1)
1585         unless(isinstance(dsn2, Message))
1586         eq(dsn2['action'], 'failed')
1587         eq(dsn2.get_params(header='original-recipient'),
1588            [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1589         eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1590         # Subpart 3 is the original message
1591         subpart = msg.get_payload(2)
1592         eq(subpart.get_type(), 'message/rfc822')
1593         payload = subpart.get_payload()
1594         unless(isinstance(payload, list))
1595         eq(len(payload), 1)
1596         subsubpart = payload[0]
1597         unless(isinstance(subsubpart, Message))
1598         eq(subsubpart.get_type(), 'text/plain')
1599         eq(subsubpart['message-id'],
1600            '<002001c144a6$8752e060$56104586@oxy.edu>')
1601 
1602     def test_epilogue(self):
1603         eq = self.ndiffAssertEqual
1604         fp = openfile('msg_21.txt')
1605         try:
1606             text = fp.read()
1607         finally:
1608             fp.close()
1609         msg = Message()
1610         msg['From'] = 'aperson@dom.ain'
1611         msg['To'] = 'bperson@dom.ain'
1612         msg['Subject'] = 'Test'
1613         msg.preamble = 'MIME message'
1614         msg.epilogue = 'End of MIME message\n'
1615         msg1 = MIMEText('One')
1616         msg2 = MIMEText('Two')
1617         msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1618         msg.attach(msg1)
1619         msg.attach(msg2)
1620         sfp = StringIO()
1621         g = Generator(sfp)
1622         g.flatten(msg)
1623         eq(sfp.getvalue(), text)
1624 
1625     def test_no_nl_preamble(self):
1626         eq = self.ndiffAssertEqual
1627         msg = Message()
1628         msg['From'] = 'aperson@dom.ain'
1629         msg['To'] = 'bperson@dom.ain'
1630         msg['Subject'] = 'Test'
1631         msg.preamble = 'MIME message'
1632         msg.epilogue = ''
1633         msg1 = MIMEText('One')
1634         msg2 = MIMEText('Two')
1635         msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1636         msg.attach(msg1)
1637         msg.attach(msg2)
1638         eq(msg.as_string(), """\
1639 From: aperson@dom.ain
1640 To: bperson@dom.ain
1641 Subject: Test
1642 Content-Type: multipart/mixed; boundary="BOUNDARY"
1643 
1644 MIME message
1645 --BOUNDARY
1646 Content-Type: text/plain; charset="us-ascii"
1647 MIME-Version: 1.0
1648 Content-Transfer-Encoding: 7bit
1649 
1650 One
1651 --BOUNDARY
1652 Content-Type: text/plain; charset="us-ascii"
1653 MIME-Version: 1.0
1654 Content-Transfer-Encoding: 7bit
1655 
1656 Two
1657 --BOUNDARY--
1658 """)
1659 
1660     def test_default_type(self):
1661         eq = self.assertEqual
1662         fp = openfile('msg_30.txt')
1663         try:
1664             msg = email.message_from_file(fp)
1665         finally:
1666             fp.close()
1667         container1 = msg.get_payload(0)
1668         eq(container1.get_default_type(), 'message/rfc822')
1669         eq(container1.get_type(), None)
1670         container2 = msg.get_payload(1)
1671         eq(container2.get_default_type(), 'message/rfc822')
1672         eq(container2.get_type(), None)
1673         container1a = container1.get_payload(0)
1674         eq(container1a.get_default_type(), 'text/plain')
1675         eq(container1a.get_type(), 'text/plain')
1676         container2a = container2.get_payload(0)
1677         eq(container2a.get_default_type(), 'text/plain')
1678         eq(container2a.get_type(), 'text/plain')
1679 
1680     def test_default_type_with_explicit_container_type(self):
1681         eq = self.assertEqual
1682         fp = openfile('msg_28.txt')
1683         try:
1684             msg = email.message_from_file(fp)
1685         finally:
1686             fp.close()
1687         container1 = msg.get_payload(0)
1688         eq(container1.get_default_type(), 'message/rfc822')
1689         eq(container1.get_type(), 'message/rfc822')
1690         container2 = msg.get_payload(1)
1691         eq(container2.get_default_type(), 'message/rfc822')
1692         eq(container2.get_type(), 'message/rfc822')
1693         container1a = container1.get_payload(0)
1694         eq(container1a.get_default_type(), 'text/plain')
1695         eq(container1a.get_type(), 'text/plain')
1696         container2a = container2.get_payload(0)
1697         eq(container2a.get_default_type(), 'text/plain')
1698         eq(container2a.get_type(), 'text/plain')
1699 
1700     def test_default_type_non_parsed(self):
1701         eq = self.assertEqual
1702         neq = self.ndiffAssertEqual
1703         # Set up container
1704         container = MIMEMultipart('digest', 'BOUNDARY')
1705         container.epilogue = ''
1706         # Set up subparts
1707         subpart1a = MIMEText('message 1\n')
1708         subpart2a = MIMEText('message 2\n')
1709         subpart1 = MIMEMessage(subpart1a)
1710         subpart2 = MIMEMessage(subpart2a)
1711         container.attach(subpart1)
1712         container.attach(subpart2)
1713         eq(subpart1.get_type(), 'message/rfc822')
1714         eq(subpart1.get_default_type(), 'message/rfc822')
1715         eq(subpart2.get_type(), 'message/rfc822')
1716         eq(subpart2.get_default_type(), 'message/rfc822')
1717         neq(container.as_string(0), '''\
1718 Content-Type: multipart/digest; boundary="BOUNDARY"
1719 MIME-Version: 1.0
1720 
1721 --BOUNDARY
1722 Content-Type: message/rfc822
1723 MIME-Version: 1.0
1724 
1725 Content-Type: text/plain; charset="us-ascii"
1726 MIME-Version: 1.0
1727 Content-Transfer-Encoding: 7bit
1728 
1729 message 1
1730 
1731 --BOUNDARY
1732 Content-Type: message/rfc822
1733 MIME-Version: 1.0
1734 
1735 Content-Type: text/plain; charset="us-ascii"
1736 MIME-Version: 1.0
1737 Content-Transfer-Encoding: 7bit
1738 
1739 message 2
1740 
1741 --BOUNDARY--
1742 ''')
1743         del subpart1['content-type']
1744         del subpart1['mime-version']
1745         del subpart2['content-type']
1746         del subpart2['mime-version']
1747         eq(subpart1.get_type(), None)
1748         eq(subpart1.get_default_type(), 'message/rfc822')
1749         eq(subpart2.get_type(), None)
1750         eq(subpart2.get_default_type(), 'message/rfc822')
1751         neq(container.as_string(0), '''\
1752 Content-Type: multipart/digest; boundary="BOUNDARY"
1753 MIME-Version: 1.0
1754 
1755 --BOUNDARY
1756 
1757 Content-Type: text/plain; charset="us-ascii"
1758 MIME-Version: 1.0
1759 Content-Transfer-Encoding: 7bit
1760 
1761 message 1
1762 
1763 --BOUNDARY
1764 
1765 Content-Type: text/plain; charset="us-ascii"
1766 MIME-Version: 1.0
1767 Content-Transfer-Encoding: 7bit
1768 
1769 message 2
1770 
1771 --BOUNDARY--
1772 ''')
1773 
1774     def test_mime_attachments_in_constructor(self):
1775         eq = self.assertEqual
1776         text1 = MIMEText('')
1777         text2 = MIMEText('')
1778         msg = MIMEMultipart(_subparts=(text1, text2))
1779         eq(len(msg.get_payload()), 2)
1780         eq(msg.get_payload(0), text1)
1781         eq(msg.get_payload(1), text2)
1782 
1783 
1784 
1785 # A general test of parser->model->generator idempotency.  IOW, read a message
1786 # in, parse it into a message object tree, then without touching the tree,
1787 # regenerate the plain text.  The original text and the transformed text
1788 # should be identical.  Note: that we ignore the Unix-From since that may
1789 # contain a changed date.
1790 class TestIdempotent(TestEmailBase):
1791     def _msgobj(self, filename):
1792         fp = openfile(filename)
1793         try:
1794             data = fp.read()
1795         finally:
1796             fp.close()
1797         msg = email.message_from_string(data)
1798         return msg, data
1799 
1800     def _idempotent(self, msg, text):
1801         eq = self.ndiffAssertEqual
1802         s = StringIO()
1803         g = Generator(s, maxheaderlen=0)
1804         g.flatten(msg)
1805         eq(text, s.getvalue())
1806 
1807     def test_parse_text_message(self):
1808         eq = self.assertEquals
1809         msg, text = self._msgobj('msg_01.txt')
1810         eq(msg.get_type(), 'text/plain')
1811         eq(msg.get_content_maintype(), 'text')
1812         eq(msg.get_content_subtype(), 'plain')
1813         eq(msg.get_params()[1], ('charset', 'us-ascii'))
1814         eq(msg.get_param('charset'), 'us-ascii')
1815         eq(msg.preamble, None)
1816         eq(msg.epilogue, None)
1817         self._idempotent(msg, text)
1818 
1819     def test_parse_untyped_message(self):
1820         eq = self.assertEquals
1821         msg, text = self._msgobj('msg_03.txt')
1822         eq(msg.get_type(), None)
1823         eq(msg.get_params(), None)
1824         eq(msg.get_param('charset'), None)
1825         self._idempotent(msg, text)
1826 
1827     def test_simple_multipart(self):
1828         msg, text = self._msgobj('msg_04.txt')
1829         self._idempotent(msg, text)
1830 
1831     def test_MIME_digest(self):
1832         msg, text = self._msgobj('msg_02.txt')
1833         self._idempotent(msg, text)
1834 
1835     def test_long_header(self):
1836         msg, text = self._msgobj('msg_27.txt')
1837         self._idempotent(msg, text)
1838 
1839     def test_MIME_digest_with_part_headers(self):
1840         msg, text = self._msgobj('msg_28.txt')
1841         self._idempotent(msg, text)
1842 
1843     def test_mixed_with_image(self):
1844         msg, text = self._msgobj('msg_06.txt')
1845         self._idempotent(msg, text)
1846 
1847     def test_multipart_report(self):
1848         msg, text = self._msgobj('msg_05.txt')
1849         self._idempotent(msg, text)
1850 
1851     def test_dsn(self):
1852         msg, text = self._msgobj('msg_16.txt')
1853         self._idempotent(msg, text)
1854 
1855     def test_preamble_epilogue(self):
1856         msg, text = self._msgobj('msg_21.txt')
1857         self._idempotent(msg, text)
1858 
1859     def test_multipart_one_part(self):
1860         msg, text = self._msgobj('msg_23.txt')
1861         self._idempotent(msg, text)
1862 
1863     def test_multipart_no_parts(self):
1864         msg, text = self._msgobj('msg_24.txt')
1865         self._idempotent(msg, text)
1866 
1867     def test_no_start_boundary(self):
1868         msg, text = self._msgobj('msg_31.txt')
1869         self._idempotent(msg, text)
1870 
1871     def test_rfc2231_charset(self):
1872         msg, text = self._msgobj('msg_32.txt')
1873         self._idempotent(msg, text)
1874 
1875     def test_more_rfc2231_parameters(self):
1876         msg, text = self._msgobj('msg_33.txt')
1877         self._idempotent(msg, text)
1878 
1879     def test_text_plain_in_a_multipart_digest(self):
1880         msg, text = self._msgobj('msg_34.txt')
1881         self._idempotent(msg, text)
1882 
1883     def test_nested_multipart_mixeds(self):
1884         msg, text = self._msgobj('msg_12a.txt')
1885         self._idempotent(msg, text)
1886 
1887     def test_message_external_body_idempotent(self):
1888         msg, text = self._msgobj('msg_36.txt')
1889         self._idempotent(msg, text)
1890 
1891     def test_content_type(self):
1892         eq = self.assertEquals
1893         unless = self.failUnless
1894         # Get a message object and reset the seek pointer for other tests
1895         msg, text = self._msgobj('msg_05.txt')
1896         eq(msg.get_type(), 'multipart/report')
1897         # Test the Content-Type: parameters
1898         params = {}
1899         for pk, pv in msg.get_params():
1900             params[pk] = pv
1901         eq(params['report-type'], 'delivery-status')
1902         eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
1903         eq(msg.preamble, 'This is a MIME-encapsulated message.\n')
1904         eq(msg.epilogue, '\n')
1905         eq(len(msg.get_payload()), 3)
1906         # Make sure the subparts are what we expect
1907         msg1 = msg.get_payload(0)
1908         eq(msg1.get_type(), 'text/plain')
1909         eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1910         msg2 = msg.get_payload(1)
1911         eq(msg2.get_type(), None)
1912         eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1913         msg3 = msg.get_payload(2)
1914         eq(msg3.get_type(), 'message/rfc822')
1915         self.failUnless(isinstance(msg3, Message))
1916         payload = msg3.get_payload()
1917         unless(isinstance(payload, list))
1918         eq(len(payload), 1)
1919         msg4 = payload[0]
1920         unless(isinstance(msg4, Message))
1921         eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1922 
1923     def test_parser(self):
1924         eq = self.assertEquals
1925         unless = self.failUnless
1926         msg, text = self._msgobj('msg_06.txt')
1927         # Check some of the outer headers
1928         eq(msg.get_type(), 'message/rfc822')
1929         # Make sure the payload is a list of exactly one sub-Message, and that
1930         # that submessage has a type of text/plain
1931         payload = msg.get_payload()
1932         unless(isinstance(payload, list))
1933         eq(len(payload), 1)
1934         msg1 = payload[0]
1935         self.failUnless(isinstance(msg1, Message))
1936         eq(msg1.get_type(), 'text/plain')
1937         self.failUnless(isinstance(msg1.get_payload(), str))
1938         eq(msg1.get_payload(), '\n')
1939 
1940 
1941 
1942 # Test various other bits of the package's functionality
1943 class TestMiscellaneous(unittest.TestCase):
1944     def test_message_from_string(self):
1945         fp = openfile('msg_01.txt')
1946         try:
1947             text = fp.read()
1948         finally:
1949             fp.close()
1950         msg = email.message_from_string(text)
1951         s = StringIO()
1952         # Don't wrap/continue long headers since we're trying to test
1953         # idempotency.
1954         g = Generator(s, maxheaderlen=0)
1955         g.flatten(msg)
1956         self.assertEqual(text, s.getvalue())
1957 
1958     def test_message_from_file(self):
1959         fp = openfile('msg_01.txt')
1960         try:
1961             text = fp.read()
1962             fp.seek(0)
1963             msg = email.message_from_file(fp)
1964             s = StringIO()
1965             # Don't wrap/continue long headers since we're trying to test
1966             # idempotency.
1967             g = Generator(s, maxheaderlen=0)
1968             g.flatten(msg)
1969             self.assertEqual(text, s.getvalue())
1970         finally:
1971             fp.close()
1972 
1973     def test_message_from_string_with_class(self):
1974         unless = self.failUnless
1975         fp = openfile('msg_01.txt')
1976         try:
1977             text = fp.read()
1978         finally:
1979             fp.close()
1980         # Create a subclass
1981         class MyMessage(Message):
1982             pass
1983 
1984         msg = email.message_from_string(text, MyMessage)
1985         unless(isinstance(msg, MyMessage))
1986         # Try something more complicated
1987         fp = openfile('msg_02.txt')
1988         try:
1989             text = fp.read()
1990         finally:
1991             fp.close()
1992         msg = email.message_from_string(text, MyMessage)
1993         for subpart in msg.walk():
1994             unless(isinstance(subpart, MyMessage))
1995 
1996     def test_message_from_file_with_class(self):
1997         unless = self.failUnless
1998         # Create a subclass
1999         class MyMessage(Message):
2000             pass
2001 
2002         fp = openfile('msg_01.txt')
2003         try:
2004             msg = email.message_from_file(fp, MyMessage)
2005         finally:
2006             fp.close()
2007         unless(isinstance(msg, MyMessage))
2008         # Try something more complicated
2009         fp = openfile('msg_02.txt')
2010         try:
2011             msg = email.message_from_file(fp, MyMessage)
2012         finally:
2013             fp.close()
2014         for subpart in msg.walk():
2015             unless(isinstance(subpart, MyMessage))
2016 
2017     def test__all__(self):
2018         module = __import__('email')
2019         all = module.__all__
2020         all.sort()
2021         self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
2022                                'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
2023                                'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
2024                                'MIMENonMultipart', 'MIMEText', 'Message',
2025                                'Parser', 'Utils', 'base64MIME',
2026                                'message_from_file', 'message_from_string',
2027                                'quopriMIME'])
2028 
2029     def test_formatdate(self):
2030         now = time.time()
2031         self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
2032                          time.gmtime(now)[:6])
2033 
2034     def test_formatdate_localtime(self):
2035         now = time.time()
2036         self.assertEqual(
2037             Utils.parsedate(Utils.formatdate(now, localtime=True))[:6],
2038             time.localtime(now)[:6])
2039 
2040     def test_formatdate_usegmt(self):
2041         now = time.time()
2042         self.assertEqual(
2043             Utils.formatdate(now, localtime=False),
2044             time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
2045         self.assertEqual(
2046             Utils.formatdate(now, localtime=False, usegmt=True),
2047             time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
2048 
2049     def test_parsedate_none(self):
2050         self.assertEqual(Utils.parsedate(''), None)
2051 
2052     def test_parsedate_compact(self):
2053         # The FWS after the comma is optional
2054         self.assertEqual(Utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2055                          Utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2056 
2057     def test_parsedate_no_dayofweek(self):
2058         eq = self.assertEqual
2059         eq(Utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2060            (2003, 2, 25, 13, 47, 26, 0, 1, 0, -28800))
2061 
2062     def test_parsedate_compact_no_dayofweek(self):
2063         eq = self.assertEqual
2064         eq(Utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2065            (2003, 2, 5, 13, 47, 26, 0, 1, 0, -28800))
2066 
2067     def test_parseaddr_empty(self):
2068         self.assertEqual(Utils.parseaddr('<>'), ('', ''))
2069         self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
2070 
2071     def test_noquote_dump(self):
2072         self.assertEqual(
2073             Utils.formataddr(('A Silly Person', 'person@dom.ain')),
2074             'A Silly Person <person@dom.ain>')
2075 
2076     def test_escape_dump(self):
2077         self.assertEqual(
2078             Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
2079             r'"A \(Very\) Silly Person" <person@dom.ain>')
2080         a = r'A \(Special\) Person'
2081         b = 'person@dom.ain'
2082         self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
2083 
2084     def test_escape_backslashes(self):
2085         self.assertEqual(
2086             Utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
2087             r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
2088         a = r'Arthur \Backslash\ Foobar'
2089         b = 'person@dom.ain'
2090         self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
2091 
2092     def test_name_with_dot(self):
2093         x = 'John X. Doe <jxd@example.com>'
2094         y = '"John X. Doe" <jxd@example.com>'
2095         a, b = ('John X. Doe', 'jxd@example.com')
2096         self.assertEqual(Utils.parseaddr(x), (a, b))
2097         self.assertEqual(Utils.parseaddr(y), (a, b))
2098         # formataddr() quotes the name if there's a dot in it
2099         self.assertEqual(Utils.formataddr((a, b)), y)
2100 
2101     def test_quote_dump(self):
2102         self.assertEqual(
2103             Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
2104             r'"A Silly; Person" <person@dom.ain>')
2105 
2106     def test_fix_eols(self):
2107         eq = self.assertEqual
2108         eq(Utils.fix_eols('hello'), 'hello')
2109         eq(Utils.fix_eols('hello\n'), 'hello\r\n')
2110         eq(Utils.fix_eols('hello\r'), 'hello\r\n')
2111         eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
2112         eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
2113 
2114     def test_charset_richcomparisons(self):
2115         eq = self.assertEqual
2116         ne = self.failIfEqual
2117         cset1 = Charset()
2118         cset2 = Charset()
2119         eq(cset1, 'us-ascii')
2120         eq(cset1, 'US-ASCII')
2121         eq(cset1, 'Us-AsCiI')
2122         eq('us-ascii', cset1)
2123         eq('US-ASCII', cset1)
2124         eq('Us-AsCiI', cset1)
2125         ne(cset1, 'usascii')
2126         ne(cset1, 'USASCII')
2127         ne(cset1, 'UsAsCiI')
2128         ne('usascii', cset1)
2129         ne('USASCII', cset1)
2130         ne('UsAsCiI', cset1)
2131         eq(cset1, cset2)
2132         eq(cset2, cset1)
2133 
2134     def test_getaddresses(self):
2135         eq = self.assertEqual
2136         eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
2137                                'Bud Person <bperson@dom.ain>']),
2138            [('Al Person', 'aperson@dom.ain'),
2139             ('Bud Person', 'bperson@dom.ain')])
2140 
2141     def test_getaddresses_nasty(self):
2142         eq = self.assertEqual
2143         eq(Utils.getaddresses(['foo: ;']), [('', '')])
2144         eq(Utils.getaddresses(
2145            ['[]*-- =~$']),
2146            [('', ''), ('', ''), ('', '*--')])
2147         eq(Utils.getaddresses(
2148            ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
2149            [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
2150 
2151     def test_utils_quote_unquote(self):
2152         eq = self.assertEqual
2153         msg = Message()
2154         msg.add_header('content-disposition', 'attachment',
2155                        filename='foo\\wacky"name')
2156         eq(msg.get_filename(), 'foo\\wacky"name')
2157 
2158     def test_get_body_encoding_with_bogus_charset(self):
2159         charset = Charset('not a charset')
2160         self.assertEqual(charset.get_body_encoding(), 'base64')
2161 
2162     def test_get_body_encoding_with_uppercase_charset(self):
2163         eq = self.assertEqual
2164         msg = Message()
2165         msg['Content-Type'] = 'text/plain; charset=UTF-8'
2166         eq(msg['content-type'], 'text/plain; charset=UTF-8')
2167         charsets = msg.get_charsets()
2168         eq(len(charsets), 1)
2169         eq(charsets[0], 'utf-8')
2170         charset = Charset(charsets[0])
2171         eq(charset.get_body_encoding(), 'base64')
2172         msg.set_payload('hello world', charset=charset)
2173         eq(msg.get_payload(), 'hello world')
2174         eq(msg['content-transfer-encoding'], 'base64')
2175         # Try another one
2176         msg = Message()
2177         msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
2178         charsets = msg.get_charsets()
2179         eq(len(charsets), 1)
2180         eq(charsets[0], 'us-ascii')
2181         charset = Charset(charsets[0])
2182         eq(charset.get_body_encoding(), Encoders.encode_7or8bit)
2183         msg.set_payload('hello world', charset=charset)
2184         eq(msg.get_payload(), 'hello world')
2185         eq(msg['content-transfer-encoding'], '7bit')
2186 
2187     def test_charsets_case_insensitive(self):
2188         lc = Charset('us-ascii')
2189         uc = Charset('US-ASCII')
2190         self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
2191 
2192 
2193 
2194 # Test the iterator/generators
2195 class TestIterators(TestEmailBase):
2196     def test_body_line_iterator(self):
2197         eq = self.assertEqual
2198         neq = self.ndiffAssertEqual
2199         # First a simple non-multipart message
2200         msg = self._msgobj('msg_01.txt')
2201         it = Iterators.body_line_iterator(msg)
2202         lines = list(it)
2203         eq(len(lines), 6)
2204         neq(EMPTYSTRING.join(lines), msg.get_payload())
2205         # Now a more complicated multipart
2206         msg = self._msgobj('msg_02.txt')
2207         it = Iterators.body_line_iterator(msg)
2208         lines = list(it)
2209         eq(len(lines), 43)
2210         fp = openfile('msg_19.txt')
2211         try:
2212             neq(EMPTYSTRING.join(lines), fp.read())
2213         finally:
2214             fp.close()
2215 
2216     def test_typed_subpart_iterator(self):
2217         eq = self.assertEqual
2218         msg = self._msgobj('msg_04.txt')
2219         it = Iterators.typed_subpart_iterator(msg, 'text')
2220         lines = []
2221         subparts = 0
2222         for subpart in it:
2223             subparts += 1
2224             lines.append(subpart.get_payload())
2225         eq(subparts, 2)
2226         eq(EMPTYSTRING.join(lines), """\
2227 a simple kind of mirror
2228 to reflect upon our own
2229 a simple kind of mirror
2230 to reflect upon our own
2231 """)
2232 
2233     def test_typed_subpart_iterator_default_type(self):
2234         eq = self.assertEqual
2235         msg = self._msgobj('msg_03.txt')
2236         it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
2237         lines = []
2238         subparts = 0
2239         for subpart in it:
2240             subparts += 1
2241             lines.append(subpart.get_payload())
2242         eq(subparts, 1)
2243         eq(EMPTYSTRING.join(lines), """\
2244 
2245 Hi,
2246 
2247 Do you like this message?
2248 
2249 -Me
2250 """)
2251 
2252 
2253 
2254 class TestParsers(TestEmailBase):
2255     def test_header_parser(self):
2256         eq = self.assertEqual
2257         # Parse only the headers of a complex multipart MIME document
2258         fp = openfile('msg_02.txt')
2259         try:
2260             msg = HeaderParser().parse(fp)
2261         finally:
2262             fp.close()
2263         eq(msg['from'], 'ppp-request@zzz.org')
2264         eq(msg['to'], 'ppp@zzz.org')
2265         eq(msg.get_type(), 'multipart/mixed')
2266         self.failIf(msg.is_multipart())
2267         self.failUnless(isinstance(msg.get_payload(), str))
2268 
2269     def test_whitespace_continuation(self):
2270         eq = self.assertEqual
2271         # This message contains a line after the Subject: header that has only
2272         # whitespace, but it is not empty!
2273         msg = email.message_from_string("""\
2274 From: aperson@dom.ain
2275 To: bperson@dom.ain
2276 Subject: the next line has a space on it
2277 \x20
2278 Date: Mon, 8 Apr 2002 15:09:19 -0400
2279 Message-ID: spam
2280 
2281 Here's the message body
2282 """)
2283         eq(msg['subject'], 'the next line has a space on it\n ')
2284         eq(msg['message-id'], 'spam')
2285         eq(msg.get_payload(), "Here's the message body\n")
2286 
2287     def test_whitespace_continuation_last_header(self):
2288         eq = self.assertEqual
2289         # Like the previous test, but the subject line is the last
2290         # header.
2291         msg = email.message_from_string("""\
2292 From: aperson@dom.ain
2293 To: bperson@dom.ain
2294 Date: Mon, 8 Apr 2002 15:09:19 -0400
2295 Message-ID: spam
2296 Subject: the next line has a space on it
2297 \x20
2298 
2299 Here's the message body
2300 """)
2301         eq(msg['subject'], 'the next line has a space on it\n ')
2302         eq(msg['message-id'], 'spam')
2303         eq(msg.get_payload(), "Here's the message body\n")
2304 
2305     def test_crlf_separation(self):
2306         eq = self.assertEqual
2307         fp = openfile('msg_26.txt', mode='rb')
2308         try:
2309             msg = Parser().parse(fp)
2310         finally:
2311             fp.close()
2312         eq(len(msg.get_payload()), 2)
2313         part1 = msg.get_payload(0)
2314         eq(part1.get_type(), 'text/plain')
2315         eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
2316         part2 = msg.get_payload(1)
2317         eq(part2.get_type(), 'application/riscos')
2318 
2319     def test_multipart_digest_with_extra_mime_headers(self):
2320         eq = self.assertEqual
2321         neq = self.ndiffAssertEqual
2322         fp = openfile('msg_28.txt')
2323         try:
2324             msg = email.message_from_file(fp)
2325         finally:
2326             fp.close()
2327         # Structure is:
2328         # multipart/digest
2329         #   message/rfc822
2330         #     text/plain
2331         #   message/rfc822
2332         #     text/plain
2333         eq(msg.is_multipart(), 1)
2334         eq(len(msg.get_payload()), 2)
2335         part1 = msg.get_payload(0)
2336         eq(part1.get_type(), 'message/rfc822')
2337         eq(part1.is_multipart(), 1)
2338         eq(len(part1.get_payload()), 1)
2339         part1a = part1.get_payload(0)
2340         eq(part1a.is_multipart(), 0)
2341         eq(part1a.get_type(), 'text/plain')
2342         neq(part1a.get_payload(), 'message 1\n')
2343         # next message/rfc822
2344         part2 = msg.get_payload(1)
2345         eq(part2.get_type(), 'message/rfc822')
2346         eq(part2.is_multipart(), 1)
2347         eq(len(part2.get_payload()), 1)
2348         part2a = part2.get_payload(0)
2349         eq(part2a.is_multipart(), 0)
2350         eq(part2a.get_type(), 'text/plain')
2351         neq(part2a.get_payload(), 'message 2\n')
2352 
2353     def test_three_lines(self):
2354         # A bug report by Andrew McNamara
2355         lines = ['From: Andrew Person <aperson@dom.ain',
2356                  'Subject: Test',
2357                  'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2358         msg = email.message_from_string(NL.join(lines))
2359         self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2360 
2361     def test_strip_line_feed_and_carriage_return_in_headers(self):
2362         eq = self.assertEqual
2363         # For [ 1002475 ] email message parser doesn't handle \r\n correctly
2364         value1 = 'text'
2365         value2 = 'more text'
2366         m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
2367             value1, value2)
2368         msg = email.message_from_string(m)
2369         eq(msg.get('Header'), value1)
2370         eq(msg.get('Next-Header'), value2)
2371 
2372 
2373 
2374 class TestBase64(unittest.TestCase):
2375     def test_len(self):
2376         eq = self.assertEqual
2377         eq(base64MIME.base64_len('hello'),
2378            len(base64MIME.encode('hello', eol='')))
2379         for size in range(15):
2380             if   size == 0 : bsize = 0
2381             elif size <= 3 : bsize = 4
2382             elif size <= 6 : bsize = 8
2383             elif size <= 9 : bsize = 12
2384             elif size <= 12: bsize = 16
2385             else           : bsize = 20
2386             eq(base64MIME.base64_len('x'*size), bsize)
2387 
2388     def test_decode(self):
2389         eq = self.assertEqual
2390         eq(base64MIME.decode(''), '')
2391         eq(base64MIME.decode('aGVsbG8='), 'hello')
2392         eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
2393         eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
2394 
2395     def test_encode(self):
2396         eq = self.assertEqual
2397         eq(base64MIME.encode(''), '')
2398         eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
2399         # Test the binary flag
2400         eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
2401         eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
2402         # Test the maxlinelen arg
2403         eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
2404 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2405 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2406 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2407 eHh4eCB4eHh4IA==
2408 """)
2409         # Test the eol argument
2410         eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2411 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2412 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2413 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2414 eHh4eCB4eHh4IA==\r
2415 """)
2416 
2417     def test_header_encode(self):
2418         eq = self.assertEqual
2419         he = base64MIME.header_encode
2420         eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
2421         eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
2422         # Test the charset option
2423         eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
2424         # Test the keep_eols flag
2425         eq(he('hello\nworld', keep_eols=True),
2426            '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
2427         # Test the maxlinelen argument
2428         eq(he('xxxx ' * 20, maxlinelen=40), """\
2429 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
2430  =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
2431  =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
2432  =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
2433  =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
2434  =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2435         # Test the eol argument
2436         eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2437 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
2438  =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
2439  =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
2440  =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
2441  =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
2442  =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2443 
2444 
2445 
2446 class TestQuopri(unittest.TestCase):
2447     def setUp(self):
2448         self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
2449                     [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
2450                     [chr(x) for x in range(ord('0'), ord('9')+1)] + \
2451                     ['!', '*', '+', '-', '/', ' ']
2452         self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
2453         assert len(self.hlit) + len(self.hnon) == 256
2454         self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
2455         self.blit.remove('=')
2456         self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
2457         assert len(self.blit) + len(self.bnon) == 256
2458 
2459     def test_header_quopri_check(self):
2460         for c in self.hlit:
2461             self.failIf(quopriMIME.header_quopri_check(c))
2462         for c in self.hnon:
2463             self.failUnless(quopriMIME.header_quopri_check(c))
2464 
2465     def test_body_quopri_check(self):
2466         for c in self.blit:
2467             self.failIf(quopriMIME.body_quopri_check(c))
2468         for c in self.bnon:
2469             self.failUnless(quopriMIME.body_quopri_check(c))
2470 
2471     def test_header_quopri_len(self):
2472         eq = self.assertEqual
2473         hql = quopriMIME.header_quopri_len
2474         enc = quopriMIME.header_encode
2475         for s in ('hello', 'h@e@l@l@o@'):
2476             # Empty charset and no line-endings.  7 == RFC chrome
2477             eq(hql(s), len(enc(s, charset='', eol=''))-7)
2478         for c in self.hlit:
2479             eq(hql(c), 1)
2480         for c in self.hnon:
2481             eq(hql(c), 3)
2482 
2483     def test_body_quopri_len(self):
2484         eq = self.assertEqual
2485         bql = quopriMIME.body_quopri_len
2486         for c in self.blit:
2487             eq(bql(c), 1)
2488         for c in self.bnon:
2489             eq(bql(c), 3)
2490 
2491     def test_quote_unquote_idempotent(self):
2492         for x in range(256):
2493             c = chr(x)
2494             self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
2495 
2496     def test_header_encode(self):
2497         eq = self.assertEqual
2498         he = quopriMIME.header_encode
2499         eq(he('hello'), '=?iso-8859-1?q?hello?=')
2500         eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
2501         # Test the charset option
2502         eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
2503         # Test the keep_eols flag
2504         eq(he('hello\nworld', keep_eols=True), '=?iso-8859-1?q?hello=0Aworld?=')
2505         # Test a non-ASCII character
2506         eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
2507         # Test the maxlinelen argument
2508         eq(he('xxxx ' * 20, maxlinelen=40), """\
2509 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
2510  =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
2511  =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
2512  =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
2513  =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2514         # Test the eol argument
2515         eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2516 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
2517  =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
2518  =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
2519  =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
2520  =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2521 
2522     def test_decode(self):
2523         eq = self.assertEqual
2524         eq(quopriMIME.decode(''), '')
2525         eq(quopriMIME.decode('hello'), 'hello')
2526         eq(quopriMIME.decode('hello', 'X'), 'hello')
2527         eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
2528 
2529     def test_encode(self):
2530         eq = self.assertEqual
2531         eq(quopriMIME.encode(''), '')
2532         eq(quopriMIME.encode('hello'), 'hello')
2533         # Test the binary flag
2534         eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
2535         eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
2536         # Test the maxlinelen arg
2537         eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
2538 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
2539  xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
2540 x xxxx xxxx xxxx xxxx=20""")
2541         # Test the eol argument
2542         eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2543 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
2544  xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
2545 x xxxx xxxx xxxx xxxx=20""")
2546         eq(quopriMIME.encode("""\
2547 one line
2548 
2549 two line"""), """\
2550 one line
2551 
2552 two line""")
2553 
2554 
2555 
2556 # Test the Charset class
2557 class TestCharset(unittest.TestCase):
2558     def tearDown(self):
2559         from email import Charset as CharsetModule
2560         try:
2561             del CharsetModule.CHARSETS['fake']
2562         except KeyError:
2563             pass
2564 
2565     def test_idempotent(self):
2566         eq = self.assertEqual
2567         # Make sure us-ascii = no Unicode conversion
2568         c = Charset('us-ascii')
2569         s = 'Hello World!'
2570         sp = c.to_splittable(s)
2571         eq(s, c.from_splittable(sp))
2572         # test 8-bit idempotency with us-ascii
2573         s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
2574         sp = c.to_splittable(s)
2575         eq(s, c.from_splittable(sp))
2576 
2577     def test_body_encode(self):
2578         eq = self.assertEqual
2579         # Try a charset with QP body encoding
2580         c = Charset('iso-8859-1')
2581         eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
2582         # Try a charset with Base64 body encoding
2583         c = Charset('utf-8')
2584         eq('aGVsbG8gd29ybGQ=\n', c.body_encode('hello world'))
2585         # Try a charset with None body encoding
2586         c = Charset('us-ascii')
2587         eq('hello world', c.body_encode('hello world'))
2588         # Try the convert argument, where input codec <> output codec
2589         c = Charset('euc-jp')
2590         # With apologies to Tokio Kikuchi ;)
2591         try:
2592             eq('\x1b$B5FCO;~IW\x1b(B',
2593                c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
2594             eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
2595                c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
2596         except LookupError:
2597             # We probably don't have the Japanese codecs installed
2598             pass
2599         # Testing SF bug #625509, which we have to fake, since there are no
2600         # built-in encodings where the header encoding is QP but the body
2601         # encoding is not.
2602         from email import Charset as CharsetModule
2603         CharsetModule.add_charset('fake', CharsetModule.QP, None)
2604         c = Charset('fake')
2605         eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
2606 
2607 
2608 
2609 # Test multilingual MIME headers.
2610 class TestHeader(TestEmailBase):
2611     def test_simple(self):
2612         eq = self.ndiffAssertEqual
2613         h = Header('Hello World!')
2614         eq(h.encode(), 'Hello World!')
2615         h.append(' Goodbye World!')
2616         eq(h.encode(), 'Hello World!  Goodbye World!')
2617 
2618     def test_simple_surprise(self):
2619         eq = self.ndiffAssertEqual
2620         h = Header('Hello World!')
2621         eq(h.encode(), 'Hello World!')
2622         h.append('Goodbye World!')
2623         eq(h.encode(), 'Hello World! Goodbye World!')
2624 
2625     def test_header_needs_no_decoding(self):
2626         h = 'no decoding needed'
2627         self.assertEqual(decode_header(h), [(h, None)])
2628 
2629     def test_long(self):
2630         h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
2631                    maxlinelen=76)
2632         for l in h.encode(splitchars=' ').split('\n '):
2633             self.failUnless(len(l) <= 76)
2634 
2635     def test_multilingual(self):
2636         eq = self.ndiffAssertEqual
2637         g = Charset("iso-8859-1")
2638         cz = Charset("iso-8859-2")
2639         utf8 = Charset("utf-8")
2640         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. "
2641         cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
2642         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")
2643         h = Header(g_head, g)
2644         h.append(cz_head, cz)
2645         h.append(utf8_head, utf8)
2646         enc = h.encode()
2647         eq(enc, """\
2648 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_ko?=
2649  =?iso-8859-1?q?mfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wan?=
2650  =?iso-8859-1?q?dgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6?=
2651  =?iso-8859-1?q?rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
2652  =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
2653  =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
2654  =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
2655  =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
2656  =?utf-8?q?_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das_Oder_die_Fl?=
2657  =?utf-8?b?aXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBo+OBpuOBhOOBvuOBmQ==?=
2658  =?utf-8?b?44CC?=""")
2659         eq(decode_header(enc),
2660            [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
2661             (utf8_head, "utf-8")])
2662         ustr = unicode(h)
2663         eq(ustr.encode('utf-8'),
2664            'Die Mieter treten hier ein werden mit einem Foerderband '
2665            'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
2666            'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
2667            'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
2668            'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
2669            '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
2670            '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
2671            '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
2672            '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
2673            '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
2674            '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
2675            '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
2676            '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
2677            'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
2678            'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
2679            '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
2680         # Test make_header()
2681         newh = make_header(decode_header(enc))
2682         eq(newh, enc)
2683 
2684     def test_header_ctor_default_args(self):
2685         eq = self.ndiffAssertEqual
2686         h = Header()
2687         eq(h, '')
2688         h.append('foo', Charset('iso-8859-1'))
2689         eq(h, '=?iso-8859-1?q?foo?=')
2690 
2691     def test_explicit_maxlinelen(self):
2692         eq = self.ndiffAssertEqual
2693         hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
2694         h = Header(hstr)
2695         eq(h.encode(), '''\
2696 A very long line that must get split to something other than at the 76th
2697  character boundary to test the non-default behavior''')
2698         h = Header(hstr, header_name='Subject')
2699         eq(h.encode(), '''\
2700 A very long line that must get split to something other than at the
2701  76th character boundary to test the non-default behavior''')
2702         h = Header(hstr, maxlinelen=1024, header_name='Subject')
2703         eq(h.encode(), hstr)
2704 
2705     def test_us_ascii_header(self):
2706         eq = self.assertEqual
2707         s = 'hello'
2708         x = decode_header(s)
2709         eq(x, [('hello', None)])
2710         h = make_header(x)
2711         eq(s, h.encode())
2712 
2713     def test_string_charset(self):
2714         eq = self.assertEqual
2715         h = Header()
2716         h.append('hello', 'iso-8859-1')
2717         eq(h, '=?iso-8859-1?q?hello?=')
2718 
2719 ##    def test_unicode_error(self):
2720 ##        raises = self.assertRaises
2721 ##        raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
2722 ##        raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
2723 ##        h = Header()
2724 ##        raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
2725 ##        raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
2726 ##        raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
2727 
2728     def test_utf8_shortest(self):
2729         eq = self.assertEqual
2730         h = Header(u'p\xf6stal', 'utf-8')
2731         eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
2732         h = Header(u'\u83ca\u5730\u6642\u592b', 'utf-8')
2733         eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
2734 
2735     def test_bad_8bit_header(self):
2736         raises = self.assertRaises
2737         eq = self.assertEqual
2738         x = 'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
2739         raises(UnicodeError, Header, x)
2740         h = Header()
2741         raises(UnicodeError, h.append, x)
2742         eq(str(Header(x, errors='replace')), x)
2743         h.append(x, errors='replace')
2744         eq(str(h), x)
2745 
2746     def test_encoded_adjacent_nonencoded(self):
2747         eq = self.assertEqual
2748         h = Header()
2749         h.append('hello', 'iso-8859-1')
2750         h.append('world')
2751         s = h.encode()
2752         eq(s, '=?iso-8859-1?q?hello?= world')
2753         h = make_header(decode_header(s))
2754         eq(h.encode(), s)
2755 
2756     def test_whitespace_eater(self):
2757         eq = self.assertEqual
2758         s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
2759         parts = decode_header(s)
2760         eq(parts, [('Subject:', None), ('\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), ('zz.', None)])
2761         hdr = make_header(parts)
2762         eq(hdr.encode(),
2763            'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
2764 
2765     def test_broken_base64_header(self):
2766         raises = self.assertRaises
2767         s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ?='
2768         raises(Errors.HeaderParseError, decode_header, s)
2769 
2770 
2771 
2772 # Test RFC 2231 header parameters (en/de)coding
2773 class TestRFC2231(TestEmailBase):
2774     def test_get_param(self):
2775         eq = self.assertEqual
2776         msg = self._msgobj('msg_29.txt')
2777         eq(msg.get_param('title'),
2778            ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2779         eq(msg.get_param('title', unquote=False),
2780            ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
2781 
2782     def test_set_param(self):
2783         eq = self.assertEqual
2784         msg = Message()
2785         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2786                       charset='us-ascii')
2787         eq(msg.get_param('title'),
2788            ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
2789         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2790                       charset='us-ascii', language='en')
2791         eq(msg.get_param('title'),
2792            ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2793         msg = self._msgobj('msg_01.txt')
2794         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2795                       charset='us-ascii', language='en')
2796         eq(msg.as_string(), """\
2797 Return-Path: <bbb@zzz.org>
2798 Delivered-To: bbb@zzz.org
2799 Received: by mail.zzz.org (Postfix, from userid 889)
2800 \tid 27CEAD38CC; Fri,  4 May 2001 14:05:44 -0400 (EDT)
2801 MIME-Version: 1.0
2802 Content-Transfer-Encoding: 7bit
2803 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2804 From: bbb@ddd.com (John X. Doe)
2805 To: bbb@zzz.org
2806 Subject: This is a test message
2807 Date: Fri, 4 May 2001 14:05:44 -0400
2808 Content-Type: text/plain; charset=us-ascii;
2809 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2810 
2811 
2812 Hi,
2813 
2814 Do you like this message?
2815 
2816 -Me
2817 """)
2818 
2819     def test_del_param(self):
2820         eq = self.ndiffAssertEqual
2821         msg = self._msgobj('msg_01.txt')
2822         msg.set_param('foo', 'bar', charset='us-ascii', language='en')
2823         msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2824             charset='us-ascii', language='en')
2825         msg.del_param('foo', header='Content-Type')
2826         eq(msg.as_string(), """\
2827 Return-Path: <bbb@zzz.org>
2828 Delivered-To: bbb@zzz.org
2829 Received: by mail.zzz.org (Postfix, from userid 889)
2830 \tid 27CEAD38CC; Fri,  4 May 2001 14:05:44 -0400 (EDT)
2831 MIME-Version: 1.0
2832 Content-Transfer-Encoding: 7bit
2833 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2834 From: bbb@ddd.com (John X. Doe)
2835 To: bbb@zzz.org
2836 Subject: This is a test message
2837 Date: Fri, 4 May 2001 14:05:44 -0400
2838 Content-Type: text/plain; charset="us-ascii";
2839 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2840 
2841 
2842 Hi,
2843 
2844 Do you like this message?
2845 
2846 -Me
2847 """)
2848 
2849     def test_rfc2231_get_content_charset(self):
2850         eq = self.assertEqual
2851         msg = self._msgobj('msg_32.txt')
2852         eq(msg.get_content_charset(), 'us-ascii')
2853 
2854     def test_rfc2231_no_language_or_charset(self):
2855         m = '''\
2856 Content-Transfer-Encoding: 8bit
2857 Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
2858 Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
2859 
2860 '''
2861         msg = email.message_from_string(m)
2862         self.assertEqual(msg.get_param('NAME'),
2863                          (None, None, 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm'))
2864 
2865     def test_rfc2231_no_language_or_charset_in_filename(self):
2866         m = '''\
2867 Content-Disposition: inline;
2868 \tfilename*0="This%20is%20even%20more%20";
2869 \tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
2870 \tfilename*2="is it not.pdf"
2871 
2872 '''
2873         msg = email.message_from_string(m)
2874         self.assertEqual(msg.get_filename(),
2875                          'This is even more ***fun*** is it not.pdf')
2876 
2877     def test_rfc2231_no_language_or_charset_in_boundary(self):
2878         m = '''\
2879 Content-Type: multipart/alternative;
2880 \tboundary*0="This%20is%20even%20more%20";
2881 \tboundary*1="%2A%2A%2Afun%2A%2A%2A%20";
2882 \tboundary*2="is it not.pdf"
2883 
2884 '''
2885         msg = email.message_from_string(m)
2886         self.assertEqual(msg.get_boundary(),
2887                          'This is even more ***fun*** is it not.pdf')
2888 
2889     def test_rfc2231_no_language_or_charset_in_charset(self):
2890         # This is a nonsensical charset value, but tests the code anyway
2891         m = '''\
2892 Content-Type: text/plain;
2893 \tcharset*0="This%20is%20even%20more%20";
2894 \tcharset*1="%2A%2A%2Afun%2A%2A%2A%20";
2895 \tcharset*2="is it not.pdf"
2896 
2897 '''
2898         msg = email.message_from_string(m)
2899         self.assertEqual(msg.get_content_charset(),
2900                          'this is even more ***fun*** is it not.pdf')
2901 
2902     def test_rfc2231_unknown_encoding(self):
2903         m = """\
2904 Content-Transfer-Encoding: 8bit
2905 Content-Disposition: inline; filename*0=X-UNKNOWN''myfile.txt
2906 
2907 """
2908         msg = email.message_from_string(m)
2909         self.assertEqual(msg.get_filename(), 'myfile.txt')
2910 
2911 
2912 
2913 def _testclasses():
2914     mod = sys.modules[__name__]
2915     return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
2916 
2917 
2918 def suite():
2919     suite = unittest.TestSuite()
2920     for testclass in _testclasses():
2921         suite.addTest(unittest.makeSuite(testclass))
2922     return suite
2923 
2924 
2925 def test_main():
2926     for testclass in _testclasses():
2927         run_unittest(testclass)
2928 
2929 
2930 
2931 if __name__ == '__main__':
2932     unittest.main(defaultTest='suite')
2933 

Generated by PyXR 0.9.4
SourceForge.net Logo