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