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&&Jill"\n') 0308 self.assertEqual(msg.get_param('name'), 'Jim&&Jill') 0309 self.assertEqual(msg.get_param('name', unquote=False), 0310 '"Jim&&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