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