blob: 5b8b7bfbdb80d2ca6643cfb717036423cda57e7d [file] [log] [blame]
Benjamin Petersonffeda292010-01-09 18:48:46 +00001# Copyright (C) 2001-2010 Python Software Foundation
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002# Contact: email-sig@python.org
3# email package unit tests
4
5import os
6import sys
7import time
8import base64
9import difflib
10import unittest
11import warnings
R. David Murray43b2f452011-02-11 03:13:19 +000012import textwrap
Guido van Rossum8b3febe2007-08-30 01:15:14 +000013
14from io import StringIO
15from itertools import chain
16
17import email
18
19from email.charset import Charset
20from email.header import Header, decode_header, make_header
21from email.parser import Parser, HeaderParser
22from email.generator import Generator, DecodedGenerator
23from email.message import Message
24from email.mime.application import MIMEApplication
25from email.mime.audio import MIMEAudio
26from email.mime.text import MIMEText
27from email.mime.image import MIMEImage
28from email.mime.base import MIMEBase
29from email.mime.message import MIMEMessage
30from email.mime.multipart import MIMEMultipart
31from email import utils
32from email import errors
33from email import encoders
34from email import iterators
35from email import base64mime
36from email import quoprimime
37
Benjamin Petersonee8712c2008-05-20 21:35:26 +000038from test.support import findfile, run_unittest
Guido van Rossum8b3febe2007-08-30 01:15:14 +000039from email.test import __file__ as landmark
40
41
42NL = '\n'
43EMPTYSTRING = ''
44SPACE = ' '
45
46
Ezio Melotti19f2aeb2010-11-21 01:30:29 +000047
Guido van Rossum8b3febe2007-08-30 01:15:14 +000048def openfile(filename, *args, **kws):
49 path = os.path.join(os.path.dirname(landmark), 'data', filename)
50 return open(path, *args, **kws)
51
52
Ezio Melotti19f2aeb2010-11-21 01:30:29 +000053
Guido van Rossum8b3febe2007-08-30 01:15:14 +000054# Base test class
55class TestEmailBase(unittest.TestCase):
56 def ndiffAssertEqual(self, first, second):
Georg Brandlab91fde2009-08-13 08:51:18 +000057 """Like assertEqual except use ndiff for readable output."""
Guido van Rossum8b3febe2007-08-30 01:15:14 +000058 if first != second:
59 sfirst = str(first)
60 ssecond = str(second)
61 rfirst = [repr(line) for line in sfirst.splitlines()]
62 rsecond = [repr(line) for line in ssecond.splitlines()]
63 diff = difflib.ndiff(rfirst, rsecond)
64 raise self.failureException(NL + NL.join(diff))
65
66 def _msgobj(self, filename):
67 with openfile(findfile(filename)) as fp:
68 return email.message_from_file(fp)
69
70
Ezio Melotti19f2aeb2010-11-21 01:30:29 +000071
Guido van Rossum8b3febe2007-08-30 01:15:14 +000072# Test various aspects of the Message class's API
73class TestMessageAPI(TestEmailBase):
74 def test_get_all(self):
75 eq = self.assertEqual
76 msg = self._msgobj('msg_20.txt')
77 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
78 eq(msg.get_all('xx', 'n/a'), 'n/a')
79
80 def test_getset_charset(self):
81 eq = self.assertEqual
82 msg = Message()
83 eq(msg.get_charset(), None)
84 charset = Charset('iso-8859-1')
85 msg.set_charset(charset)
86 eq(msg['mime-version'], '1.0')
87 eq(msg.get_content_type(), 'text/plain')
88 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
89 eq(msg.get_param('charset'), 'iso-8859-1')
90 eq(msg['content-transfer-encoding'], 'quoted-printable')
91 eq(msg.get_charset().input_charset, 'iso-8859-1')
92 # Remove the charset
93 msg.set_charset(None)
94 eq(msg.get_charset(), None)
95 eq(msg['content-type'], 'text/plain')
96 # Try adding a charset when there's already MIME headers present
97 msg = Message()
98 msg['MIME-Version'] = '2.0'
99 msg['Content-Type'] = 'text/x-weird'
100 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
101 msg.set_charset(charset)
102 eq(msg['mime-version'], '2.0')
103 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
104 eq(msg['content-transfer-encoding'], 'quinted-puntable')
105
106 def test_set_charset_from_string(self):
107 eq = self.assertEqual
108 msg = Message()
109 msg.set_charset('us-ascii')
110 eq(msg.get_charset().input_charset, 'us-ascii')
111 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
112
113 def test_set_payload_with_charset(self):
114 msg = Message()
115 charset = Charset('iso-8859-1')
116 msg.set_payload('This is a string payload', charset)
117 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
118
119 def test_get_charsets(self):
120 eq = self.assertEqual
121
122 msg = self._msgobj('msg_08.txt')
123 charsets = msg.get_charsets()
124 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
125
126 msg = self._msgobj('msg_09.txt')
127 charsets = msg.get_charsets('dingbat')
128 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
129 'koi8-r'])
130
131 msg = self._msgobj('msg_12.txt')
132 charsets = msg.get_charsets()
133 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
134 'iso-8859-3', 'us-ascii', 'koi8-r'])
135
136 def test_get_filename(self):
137 eq = self.assertEqual
138
139 msg = self._msgobj('msg_04.txt')
140 filenames = [p.get_filename() for p in msg.get_payload()]
141 eq(filenames, ['msg.txt', 'msg.txt'])
142
143 msg = self._msgobj('msg_07.txt')
144 subpart = msg.get_payload(1)
145 eq(subpart.get_filename(), 'dingusfish.gif')
146
147 def test_get_filename_with_name_parameter(self):
148 eq = self.assertEqual
149
150 msg = self._msgobj('msg_44.txt')
151 filenames = [p.get_filename() for p in msg.get_payload()]
152 eq(filenames, ['msg.txt', 'msg.txt'])
153
154 def test_get_boundary(self):
155 eq = self.assertEqual
156 msg = self._msgobj('msg_07.txt')
157 # No quotes!
158 eq(msg.get_boundary(), 'BOUNDARY')
159
160 def test_set_boundary(self):
161 eq = self.assertEqual
162 # This one has no existing boundary parameter, but the Content-Type:
163 # header appears fifth.
164 msg = self._msgobj('msg_01.txt')
165 msg.set_boundary('BOUNDARY')
166 header, value = msg.items()[4]
167 eq(header.lower(), 'content-type')
168 eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
169 # This one has a Content-Type: header, with a boundary, stuck in the
170 # middle of its headers. Make sure the order is preserved; it should
171 # be fifth.
172 msg = self._msgobj('msg_04.txt')
173 msg.set_boundary('BOUNDARY')
174 header, value = msg.items()[4]
175 eq(header.lower(), 'content-type')
176 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
177 # And this one has no Content-Type: header at all.
178 msg = self._msgobj('msg_03.txt')
179 self.assertRaises(errors.HeaderParseError,
180 msg.set_boundary, 'BOUNDARY')
181
R. David Murray27c19142010-12-21 18:11:01 +0000182 def test_make_boundary(self):
183 msg = MIMEMultipart('form-data')
184 # Note that when the boundary gets created is an implementation
185 # detail and might change.
186 self.assertEqual(msg.items()[0][1], 'multipart/form-data')
187 # Trigger creation of boundary
188 msg.as_string()
189 self.assertEqual(msg.items()[0][1][:33],
190 'multipart/form-data; boundary="==')
191 # XXX: there ought to be tests of the uniqueness of the boundary, too.
192
R. David Murrayd0a04ff2010-02-21 04:48:18 +0000193 def test_message_rfc822_only(self):
194 # Issue 7970: message/rfc822 not in multipart parsed by
195 # HeaderParser caused an exception when flattened.
196 fp = openfile(findfile('msg_46.txt'))
197 msgdata = fp.read()
198 parser = HeaderParser()
199 msg = parser.parsestr(msgdata)
200 out = StringIO()
201 gen = Generator(out, True, 0)
202 gen.flatten(msg, False)
203 self.assertEqual(out.getvalue(), msgdata)
204
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000205 def test_get_decoded_payload(self):
206 eq = self.assertEqual
207 msg = self._msgobj('msg_10.txt')
208 # The outer message is a multipart
209 eq(msg.get_payload(decode=True), None)
210 # Subpart 1 is 7bit encoded
211 eq(msg.get_payload(0).get_payload(decode=True),
212 b'This is a 7bit encoded message.\n')
213 # Subpart 2 is quopri
214 eq(msg.get_payload(1).get_payload(decode=True),
215 b'\xa1This is a Quoted Printable encoded message!\n')
216 # Subpart 3 is base64
217 eq(msg.get_payload(2).get_payload(decode=True),
218 b'This is a Base64 encoded message.')
219 # Subpart 4 has no Content-Transfer-Encoding: header.
220 eq(msg.get_payload(3).get_payload(decode=True),
221 b'This has no Content-Transfer-Encoding: header.\n')
222
223 def test_get_decoded_uu_payload(self):
224 eq = self.assertEqual
225 msg = Message()
226 msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
227 for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
228 msg['content-transfer-encoding'] = cte
229 eq(msg.get_payload(decode=True), b'hello world')
230 # Now try some bogus data
231 msg.set_payload('foo')
232 eq(msg.get_payload(decode=True), b'foo')
233
234 def test_decoded_generator(self):
235 eq = self.assertEqual
236 msg = self._msgobj('msg_07.txt')
237 with openfile('msg_17.txt') as fp:
238 text = fp.read()
239 s = StringIO()
240 g = DecodedGenerator(s)
241 g.flatten(msg)
242 eq(s.getvalue(), text)
243
244 def test__contains__(self):
245 msg = Message()
246 msg['From'] = 'Me'
247 msg['to'] = 'You'
248 # Check for case insensitivity
Georg Brandlab91fde2009-08-13 08:51:18 +0000249 self.assertTrue('from' in msg)
250 self.assertTrue('From' in msg)
251 self.assertTrue('FROM' in msg)
252 self.assertTrue('to' in msg)
253 self.assertTrue('To' in msg)
254 self.assertTrue('TO' in msg)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000255
256 def test_as_string(self):
257 eq = self.ndiffAssertEqual
258 msg = self._msgobj('msg_01.txt')
259 with openfile('msg_01.txt') as fp:
260 text = fp.read()
261 eq(text, str(msg))
262 fullrepr = msg.as_string(unixfrom=True)
263 lines = fullrepr.split('\n')
Georg Brandlab91fde2009-08-13 08:51:18 +0000264 self.assertTrue(lines[0].startswith('From '))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000265 eq(text, NL.join(lines[1:]))
266
267 def test_bad_param(self):
268 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
269 self.assertEqual(msg.get_param('baz'), '')
270
271 def test_missing_filename(self):
272 msg = email.message_from_string("From: foo\n")
273 self.assertEqual(msg.get_filename(), None)
274
275 def test_bogus_filename(self):
276 msg = email.message_from_string(
277 "Content-Disposition: blarg; filename\n")
278 self.assertEqual(msg.get_filename(), '')
279
280 def test_missing_boundary(self):
281 msg = email.message_from_string("From: foo\n")
282 self.assertEqual(msg.get_boundary(), None)
283
284 def test_get_params(self):
285 eq = self.assertEqual
286 msg = email.message_from_string(
287 'X-Header: foo=one; bar=two; baz=three\n')
288 eq(msg.get_params(header='x-header'),
289 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
290 msg = email.message_from_string(
291 'X-Header: foo; bar=one; baz=two\n')
292 eq(msg.get_params(header='x-header'),
293 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
294 eq(msg.get_params(), None)
295 msg = email.message_from_string(
296 'X-Header: foo; bar="one"; baz=two\n')
297 eq(msg.get_params(header='x-header'),
298 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
299
300 def test_get_param_liberal(self):
301 msg = Message()
302 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
303 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
304
305 def test_get_param(self):
306 eq = self.assertEqual
307 msg = email.message_from_string(
308 "X-Header: foo=one; bar=two; baz=three\n")
309 eq(msg.get_param('bar', header='x-header'), 'two')
310 eq(msg.get_param('quuz', header='x-header'), None)
311 eq(msg.get_param('quuz'), None)
312 msg = email.message_from_string(
313 'X-Header: foo; bar="one"; baz=two\n')
314 eq(msg.get_param('foo', header='x-header'), '')
315 eq(msg.get_param('bar', header='x-header'), 'one')
316 eq(msg.get_param('baz', header='x-header'), 'two')
317 # XXX: We are not RFC-2045 compliant! We cannot parse:
318 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
319 # msg.get_param("weird")
320 # yet.
321
322 def test_get_param_funky_continuation_lines(self):
323 msg = self._msgobj('msg_22.txt')
324 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
325
326 def test_get_param_with_semis_in_quotes(self):
327 msg = email.message_from_string(
328 'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
329 self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
330 self.assertEqual(msg.get_param('name', unquote=False),
331 '"Jim&amp;&amp;Jill"')
332
R. David Murray84ee3102010-04-14 19:05:38 +0000333 def test_get_param_with_quotes(self):
334 msg = email.message_from_string(
335 'Content-Type: foo; bar*0="baz\\"foobar"; bar*1="\\"baz"')
336 self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
337 msg = email.message_from_string(
338 "Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"")
339 self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
340
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000341 def test_field_containment(self):
Georg Brandlab91fde2009-08-13 08:51:18 +0000342 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000343 msg = email.message_from_string('Header: exists')
344 unless('header' in msg)
345 unless('Header' in msg)
346 unless('HEADER' in msg)
Georg Brandlab91fde2009-08-13 08:51:18 +0000347 self.assertFalse('headerx' in msg)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000348
349 def test_set_param(self):
350 eq = self.assertEqual
351 msg = Message()
352 msg.set_param('charset', 'iso-2022-jp')
353 eq(msg.get_param('charset'), 'iso-2022-jp')
354 msg.set_param('importance', 'high value')
355 eq(msg.get_param('importance'), 'high value')
356 eq(msg.get_param('importance', unquote=False), '"high value"')
357 eq(msg.get_params(), [('text/plain', ''),
358 ('charset', 'iso-2022-jp'),
359 ('importance', 'high value')])
360 eq(msg.get_params(unquote=False), [('text/plain', ''),
361 ('charset', '"iso-2022-jp"'),
362 ('importance', '"high value"')])
363 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
364 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
365
366 def test_del_param(self):
367 eq = self.assertEqual
368 msg = self._msgobj('msg_05.txt')
369 eq(msg.get_params(),
370 [('multipart/report', ''), ('report-type', 'delivery-status'),
371 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
372 old_val = msg.get_param("report-type")
373 msg.del_param("report-type")
374 eq(msg.get_params(),
375 [('multipart/report', ''),
376 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
377 msg.set_param("report-type", old_val)
378 eq(msg.get_params(),
379 [('multipart/report', ''),
380 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
381 ('report-type', old_val)])
382
383 def test_del_param_on_other_header(self):
384 msg = Message()
385 msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
386 msg.del_param('filename', 'content-disposition')
387 self.assertEqual(msg['content-disposition'], 'attachment')
388
389 def test_set_type(self):
390 eq = self.assertEqual
391 msg = Message()
392 self.assertRaises(ValueError, msg.set_type, 'text')
393 msg.set_type('text/plain')
394 eq(msg['content-type'], 'text/plain')
395 msg.set_param('charset', 'us-ascii')
396 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
397 msg.set_type('text/html')
398 eq(msg['content-type'], 'text/html; charset="us-ascii"')
399
400 def test_set_type_on_other_header(self):
401 msg = Message()
402 msg['X-Content-Type'] = 'text/plain'
403 msg.set_type('application/octet-stream', 'X-Content-Type')
404 self.assertEqual(msg['x-content-type'], 'application/octet-stream')
405
406 def test_get_content_type_missing(self):
407 msg = Message()
408 self.assertEqual(msg.get_content_type(), 'text/plain')
409
410 def test_get_content_type_missing_with_default_type(self):
411 msg = Message()
412 msg.set_default_type('message/rfc822')
413 self.assertEqual(msg.get_content_type(), 'message/rfc822')
414
415 def test_get_content_type_from_message_implicit(self):
416 msg = self._msgobj('msg_30.txt')
417 self.assertEqual(msg.get_payload(0).get_content_type(),
418 'message/rfc822')
419
420 def test_get_content_type_from_message_explicit(self):
421 msg = self._msgobj('msg_28.txt')
422 self.assertEqual(msg.get_payload(0).get_content_type(),
423 'message/rfc822')
424
425 def test_get_content_type_from_message_text_plain_implicit(self):
426 msg = self._msgobj('msg_03.txt')
427 self.assertEqual(msg.get_content_type(), 'text/plain')
428
429 def test_get_content_type_from_message_text_plain_explicit(self):
430 msg = self._msgobj('msg_01.txt')
431 self.assertEqual(msg.get_content_type(), 'text/plain')
432
433 def test_get_content_maintype_missing(self):
434 msg = Message()
435 self.assertEqual(msg.get_content_maintype(), 'text')
436
437 def test_get_content_maintype_missing_with_default_type(self):
438 msg = Message()
439 msg.set_default_type('message/rfc822')
440 self.assertEqual(msg.get_content_maintype(), 'message')
441
442 def test_get_content_maintype_from_message_implicit(self):
443 msg = self._msgobj('msg_30.txt')
444 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
445
446 def test_get_content_maintype_from_message_explicit(self):
447 msg = self._msgobj('msg_28.txt')
448 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
449
450 def test_get_content_maintype_from_message_text_plain_implicit(self):
451 msg = self._msgobj('msg_03.txt')
452 self.assertEqual(msg.get_content_maintype(), 'text')
453
454 def test_get_content_maintype_from_message_text_plain_explicit(self):
455 msg = self._msgobj('msg_01.txt')
456 self.assertEqual(msg.get_content_maintype(), 'text')
457
458 def test_get_content_subtype_missing(self):
459 msg = Message()
460 self.assertEqual(msg.get_content_subtype(), 'plain')
461
462 def test_get_content_subtype_missing_with_default_type(self):
463 msg = Message()
464 msg.set_default_type('message/rfc822')
465 self.assertEqual(msg.get_content_subtype(), 'rfc822')
466
467 def test_get_content_subtype_from_message_implicit(self):
468 msg = self._msgobj('msg_30.txt')
469 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
470
471 def test_get_content_subtype_from_message_explicit(self):
472 msg = self._msgobj('msg_28.txt')
473 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
474
475 def test_get_content_subtype_from_message_text_plain_implicit(self):
476 msg = self._msgobj('msg_03.txt')
477 self.assertEqual(msg.get_content_subtype(), 'plain')
478
479 def test_get_content_subtype_from_message_text_plain_explicit(self):
480 msg = self._msgobj('msg_01.txt')
481 self.assertEqual(msg.get_content_subtype(), 'plain')
482
483 def test_get_content_maintype_error(self):
484 msg = Message()
485 msg['Content-Type'] = 'no-slash-in-this-string'
486 self.assertEqual(msg.get_content_maintype(), 'text')
487
488 def test_get_content_subtype_error(self):
489 msg = Message()
490 msg['Content-Type'] = 'no-slash-in-this-string'
491 self.assertEqual(msg.get_content_subtype(), 'plain')
492
493 def test_replace_header(self):
494 eq = self.assertEqual
495 msg = Message()
496 msg.add_header('First', 'One')
497 msg.add_header('Second', 'Two')
498 msg.add_header('Third', 'Three')
499 eq(msg.keys(), ['First', 'Second', 'Third'])
500 eq(msg.values(), ['One', 'Two', 'Three'])
501 msg.replace_header('Second', 'Twenty')
502 eq(msg.keys(), ['First', 'Second', 'Third'])
503 eq(msg.values(), ['One', 'Twenty', 'Three'])
504 msg.add_header('First', 'Eleven')
505 msg.replace_header('First', 'One Hundred')
506 eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
507 eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
508 self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
509
510 def test_broken_base64_payload(self):
511 x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
512 msg = Message()
513 msg['content-type'] = 'audio/x-midi'
514 msg['content-transfer-encoding'] = 'base64'
515 msg.set_payload(x)
516 self.assertEqual(msg.get_payload(decode=True),
Guido van Rossum9604e662007-08-30 03:46:43 +0000517 bytes(x, 'raw-unicode-escape'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000518
R. David Murrayccb9d052010-12-13 23:57:01 +0000519 # Issue 1078919
520 def test_ascii_add_header(self):
521 msg = Message()
522 msg.add_header('Content-Disposition', 'attachment',
523 filename='bud.gif')
524 self.assertEqual('attachment; filename="bud.gif"',
525 msg['Content-Disposition'])
526
527 def test_noascii_add_header(self):
528 msg = Message()
529 msg.add_header('Content-Disposition', 'attachment',
530 filename="Fußballer.ppt")
531 self.assertEqual(
532 'attachment; filename*="utf-8\'\'Fu%C3%9Fballer.ppt"',
533 msg['Content-Disposition'])
534
535 def test_nonascii_add_header_via_triple(self):
536 msg = Message()
537 msg.add_header('Content-Disposition', 'attachment',
538 filename=('iso-8859-1', '', 'Fußballer.ppt'))
539 self.assertEqual(
540 'attachment; filename*="iso-8859-1\'\'Fu%DFballer.ppt"',
541 msg['Content-Disposition'])
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000542
Ezio Melotti19f2aeb2010-11-21 01:30:29 +0000543
R. David Murray389af002011-01-09 02:48:04 +0000544 # Issue 5871: reject an attempt to embed a header inside a header value
545 # (header injection attack).
546 def test_embeded_header_via_Header_rejected(self):
547 msg = Message()
548 msg['Dummy'] = Header('dummy\nX-Injected-Header: test')
549 self.assertRaises(errors.HeaderParseError, msg.as_string)
550
551 def test_embeded_header_via_string_rejected(self):
552 msg = Message()
553 msg['Dummy'] = 'dummy\nX-Injected-Header: test'
554 self.assertRaises(errors.HeaderParseError, msg.as_string)
555
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000556# Test the email.encoders module
557class TestEncoders(unittest.TestCase):
R David Murray6d94bd42011-03-16 15:52:22 -0400558
559 def test_EncodersEncode_base64(self):
560 with openfile('PyBanner048.gif', 'rb') as fp:
561 bindata = fp.read()
562 mimed = email.mime.image.MIMEImage(bindata)
563 base64ed = mimed.get_payload()
564 # the transfer-encoded body lines should all be <=76 characters
565 lines = base64ed.split('\n')
566 self.assertLessEqual(max([ len(x) for x in lines ]), 76)
567
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000568 def test_encode_empty_payload(self):
569 eq = self.assertEqual
570 msg = Message()
571 msg.set_charset('us-ascii')
572 eq(msg['content-transfer-encoding'], '7bit')
573
574 def test_default_cte(self):
575 eq = self.assertEqual
Ezio Melottic30bb7d2010-04-22 11:58:06 +0000576 # 7bit data and the default us-ascii _charset
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000577 msg = MIMEText('hello world')
578 eq(msg['content-transfer-encoding'], '7bit')
Ezio Melottic30bb7d2010-04-22 11:58:06 +0000579 # Similar, but with 8bit data
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000580 msg = MIMEText('hello \xf8 world')
581 eq(msg['content-transfer-encoding'], '8bit')
582 # And now with a different charset
583 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
584 eq(msg['content-transfer-encoding'], 'quoted-printable')
585
R. David Murrayf870d872010-05-06 01:53:03 +0000586 def test_encode7or8bit(self):
587 # Make sure a charset whose input character set is 8bit but
588 # whose output character set is 7bit gets a transfer-encoding
589 # of 7bit.
590 eq = self.assertEqual
R. David Murrayd2d08c62010-06-03 02:05:47 +0000591 msg = MIMEText('æ–‡', _charset='euc-jp')
R. David Murrayf870d872010-05-06 01:53:03 +0000592 eq(msg['content-transfer-encoding'], '7bit')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000593
Ezio Melotti19f2aeb2010-11-21 01:30:29 +0000594
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000595# Test long header wrapping
596class TestLongHeaders(TestEmailBase):
597 def test_split_long_continuation(self):
598 eq = self.ndiffAssertEqual
599 msg = email.message_from_string("""\
600Subject: bug demonstration
601\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
602\tmore text
603
604test
605""")
606 sfp = StringIO()
607 g = Generator(sfp)
608 g.flatten(msg)
609 eq(sfp.getvalue(), """\
610Subject: bug demonstration
611\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
612\tmore text
613
614test
615""")
616
617 def test_another_long_almost_unsplittable_header(self):
618 eq = self.ndiffAssertEqual
619 hstr = """\
620bug demonstration
621\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
622\tmore text"""
623 h = Header(hstr, continuation_ws='\t')
624 eq(h.encode(), """\
625bug demonstration
626\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
627\tmore text""")
628 h = Header(hstr.replace('\t', ' '))
629 eq(h.encode(), """\
630bug demonstration
631 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
632 more text""")
633
634 def test_long_nonstring(self):
635 eq = self.ndiffAssertEqual
636 g = Charset("iso-8859-1")
637 cz = Charset("iso-8859-2")
638 utf8 = Charset("utf-8")
639 g_head = (b'Die Mieter treten hier ein werden mit einem Foerderband '
640 b'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
641 b'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
642 b'bef\xf6rdert. ')
643 cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
644 b'd\xf9vtipu.. ')
645 utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
646 '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
647 '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
648 '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
649 '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
650 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
651 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
652 '\u3044\u307e\u3059\u3002')
653 h = Header(g_head, g, header_name='Subject')
654 h.append(cz_head, cz)
655 h.append(utf8_head, utf8)
656 msg = Message()
657 msg['Subject'] = h
658 sfp = StringIO()
659 g = Generator(sfp)
660 g.flatten(msg)
661 eq(sfp.getvalue(), """\
Guido van Rossum9604e662007-08-30 03:46:43 +0000662Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderb?=
663 =?iso-8859-1?q?and_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen?=
664 =?iso-8859-1?q?_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef?=
665 =?iso-8859-1?q?=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hrouti?=
666 =?iso-8859-2?q?ly_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
667 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
668 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn44Gf44KJ?=
669 =?utf-8?b?44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFzIE51bnN0dWNr?=
670 =?utf-8?b?IGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5kIGRhcyBPZGVyIGRp?=
671 =?utf-8?b?ZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIDjgaPjgabjgYTjgb7jgZk=?=
672 =?utf-8?b?44CC?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000673
674""")
Guido van Rossum9604e662007-08-30 03:46:43 +0000675 eq(h.encode(maxlinelen=76), """\
676=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerde?=
677 =?iso-8859-1?q?rband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndis?=
678 =?iso-8859-1?q?chen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klinge?=
679 =?iso-8859-1?q?n_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se?=
680 =?iso-8859-2?q?_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
681 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb?=
682 =?utf-8?b?44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go?=
683 =?utf-8?b?44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBp?=
684 =?utf-8?b?c3QgZGFzIE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWlo?=
685 =?utf-8?b?ZXJodW5kIGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI0=?=
686 =?utf-8?b?44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000687
688 def test_long_header_encode(self):
689 eq = self.ndiffAssertEqual
690 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
691 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
692 header_name='X-Foobar-Spoink-Defrobnit')
693 eq(h.encode(), '''\
694wasnipoop; giraffes="very-long-necked-animals";
695 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
696
697 def test_long_header_encode_with_tab_continuation_is_just_a_hint(self):
698 eq = self.ndiffAssertEqual
699 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
700 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
701 header_name='X-Foobar-Spoink-Defrobnit',
702 continuation_ws='\t')
703 eq(h.encode(), '''\
704wasnipoop; giraffes="very-long-necked-animals";
705 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
706
707 def test_long_header_encode_with_tab_continuation(self):
708 eq = self.ndiffAssertEqual
709 h = Header('wasnipoop; giraffes="very-long-necked-animals";\t'
710 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
711 header_name='X-Foobar-Spoink-Defrobnit',
712 continuation_ws='\t')
713 eq(h.encode(), '''\
714wasnipoop; giraffes="very-long-necked-animals";
715\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
716
717 def test_header_splitter(self):
718 eq = self.ndiffAssertEqual
719 msg = MIMEText('')
720 # It'd be great if we could use add_header() here, but that doesn't
721 # guarantee an order of the parameters.
722 msg['X-Foobar-Spoink-Defrobnit'] = (
723 'wasnipoop; giraffes="very-long-necked-animals"; '
724 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
725 sfp = StringIO()
726 g = Generator(sfp)
727 g.flatten(msg)
728 eq(sfp.getvalue(), '''\
729Content-Type: text/plain; charset="us-ascii"
730MIME-Version: 1.0
731Content-Transfer-Encoding: 7bit
732X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
733 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
734
735''')
736
737 def test_no_semis_header_splitter(self):
738 eq = self.ndiffAssertEqual
739 msg = Message()
740 msg['From'] = 'test@dom.ain'
741 msg['References'] = SPACE.join('<%d@dom.ain>' % i for i in range(10))
742 msg.set_payload('Test')
743 sfp = StringIO()
744 g = Generator(sfp)
745 g.flatten(msg)
746 eq(sfp.getvalue(), """\
747From: test@dom.ain
748References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
749 <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
750
751Test""")
752
R David Murray7da4db12011-04-07 20:37:17 -0400753 def test_last_split_chunk_does_not_fit(self):
754 eq = self.ndiffAssertEqual
755 h = Header('Subject: the first part of this is short, but_the_second'
756 '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line'
757 '_all_by_itself')
758 eq(h.encode(), """\
759Subject: the first part of this is short,
760 but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""")
761
762 def test_splittable_leading_char_followed_by_overlong_unsplitable(self):
763 eq = self.ndiffAssertEqual
764 h = Header(', but_the_second'
765 '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line'
766 '_all_by_itself')
767 eq(h.encode(), """\
768,
769 but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""")
770
771 def test_multiple_splittable_leading_char_followed_by_overlong_unsplitable(self):
772 eq = self.ndiffAssertEqual
773 h = Header(', , but_the_second'
774 '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line'
775 '_all_by_itself')
776 eq(h.encode(), """\
777, ,
778 but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""")
779
780 def test_trailing_splitable_on_overlong_unsplitable(self):
781 eq = self.ndiffAssertEqual
782 h = Header('this_part_does_not_fit_within_maxlinelen_and_thus_should_'
783 'be_on_a_line_all_by_itself;')
784 eq(h.encode(), "this_part_does_not_fit_within_maxlinelen_and_thus_should_"
785 "be_on_a_line_all_by_itself;")
786
787 def test_trailing_splitable_on_overlong_unsplitable_with_leading_splitable(self):
788 eq = self.ndiffAssertEqual
789 h = Header('; '
790 'this_part_does_not_fit_within_maxlinelen_and_thus_should_'
791 'be_on_a_line_all_by_itself;')
792 eq(h.encode(), """\
793;
794 this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself;""")
795
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000796 def test_no_split_long_header(self):
797 eq = self.ndiffAssertEqual
798 hstr = 'References: ' + 'x' * 80
Guido van Rossum9604e662007-08-30 03:46:43 +0000799 h = Header(hstr)
800 # These come on two lines because Headers are really field value
801 # classes and don't really know about their field names.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000802 eq(h.encode(), """\
Guido van Rossum9604e662007-08-30 03:46:43 +0000803References:
804 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
805 h = Header('x' * 80)
806 eq(h.encode(), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000807
808 def test_splitting_multiple_long_lines(self):
809 eq = self.ndiffAssertEqual
810 hstr = """\
811from 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)
812\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)
813\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)
814"""
815 h = Header(hstr, continuation_ws='\t')
816 eq(h.encode(), """\
817from babylon.socal-raves.org (localhost [127.0.0.1]);
818 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
819 for <mailman-admin@babylon.socal-raves.org>;
820 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
821\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
822 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
823 for <mailman-admin@babylon.socal-raves.org>;
824 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
825\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
826 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
827 for <mailman-admin@babylon.socal-raves.org>;
828 Sat, 2 Feb 2002 17:00:06 -0800 (PST)""")
829
830 def test_splitting_first_line_only_is_long(self):
831 eq = self.ndiffAssertEqual
832 hstr = """\
833from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
834\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
835\tid 17k4h5-00034i-00
836\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
837 h = Header(hstr, maxlinelen=78, header_name='Received',
838 continuation_ws='\t')
839 eq(h.encode(), """\
840from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
841 helo=cthulhu.gerg.ca)
842\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
843\tid 17k4h5-00034i-00
844\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
845
846 def test_long_8bit_header(self):
847 eq = self.ndiffAssertEqual
848 msg = Message()
849 h = Header('Britische Regierung gibt', 'iso-8859-1',
850 header_name='Subject')
851 h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
Guido van Rossum9604e662007-08-30 03:46:43 +0000852 eq(h.encode(maxlinelen=76), """\
853=?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
854 =?iso-8859-1?q?hore-Windkraftprojekte?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000855 msg['Subject'] = h
Guido van Rossum9604e662007-08-30 03:46:43 +0000856 eq(msg.as_string(maxheaderlen=76), """\
857Subject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
858 =?iso-8859-1?q?hore-Windkraftprojekte?=
859
860""")
861 eq(msg.as_string(maxheaderlen=0), """\
862Subject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offshore-Windkraftprojekte?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000863
864""")
865
866 def test_long_8bit_header_no_charset(self):
867 eq = self.ndiffAssertEqual
868 msg = Message()
Barry Warsaw8c571042007-08-30 19:17:18 +0000869 header_string = ('Britische Regierung gibt gr\xfcnes Licht '
870 'f\xfcr Offshore-Windkraftprojekte '
871 '<a-very-long-address@example.com>')
872 msg['Reply-To'] = header_string
873 self.assertRaises(UnicodeEncodeError, msg.as_string)
874 msg = Message()
875 msg['Reply-To'] = Header(header_string, 'utf-8',
876 header_name='Reply-To')
877 eq(msg.as_string(maxheaderlen=78), """\
878Reply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?=
879 =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000880
881""")
882
883 def test_long_to_header(self):
884 eq = self.ndiffAssertEqual
885 to = ('"Someone Test #A" <someone@eecs.umich.edu>,'
886 '<someone@eecs.umich.edu>,'
887 '"Someone Test #B" <someone@umich.edu>, '
888 '"Someone Test #C" <someone@eecs.umich.edu>, '
889 '"Someone Test #D" <someone@eecs.umich.edu>')
890 msg = Message()
891 msg['To'] = to
892 eq(msg.as_string(maxheaderlen=78), '''\
Guido van Rossum9604e662007-08-30 03:46:43 +0000893To: "Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,
Barry Warsaw70d61ce2009-03-30 23:12:30 +0000894 "Someone Test #B" <someone@umich.edu>,
Guido van Rossum9604e662007-08-30 03:46:43 +0000895 "Someone Test #C" <someone@eecs.umich.edu>,
896 "Someone Test #D" <someone@eecs.umich.edu>
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000897
898''')
899
900 def test_long_line_after_append(self):
901 eq = self.ndiffAssertEqual
902 s = 'This is an example of string which has almost the limit of header length.'
903 h = Header(s)
904 h.append('Add another line.')
Guido van Rossum9604e662007-08-30 03:46:43 +0000905 eq(h.encode(maxlinelen=76), """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000906This is an example of string which has almost the limit of header length.
907 Add another line.""")
908
909 def test_shorter_line_with_append(self):
910 eq = self.ndiffAssertEqual
911 s = 'This is a shorter line.'
912 h = Header(s)
913 h.append('Add another sentence. (Surprise?)')
914 eq(h.encode(),
915 'This is a shorter line. Add another sentence. (Surprise?)')
916
917 def test_long_field_name(self):
918 eq = self.ndiffAssertEqual
919 fn = 'X-Very-Very-Very-Long-Header-Name'
Guido van Rossum9604e662007-08-30 03:46:43 +0000920 gs = ('Die Mieter treten hier ein werden mit einem Foerderband '
921 'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
922 'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
923 'bef\xf6rdert. ')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000924 h = Header(gs, 'iso-8859-1', header_name=fn)
925 # BAW: this seems broken because the first line is too long
Guido van Rossum9604e662007-08-30 03:46:43 +0000926 eq(h.encode(maxlinelen=76), """\
927=?iso-8859-1?q?Die_Mieter_treten_hier_e?=
928 =?iso-8859-1?q?in_werden_mit_einem_Foerderband_komfortabel_den_Korridor_e?=
929 =?iso-8859-1?q?ntlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_ge?=
930 =?iso-8859-1?q?gen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000931
932 def test_long_received_header(self):
933 h = ('from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) '
934 'by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; '
935 'Wed, 05 Mar 2003 18:10:18 -0700')
936 msg = Message()
937 msg['Received-1'] = Header(h, continuation_ws='\t')
938 msg['Received-2'] = h
Barry Warsawbef9d212007-08-31 10:55:37 +0000939 # This should be splitting on spaces not semicolons.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000940 self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
Barry Warsawbef9d212007-08-31 10:55:37 +0000941Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
942 Wed, 05 Mar 2003 18:10:18 -0700
943Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
944 Wed, 05 Mar 2003 18:10:18 -0700
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000945
946""")
947
948 def test_string_headerinst_eq(self):
949 h = ('<15975.17901.207240.414604@sgigritzmann1.mathematik.'
950 'tu-muenchen.de> (David Bremner\'s message of '
951 '"Thu, 6 Mar 2003 13:58:21 +0100")')
952 msg = Message()
953 msg['Received-1'] = Header(h, header_name='Received-1',
954 continuation_ws='\t')
955 msg['Received-2'] = h
Barry Warsawbef9d212007-08-31 10:55:37 +0000956 # XXX This should be splitting on spaces not commas.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000957 self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
Barry Warsawbef9d212007-08-31 10:55:37 +0000958Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu,
959 6 Mar 2003 13:58:21 +0100\")
960Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu,
961 6 Mar 2003 13:58:21 +0100\")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000962
963""")
964
965 def test_long_unbreakable_lines_with_continuation(self):
966 eq = self.ndiffAssertEqual
967 msg = Message()
968 t = """\
969iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
970 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
971 msg['Face-1'] = t
972 msg['Face-2'] = Header(t, header_name='Face-2')
Barry Warsawbef9d212007-08-31 10:55:37 +0000973 # XXX This splitting is all wrong. It the first value line should be
974 # snug against the field name.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000975 eq(msg.as_string(maxheaderlen=78), """\
Barry Warsawc5a6a302007-08-31 11:19:21 +0000976Face-1:\x20
Barry Warsaw70d61ce2009-03-30 23:12:30 +0000977 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000978 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
Barry Warsawc5a6a302007-08-31 11:19:21 +0000979Face-2:\x20
Barry Warsawbef9d212007-08-31 10:55:37 +0000980 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000981 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
982
983""")
984
985 def test_another_long_multiline_header(self):
986 eq = self.ndiffAssertEqual
987 m = ('Received: from siimage.com '
988 '([172.25.1.3]) by zima.siliconimage.com with '
Guido van Rossum9604e662007-08-30 03:46:43 +0000989 'Microsoft SMTPSVC(5.0.2195.4905); '
990 'Wed, 16 Oct 2002 07:41:11 -0700')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000991 msg = email.message_from_string(m)
992 eq(msg.as_string(maxheaderlen=78), '''\
Barry Warsawbef9d212007-08-31 10:55:37 +0000993Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
994 Wed, 16 Oct 2002 07:41:11 -0700
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000995
996''')
997
998 def test_long_lines_with_different_header(self):
999 eq = self.ndiffAssertEqual
1000 h = ('List-Unsubscribe: '
1001 '<http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,'
1002 ' <mailto:spamassassin-talk-request@lists.sourceforge.net'
1003 '?subject=unsubscribe>')
1004 msg = Message()
1005 msg['List'] = h
1006 msg['List'] = Header(h, header_name='List')
1007 eq(msg.as_string(maxheaderlen=78), """\
1008List: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
Barry Warsawbef9d212007-08-31 10:55:37 +00001009 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001010List: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
Barry Warsawbef9d212007-08-31 10:55:37 +00001011 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001012
1013""")
1014
R. David Murray43b2f452011-02-11 03:13:19 +00001015 def test_long_rfc2047_header_with_embedded_fws(self):
1016 h = Header(textwrap.dedent("""\
1017 We're going to pretend this header is in a non-ascii character set
1018 \tto see if line wrapping with encoded words and embedded
1019 folding white space works"""),
1020 charset='utf-8',
1021 header_name='Test')
1022 self.assertEqual(h.encode()+'\n', textwrap.dedent("""\
1023 =?utf-8?q?We=27re_going_to_pretend_this_header_is_in_a_non-ascii_chara?=
1024 =?utf-8?q?cter_set?=
1025 =?utf-8?q?_to_see_if_line_wrapping_with_encoded_words_and_embedded?=
1026 =?utf-8?q?_folding_white_space_works?=""")+'\n')
1027
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001028
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001029
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001030# Test mangling of "From " lines in the body of a message
1031class TestFromMangling(unittest.TestCase):
1032 def setUp(self):
1033 self.msg = Message()
1034 self.msg['From'] = 'aaa@bbb.org'
1035 self.msg.set_payload("""\
1036From the desk of A.A.A.:
1037Blah blah blah
1038""")
1039
1040 def test_mangled_from(self):
1041 s = StringIO()
1042 g = Generator(s, mangle_from_=True)
1043 g.flatten(self.msg)
1044 self.assertEqual(s.getvalue(), """\
1045From: aaa@bbb.org
1046
1047>From the desk of A.A.A.:
1048Blah blah blah
1049""")
1050
1051 def test_dont_mangle_from(self):
1052 s = StringIO()
1053 g = Generator(s, mangle_from_=False)
1054 g.flatten(self.msg)
1055 self.assertEqual(s.getvalue(), """\
1056From: aaa@bbb.org
1057
1058From the desk of A.A.A.:
1059Blah blah blah
1060""")
1061
1062
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001063
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001064# Test the basic MIMEAudio class
1065class TestMIMEAudio(unittest.TestCase):
1066 def setUp(self):
1067 # Make sure we pick up the audiotest.au that lives in email/test/data.
1068 # In Python, there's an audiotest.au living in Lib/test but that isn't
1069 # included in some binary distros that don't include the test
1070 # package. The trailing empty string on the .join() is significant
1071 # since findfile() will do a dirname().
1072 datadir = os.path.join(os.path.dirname(landmark), 'data', '')
1073 with open(findfile('audiotest.au', datadir), 'rb') as fp:
1074 self._audiodata = fp.read()
1075 self._au = MIMEAudio(self._audiodata)
1076
1077 def test_guess_minor_type(self):
1078 self.assertEqual(self._au.get_content_type(), 'audio/basic')
1079
1080 def test_encoding(self):
1081 payload = self._au.get_payload()
R. David Murray99147c42010-06-04 16:15:34 +00001082 self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
1083 self._audiodata)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001084
1085 def test_checkSetMinor(self):
1086 au = MIMEAudio(self._audiodata, 'fish')
1087 self.assertEqual(au.get_content_type(), 'audio/fish')
1088
1089 def test_add_header(self):
1090 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001091 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001092 self._au.add_header('Content-Disposition', 'attachment',
1093 filename='audiotest.au')
1094 eq(self._au['content-disposition'],
1095 'attachment; filename="audiotest.au"')
1096 eq(self._au.get_params(header='content-disposition'),
1097 [('attachment', ''), ('filename', 'audiotest.au')])
1098 eq(self._au.get_param('filename', header='content-disposition'),
1099 'audiotest.au')
1100 missing = []
1101 eq(self._au.get_param('attachment', header='content-disposition'), '')
1102 unless(self._au.get_param('foo', failobj=missing,
1103 header='content-disposition') is missing)
1104 # Try some missing stuff
1105 unless(self._au.get_param('foobar', missing) is missing)
1106 unless(self._au.get_param('attachment', missing,
1107 header='foobar') is missing)
1108
1109
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001110
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001111# Test the basic MIMEImage class
1112class TestMIMEImage(unittest.TestCase):
1113 def setUp(self):
1114 with openfile('PyBanner048.gif', 'rb') as fp:
1115 self._imgdata = fp.read()
1116 self._im = MIMEImage(self._imgdata)
1117
1118 def test_guess_minor_type(self):
1119 self.assertEqual(self._im.get_content_type(), 'image/gif')
1120
1121 def test_encoding(self):
1122 payload = self._im.get_payload()
R. David Murray99147c42010-06-04 16:15:34 +00001123 self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
1124 self._imgdata)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001125
1126 def test_checkSetMinor(self):
1127 im = MIMEImage(self._imgdata, 'fish')
1128 self.assertEqual(im.get_content_type(), 'image/fish')
1129
1130 def test_add_header(self):
1131 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001132 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001133 self._im.add_header('Content-Disposition', 'attachment',
1134 filename='dingusfish.gif')
1135 eq(self._im['content-disposition'],
1136 'attachment; filename="dingusfish.gif"')
1137 eq(self._im.get_params(header='content-disposition'),
1138 [('attachment', ''), ('filename', 'dingusfish.gif')])
1139 eq(self._im.get_param('filename', header='content-disposition'),
1140 'dingusfish.gif')
1141 missing = []
1142 eq(self._im.get_param('attachment', header='content-disposition'), '')
1143 unless(self._im.get_param('foo', failobj=missing,
1144 header='content-disposition') is missing)
1145 # Try some missing stuff
1146 unless(self._im.get_param('foobar', missing) is missing)
1147 unless(self._im.get_param('attachment', missing,
1148 header='foobar') is missing)
1149
1150
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001151
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001152# Test the basic MIMEApplication class
1153class TestMIMEApplication(unittest.TestCase):
1154 def test_headers(self):
1155 eq = self.assertEqual
Barry Warsaw8b2af272007-08-31 03:04:26 +00001156 msg = MIMEApplication(b'\xfa\xfb\xfc\xfd\xfe\xff')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001157 eq(msg.get_content_type(), 'application/octet-stream')
1158 eq(msg['content-transfer-encoding'], 'base64')
1159
1160 def test_body(self):
1161 eq = self.assertEqual
R David Murray6d94bd42011-03-16 15:52:22 -04001162 bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff'
1163 msg = MIMEApplication(bytesdata)
1164 # whitespace in the cte encoded block is RFC-irrelevant.
1165 eq(msg.get_payload().strip(), '+vv8/f7/')
1166 eq(msg.get_payload(decode=True), bytesdata)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001167
1168
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001169
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001170# Test the basic MIMEText class
1171class TestMIMEText(unittest.TestCase):
1172 def setUp(self):
1173 self._msg = MIMEText('hello there')
1174
1175 def test_types(self):
1176 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001177 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001178 eq(self._msg.get_content_type(), 'text/plain')
1179 eq(self._msg.get_param('charset'), 'us-ascii')
1180 missing = []
1181 unless(self._msg.get_param('foobar', missing) is missing)
1182 unless(self._msg.get_param('charset', missing, header='foobar')
1183 is missing)
1184
1185 def test_payload(self):
1186 self.assertEqual(self._msg.get_payload(), 'hello there')
Georg Brandlab91fde2009-08-13 08:51:18 +00001187 self.assertTrue(not self._msg.is_multipart())
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001188
1189 def test_charset(self):
1190 eq = self.assertEqual
1191 msg = MIMEText('hello there', _charset='us-ascii')
1192 eq(msg.get_charset().input_charset, 'us-ascii')
1193 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1194
R. David Murrayd2d08c62010-06-03 02:05:47 +00001195 def test_7bit_input(self):
1196 eq = self.assertEqual
1197 msg = MIMEText('hello there', _charset='us-ascii')
1198 eq(msg.get_charset().input_charset, 'us-ascii')
1199 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1200
1201 def test_7bit_input_no_charset(self):
1202 eq = self.assertEqual
1203 msg = MIMEText('hello there')
1204 eq(msg.get_charset(), 'us-ascii')
1205 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1206 self.assertTrue('hello there' in msg.as_string())
1207
1208 def test_utf8_input(self):
1209 teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1210 eq = self.assertEqual
1211 msg = MIMEText(teststr, _charset='utf-8')
1212 eq(msg.get_charset().output_charset, 'utf-8')
1213 eq(msg['content-type'], 'text/plain; charset="utf-8"')
1214 eq(msg.get_payload(decode=True), teststr.encode('utf-8'))
1215
1216 @unittest.skip("can't fix because of backward compat in email5, "
1217 "will fix in email6")
1218 def test_utf8_input_no_charset(self):
1219 teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1220 self.assertRaises(UnicodeEncodeError, MIMEText, teststr)
1221
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001222
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001223
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001224# Test complicated multipart/* messages
1225class TestMultipart(TestEmailBase):
1226 def setUp(self):
1227 with openfile('PyBanner048.gif', 'rb') as fp:
1228 data = fp.read()
1229 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
1230 image = MIMEImage(data, name='dingusfish.gif')
1231 image.add_header('content-disposition', 'attachment',
1232 filename='dingusfish.gif')
1233 intro = MIMEText('''\
1234Hi there,
1235
1236This is the dingus fish.
1237''')
1238 container.attach(intro)
1239 container.attach(image)
1240 container['From'] = 'Barry <barry@digicool.com>'
1241 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1242 container['Subject'] = 'Here is your dingus fish'
1243
1244 now = 987809702.54848599
1245 timetuple = time.localtime(now)
1246 if timetuple[-1] == 0:
1247 tzsecs = time.timezone
1248 else:
1249 tzsecs = time.altzone
1250 if tzsecs > 0:
1251 sign = '-'
1252 else:
1253 sign = '+'
1254 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
1255 container['Date'] = time.strftime(
1256 '%a, %d %b %Y %H:%M:%S',
1257 time.localtime(now)) + tzoffset
1258 self._msg = container
1259 self._im = image
1260 self._txt = intro
1261
1262 def test_hierarchy(self):
1263 # convenience
1264 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001265 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001266 raises = self.assertRaises
1267 # tests
1268 m = self._msg
1269 unless(m.is_multipart())
1270 eq(m.get_content_type(), 'multipart/mixed')
1271 eq(len(m.get_payload()), 2)
1272 raises(IndexError, m.get_payload, 2)
1273 m0 = m.get_payload(0)
1274 m1 = m.get_payload(1)
1275 unless(m0 is self._txt)
1276 unless(m1 is self._im)
1277 eq(m.get_payload(), [m0, m1])
1278 unless(not m0.is_multipart())
1279 unless(not m1.is_multipart())
1280
1281 def test_empty_multipart_idempotent(self):
1282 text = """\
1283Content-Type: multipart/mixed; boundary="BOUNDARY"
1284MIME-Version: 1.0
1285Subject: A subject
1286To: aperson@dom.ain
1287From: bperson@dom.ain
1288
1289
1290--BOUNDARY
1291
1292
1293--BOUNDARY--
1294"""
1295 msg = Parser().parsestr(text)
1296 self.ndiffAssertEqual(text, msg.as_string())
1297
1298 def test_no_parts_in_a_multipart_with_none_epilogue(self):
1299 outer = MIMEBase('multipart', 'mixed')
1300 outer['Subject'] = 'A subject'
1301 outer['To'] = 'aperson@dom.ain'
1302 outer['From'] = 'bperson@dom.ain'
1303 outer.set_boundary('BOUNDARY')
1304 self.ndiffAssertEqual(outer.as_string(), '''\
1305Content-Type: multipart/mixed; boundary="BOUNDARY"
1306MIME-Version: 1.0
1307Subject: A subject
1308To: aperson@dom.ain
1309From: bperson@dom.ain
1310
1311--BOUNDARY
1312
1313--BOUNDARY--''')
1314
1315 def test_no_parts_in_a_multipart_with_empty_epilogue(self):
1316 outer = MIMEBase('multipart', 'mixed')
1317 outer['Subject'] = 'A subject'
1318 outer['To'] = 'aperson@dom.ain'
1319 outer['From'] = 'bperson@dom.ain'
1320 outer.preamble = ''
1321 outer.epilogue = ''
1322 outer.set_boundary('BOUNDARY')
1323 self.ndiffAssertEqual(outer.as_string(), '''\
1324Content-Type: multipart/mixed; boundary="BOUNDARY"
1325MIME-Version: 1.0
1326Subject: A subject
1327To: aperson@dom.ain
1328From: bperson@dom.ain
1329
1330
1331--BOUNDARY
1332
1333--BOUNDARY--
1334''')
1335
1336 def test_one_part_in_a_multipart(self):
1337 eq = self.ndiffAssertEqual
1338 outer = MIMEBase('multipart', 'mixed')
1339 outer['Subject'] = 'A subject'
1340 outer['To'] = 'aperson@dom.ain'
1341 outer['From'] = 'bperson@dom.ain'
1342 outer.set_boundary('BOUNDARY')
1343 msg = MIMEText('hello world')
1344 outer.attach(msg)
1345 eq(outer.as_string(), '''\
1346Content-Type: multipart/mixed; boundary="BOUNDARY"
1347MIME-Version: 1.0
1348Subject: A subject
1349To: aperson@dom.ain
1350From: bperson@dom.ain
1351
1352--BOUNDARY
1353Content-Type: text/plain; charset="us-ascii"
1354MIME-Version: 1.0
1355Content-Transfer-Encoding: 7bit
1356
1357hello world
1358--BOUNDARY--''')
1359
1360 def test_seq_parts_in_a_multipart_with_empty_preamble(self):
1361 eq = self.ndiffAssertEqual
1362 outer = MIMEBase('multipart', 'mixed')
1363 outer['Subject'] = 'A subject'
1364 outer['To'] = 'aperson@dom.ain'
1365 outer['From'] = 'bperson@dom.ain'
1366 outer.preamble = ''
1367 msg = MIMEText('hello world')
1368 outer.attach(msg)
1369 outer.set_boundary('BOUNDARY')
1370 eq(outer.as_string(), '''\
1371Content-Type: multipart/mixed; boundary="BOUNDARY"
1372MIME-Version: 1.0
1373Subject: A subject
1374To: aperson@dom.ain
1375From: bperson@dom.ain
1376
1377
1378--BOUNDARY
1379Content-Type: text/plain; charset="us-ascii"
1380MIME-Version: 1.0
1381Content-Transfer-Encoding: 7bit
1382
1383hello world
1384--BOUNDARY--''')
1385
1386
1387 def test_seq_parts_in_a_multipart_with_none_preamble(self):
1388 eq = self.ndiffAssertEqual
1389 outer = MIMEBase('multipart', 'mixed')
1390 outer['Subject'] = 'A subject'
1391 outer['To'] = 'aperson@dom.ain'
1392 outer['From'] = 'bperson@dom.ain'
1393 outer.preamble = None
1394 msg = MIMEText('hello world')
1395 outer.attach(msg)
1396 outer.set_boundary('BOUNDARY')
1397 eq(outer.as_string(), '''\
1398Content-Type: multipart/mixed; boundary="BOUNDARY"
1399MIME-Version: 1.0
1400Subject: A subject
1401To: aperson@dom.ain
1402From: bperson@dom.ain
1403
1404--BOUNDARY
1405Content-Type: text/plain; charset="us-ascii"
1406MIME-Version: 1.0
1407Content-Transfer-Encoding: 7bit
1408
1409hello world
1410--BOUNDARY--''')
1411
1412
1413 def test_seq_parts_in_a_multipart_with_none_epilogue(self):
1414 eq = self.ndiffAssertEqual
1415 outer = MIMEBase('multipart', 'mixed')
1416 outer['Subject'] = 'A subject'
1417 outer['To'] = 'aperson@dom.ain'
1418 outer['From'] = 'bperson@dom.ain'
1419 outer.epilogue = None
1420 msg = MIMEText('hello world')
1421 outer.attach(msg)
1422 outer.set_boundary('BOUNDARY')
1423 eq(outer.as_string(), '''\
1424Content-Type: multipart/mixed; boundary="BOUNDARY"
1425MIME-Version: 1.0
1426Subject: A subject
1427To: aperson@dom.ain
1428From: bperson@dom.ain
1429
1430--BOUNDARY
1431Content-Type: text/plain; charset="us-ascii"
1432MIME-Version: 1.0
1433Content-Transfer-Encoding: 7bit
1434
1435hello world
1436--BOUNDARY--''')
1437
1438
1439 def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
1440 eq = self.ndiffAssertEqual
1441 outer = MIMEBase('multipart', 'mixed')
1442 outer['Subject'] = 'A subject'
1443 outer['To'] = 'aperson@dom.ain'
1444 outer['From'] = 'bperson@dom.ain'
1445 outer.epilogue = ''
1446 msg = MIMEText('hello world')
1447 outer.attach(msg)
1448 outer.set_boundary('BOUNDARY')
1449 eq(outer.as_string(), '''\
1450Content-Type: multipart/mixed; boundary="BOUNDARY"
1451MIME-Version: 1.0
1452Subject: A subject
1453To: aperson@dom.ain
1454From: bperson@dom.ain
1455
1456--BOUNDARY
1457Content-Type: text/plain; charset="us-ascii"
1458MIME-Version: 1.0
1459Content-Transfer-Encoding: 7bit
1460
1461hello world
1462--BOUNDARY--
1463''')
1464
1465
1466 def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
1467 eq = self.ndiffAssertEqual
1468 outer = MIMEBase('multipart', 'mixed')
1469 outer['Subject'] = 'A subject'
1470 outer['To'] = 'aperson@dom.ain'
1471 outer['From'] = 'bperson@dom.ain'
1472 outer.epilogue = '\n'
1473 msg = MIMEText('hello world')
1474 outer.attach(msg)
1475 outer.set_boundary('BOUNDARY')
1476 eq(outer.as_string(), '''\
1477Content-Type: multipart/mixed; boundary="BOUNDARY"
1478MIME-Version: 1.0
1479Subject: A subject
1480To: aperson@dom.ain
1481From: bperson@dom.ain
1482
1483--BOUNDARY
1484Content-Type: text/plain; charset="us-ascii"
1485MIME-Version: 1.0
1486Content-Transfer-Encoding: 7bit
1487
1488hello world
1489--BOUNDARY--
1490
1491''')
1492
1493 def test_message_external_body(self):
1494 eq = self.assertEqual
1495 msg = self._msgobj('msg_36.txt')
1496 eq(len(msg.get_payload()), 2)
1497 msg1 = msg.get_payload(1)
1498 eq(msg1.get_content_type(), 'multipart/alternative')
1499 eq(len(msg1.get_payload()), 2)
1500 for subpart in msg1.get_payload():
1501 eq(subpart.get_content_type(), 'message/external-body')
1502 eq(len(subpart.get_payload()), 1)
1503 subsubpart = subpart.get_payload(0)
1504 eq(subsubpart.get_content_type(), 'text/plain')
1505
1506 def test_double_boundary(self):
1507 # msg_37.txt is a multipart that contains two dash-boundary's in a
1508 # row. Our interpretation of RFC 2046 calls for ignoring the second
1509 # and subsequent boundaries.
1510 msg = self._msgobj('msg_37.txt')
1511 self.assertEqual(len(msg.get_payload()), 3)
1512
1513 def test_nested_inner_contains_outer_boundary(self):
1514 eq = self.ndiffAssertEqual
1515 # msg_38.txt has an inner part that contains outer boundaries. My
1516 # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
1517 # these are illegal and should be interpreted as unterminated inner
1518 # parts.
1519 msg = self._msgobj('msg_38.txt')
1520 sfp = StringIO()
1521 iterators._structure(msg, sfp)
1522 eq(sfp.getvalue(), """\
1523multipart/mixed
1524 multipart/mixed
1525 multipart/alternative
1526 text/plain
1527 text/plain
1528 text/plain
1529 text/plain
1530""")
1531
1532 def test_nested_with_same_boundary(self):
1533 eq = self.ndiffAssertEqual
1534 # msg 39.txt is similarly evil in that it's got inner parts that use
1535 # the same boundary as outer parts. Again, I believe the way this is
1536 # parsed is closest to the spirit of RFC 2046
1537 msg = self._msgobj('msg_39.txt')
1538 sfp = StringIO()
1539 iterators._structure(msg, sfp)
1540 eq(sfp.getvalue(), """\
1541multipart/mixed
1542 multipart/mixed
1543 multipart/alternative
1544 application/octet-stream
1545 application/octet-stream
1546 text/plain
1547""")
1548
1549 def test_boundary_in_non_multipart(self):
1550 msg = self._msgobj('msg_40.txt')
1551 self.assertEqual(msg.as_string(), '''\
1552MIME-Version: 1.0
1553Content-Type: text/html; boundary="--961284236552522269"
1554
1555----961284236552522269
1556Content-Type: text/html;
1557Content-Transfer-Encoding: 7Bit
1558
1559<html></html>
1560
1561----961284236552522269--
1562''')
1563
1564 def test_boundary_with_leading_space(self):
1565 eq = self.assertEqual
1566 msg = email.message_from_string('''\
1567MIME-Version: 1.0
1568Content-Type: multipart/mixed; boundary=" XXXX"
1569
1570-- XXXX
1571Content-Type: text/plain
1572
1573
1574-- XXXX
1575Content-Type: text/plain
1576
1577-- XXXX--
1578''')
Georg Brandlab91fde2009-08-13 08:51:18 +00001579 self.assertTrue(msg.is_multipart())
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001580 eq(msg.get_boundary(), ' XXXX')
1581 eq(len(msg.get_payload()), 2)
1582
1583 def test_boundary_without_trailing_newline(self):
1584 m = Parser().parsestr("""\
1585Content-Type: multipart/mixed; boundary="===============0012394164=="
1586MIME-Version: 1.0
1587
1588--===============0012394164==
1589Content-Type: image/file1.jpg
1590MIME-Version: 1.0
1591Content-Transfer-Encoding: base64
1592
1593YXNkZg==
1594--===============0012394164==--""")
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001595 self.assertEqual(m.get_payload(0).get_payload(), 'YXNkZg==')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001596
1597
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001598
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001599# Test some badly formatted messages
1600class TestNonConformant(TestEmailBase):
1601 def test_parse_missing_minor_type(self):
1602 eq = self.assertEqual
1603 msg = self._msgobj('msg_14.txt')
1604 eq(msg.get_content_type(), 'text/plain')
1605 eq(msg.get_content_maintype(), 'text')
1606 eq(msg.get_content_subtype(), 'plain')
1607
1608 def test_same_boundary_inner_outer(self):
Georg Brandlab91fde2009-08-13 08:51:18 +00001609 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001610 msg = self._msgobj('msg_15.txt')
1611 # XXX We can probably eventually do better
1612 inner = msg.get_payload(0)
1613 unless(hasattr(inner, 'defects'))
1614 self.assertEqual(len(inner.defects), 1)
1615 unless(isinstance(inner.defects[0],
1616 errors.StartBoundaryNotFoundDefect))
1617
1618 def test_multipart_no_boundary(self):
Georg Brandlab91fde2009-08-13 08:51:18 +00001619 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001620 msg = self._msgobj('msg_25.txt')
1621 unless(isinstance(msg.get_payload(), str))
1622 self.assertEqual(len(msg.defects), 2)
1623 unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
1624 unless(isinstance(msg.defects[1],
1625 errors.MultipartInvariantViolationDefect))
1626
1627 def test_invalid_content_type(self):
1628 eq = self.assertEqual
1629 neq = self.ndiffAssertEqual
1630 msg = Message()
1631 # RFC 2045, $5.2 says invalid yields text/plain
1632 msg['Content-Type'] = 'text'
1633 eq(msg.get_content_maintype(), 'text')
1634 eq(msg.get_content_subtype(), 'plain')
1635 eq(msg.get_content_type(), 'text/plain')
1636 # Clear the old value and try something /really/ invalid
1637 del msg['content-type']
1638 msg['Content-Type'] = 'foo'
1639 eq(msg.get_content_maintype(), 'text')
1640 eq(msg.get_content_subtype(), 'plain')
1641 eq(msg.get_content_type(), 'text/plain')
1642 # Still, make sure that the message is idempotently generated
1643 s = StringIO()
1644 g = Generator(s)
1645 g.flatten(msg)
1646 neq(s.getvalue(), 'Content-Type: foo\n\n')
1647
1648 def test_no_start_boundary(self):
1649 eq = self.ndiffAssertEqual
1650 msg = self._msgobj('msg_31.txt')
1651 eq(msg.get_payload(), """\
1652--BOUNDARY
1653Content-Type: text/plain
1654
1655message 1
1656
1657--BOUNDARY
1658Content-Type: text/plain
1659
1660message 2
1661
1662--BOUNDARY--
1663""")
1664
1665 def test_no_separating_blank_line(self):
1666 eq = self.ndiffAssertEqual
1667 msg = self._msgobj('msg_35.txt')
1668 eq(msg.as_string(), """\
1669From: aperson@dom.ain
1670To: bperson@dom.ain
1671Subject: here's something interesting
1672
1673counter to RFC 2822, there's no separating newline here
1674""")
1675
1676 def test_lying_multipart(self):
Georg Brandlab91fde2009-08-13 08:51:18 +00001677 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001678 msg = self._msgobj('msg_41.txt')
1679 unless(hasattr(msg, 'defects'))
1680 self.assertEqual(len(msg.defects), 2)
1681 unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
1682 unless(isinstance(msg.defects[1],
1683 errors.MultipartInvariantViolationDefect))
1684
1685 def test_missing_start_boundary(self):
1686 outer = self._msgobj('msg_42.txt')
1687 # The message structure is:
1688 #
1689 # multipart/mixed
1690 # text/plain
1691 # message/rfc822
1692 # multipart/mixed [*]
1693 #
1694 # [*] This message is missing its start boundary
1695 bad = outer.get_payload(1).get_payload(0)
1696 self.assertEqual(len(bad.defects), 1)
Georg Brandlab91fde2009-08-13 08:51:18 +00001697 self.assertTrue(isinstance(bad.defects[0],
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001698 errors.StartBoundaryNotFoundDefect))
1699
1700 def test_first_line_is_continuation_header(self):
1701 eq = self.assertEqual
1702 m = ' Line 1\nLine 2\nLine 3'
1703 msg = email.message_from_string(m)
1704 eq(msg.keys(), [])
1705 eq(msg.get_payload(), 'Line 2\nLine 3')
1706 eq(len(msg.defects), 1)
Georg Brandlab91fde2009-08-13 08:51:18 +00001707 self.assertTrue(isinstance(msg.defects[0],
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001708 errors.FirstHeaderLineIsContinuationDefect))
1709 eq(msg.defects[0].line, ' Line 1\n')
1710
1711
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001712
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001713# Test RFC 2047 header encoding and decoding
Guido van Rossum9604e662007-08-30 03:46:43 +00001714class TestRFC2047(TestEmailBase):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001715 def test_rfc2047_multiline(self):
1716 eq = self.assertEqual
1717 s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1718 foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1719 dh = decode_header(s)
1720 eq(dh, [
1721 (b'Re:', None),
1722 (b'r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1723 (b'baz foo bar', None),
1724 (b'r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1725 header = make_header(dh)
1726 eq(str(header),
1727 'Re: r\xe4ksm\xf6rg\xe5s baz foo bar r\xe4ksm\xf6rg\xe5s')
Barry Warsaw00b34222007-08-31 02:35:00 +00001728 self.ndiffAssertEqual(header.encode(maxlinelen=76), """\
Guido van Rossum9604e662007-08-30 03:46:43 +00001729Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?=
1730 =?mac-iceland?q?=9Arg=8Cs?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001731
1732 def test_whitespace_eater_unicode(self):
1733 eq = self.assertEqual
1734 s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1735 dh = decode_header(s)
1736 eq(dh, [(b'Andr\xe9', 'iso-8859-1'),
1737 (b'Pirard <pirard@dom.ain>', None)])
1738 header = str(make_header(dh))
1739 eq(header, 'Andr\xe9 Pirard <pirard@dom.ain>')
1740
1741 def test_whitespace_eater_unicode_2(self):
1742 eq = self.assertEqual
1743 s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1744 dh = decode_header(s)
1745 eq(dh, [(b'The', None), (b'quick brown fox', 'iso-8859-1'),
1746 (b'jumped over the', None), (b'lazy dog', 'iso-8859-1')])
1747 hu = str(make_header(dh))
1748 eq(hu, 'The quick brown fox jumped over the lazy dog')
1749
1750 def test_rfc2047_missing_whitespace(self):
1751 s = 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
1752 dh = decode_header(s)
1753 self.assertEqual(dh, [(s, None)])
1754
1755 def test_rfc2047_with_whitespace(self):
1756 s = 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
1757 dh = decode_header(s)
1758 self.assertEqual(dh, [(b'Sm', None), (b'\xf6', 'iso-8859-1'),
1759 (b'rg', None), (b'\xe5', 'iso-8859-1'),
1760 (b'sbord', None)])
1761
R. David Murraye06528c2010-08-03 23:35:44 +00001762 def test_rfc2047_B_bad_padding(self):
1763 s = '=?iso-8859-1?B?%s?='
1764 data = [ # only test complete bytes
1765 ('dm==', b'v'), ('dm=', b'v'), ('dm', b'v'),
1766 ('dmk=', b'vi'), ('dmk', b'vi')
1767 ]
1768 for q, a in data:
1769 dh = decode_header(s % q)
1770 self.assertEqual(dh, [(a, 'iso-8859-1')])
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001771
R. David Murrayf9c957f2010-10-01 15:45:48 +00001772 def test_rfc2047_Q_invalid_digits(self):
1773 # issue 10004.
1774 s = '=?iso-8659-1?Q?andr=e9=zz?='
1775 self.assertEqual(decode_header(s),
1776 [(b'andr\xe9=zz', 'iso-8659-1')])
1777
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00001778
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001779# Test the MIMEMessage class
1780class TestMIMEMessage(TestEmailBase):
1781 def setUp(self):
1782 with openfile('msg_11.txt') as fp:
1783 self._text = fp.read()
1784
1785 def test_type_error(self):
1786 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
1787
1788 def test_valid_argument(self):
1789 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001790 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001791 subject = 'A sub-message'
1792 m = Message()
1793 m['Subject'] = subject
1794 r = MIMEMessage(m)
1795 eq(r.get_content_type(), 'message/rfc822')
1796 payload = r.get_payload()
1797 unless(isinstance(payload, list))
1798 eq(len(payload), 1)
1799 subpart = payload[0]
1800 unless(subpart is m)
1801 eq(subpart['subject'], subject)
1802
1803 def test_bad_multipart(self):
1804 eq = self.assertEqual
1805 msg1 = Message()
1806 msg1['Subject'] = 'subpart 1'
1807 msg2 = Message()
1808 msg2['Subject'] = 'subpart 2'
1809 r = MIMEMessage(msg1)
1810 self.assertRaises(errors.MultipartConversionError, r.attach, msg2)
1811
1812 def test_generate(self):
1813 # First craft the message to be encapsulated
1814 m = Message()
1815 m['Subject'] = 'An enclosed message'
1816 m.set_payload('Here is the body of the message.\n')
1817 r = MIMEMessage(m)
1818 r['Subject'] = 'The enclosing message'
1819 s = StringIO()
1820 g = Generator(s)
1821 g.flatten(r)
1822 self.assertEqual(s.getvalue(), """\
1823Content-Type: message/rfc822
1824MIME-Version: 1.0
1825Subject: The enclosing message
1826
1827Subject: An enclosed message
1828
1829Here is the body of the message.
1830""")
1831
1832 def test_parse_message_rfc822(self):
1833 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001834 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001835 msg = self._msgobj('msg_11.txt')
1836 eq(msg.get_content_type(), 'message/rfc822')
1837 payload = msg.get_payload()
1838 unless(isinstance(payload, list))
1839 eq(len(payload), 1)
1840 submsg = payload[0]
Georg Brandlab91fde2009-08-13 08:51:18 +00001841 self.assertTrue(isinstance(submsg, Message))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001842 eq(submsg['subject'], 'An enclosed message')
1843 eq(submsg.get_payload(), 'Here is the body of the message.\n')
1844
1845 def test_dsn(self):
1846 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00001847 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001848 # msg 16 is a Delivery Status Notification, see RFC 1894
1849 msg = self._msgobj('msg_16.txt')
1850 eq(msg.get_content_type(), 'multipart/report')
1851 unless(msg.is_multipart())
1852 eq(len(msg.get_payload()), 3)
1853 # Subpart 1 is a text/plain, human readable section
1854 subpart = msg.get_payload(0)
1855 eq(subpart.get_content_type(), 'text/plain')
1856 eq(subpart.get_payload(), """\
1857This report relates to a message you sent with the following header fields:
1858
1859 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1860 Date: Sun, 23 Sep 2001 20:10:55 -0700
1861 From: "Ian T. Henry" <henryi@oxy.edu>
1862 To: SoCal Raves <scr@socal-raves.org>
1863 Subject: [scr] yeah for Ians!!
1864
1865Your message cannot be delivered to the following recipients:
1866
1867 Recipient address: jangel1@cougar.noc.ucla.edu
1868 Reason: recipient reached disk quota
1869
1870""")
1871 # Subpart 2 contains the machine parsable DSN information. It
1872 # consists of two blocks of headers, represented by two nested Message
1873 # objects.
1874 subpart = msg.get_payload(1)
1875 eq(subpart.get_content_type(), 'message/delivery-status')
1876 eq(len(subpart.get_payload()), 2)
1877 # message/delivery-status should treat each block as a bunch of
1878 # headers, i.e. a bunch of Message objects.
1879 dsn1 = subpart.get_payload(0)
1880 unless(isinstance(dsn1, Message))
1881 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1882 eq(dsn1.get_param('dns', header='reporting-mta'), '')
1883 # Try a missing one <wink>
1884 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1885 dsn2 = subpart.get_payload(1)
1886 unless(isinstance(dsn2, Message))
1887 eq(dsn2['action'], 'failed')
1888 eq(dsn2.get_params(header='original-recipient'),
1889 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1890 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1891 # Subpart 3 is the original message
1892 subpart = msg.get_payload(2)
1893 eq(subpart.get_content_type(), 'message/rfc822')
1894 payload = subpart.get_payload()
1895 unless(isinstance(payload, list))
1896 eq(len(payload), 1)
1897 subsubpart = payload[0]
1898 unless(isinstance(subsubpart, Message))
1899 eq(subsubpart.get_content_type(), 'text/plain')
1900 eq(subsubpart['message-id'],
1901 '<002001c144a6$8752e060$56104586@oxy.edu>')
1902
1903 def test_epilogue(self):
1904 eq = self.ndiffAssertEqual
1905 with openfile('msg_21.txt') as fp:
1906 text = fp.read()
1907 msg = Message()
1908 msg['From'] = 'aperson@dom.ain'
1909 msg['To'] = 'bperson@dom.ain'
1910 msg['Subject'] = 'Test'
1911 msg.preamble = 'MIME message'
1912 msg.epilogue = 'End of MIME message\n'
1913 msg1 = MIMEText('One')
1914 msg2 = MIMEText('Two')
1915 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1916 msg.attach(msg1)
1917 msg.attach(msg2)
1918 sfp = StringIO()
1919 g = Generator(sfp)
1920 g.flatten(msg)
1921 eq(sfp.getvalue(), text)
1922
1923 def test_no_nl_preamble(self):
1924 eq = self.ndiffAssertEqual
1925 msg = Message()
1926 msg['From'] = 'aperson@dom.ain'
1927 msg['To'] = 'bperson@dom.ain'
1928 msg['Subject'] = 'Test'
1929 msg.preamble = 'MIME message'
1930 msg.epilogue = ''
1931 msg1 = MIMEText('One')
1932 msg2 = MIMEText('Two')
1933 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1934 msg.attach(msg1)
1935 msg.attach(msg2)
1936 eq(msg.as_string(), """\
1937From: aperson@dom.ain
1938To: bperson@dom.ain
1939Subject: Test
1940Content-Type: multipart/mixed; boundary="BOUNDARY"
1941
1942MIME message
1943--BOUNDARY
1944Content-Type: text/plain; charset="us-ascii"
1945MIME-Version: 1.0
1946Content-Transfer-Encoding: 7bit
1947
1948One
1949--BOUNDARY
1950Content-Type: text/plain; charset="us-ascii"
1951MIME-Version: 1.0
1952Content-Transfer-Encoding: 7bit
1953
1954Two
1955--BOUNDARY--
1956""")
1957
1958 def test_default_type(self):
1959 eq = self.assertEqual
1960 with openfile('msg_30.txt') as fp:
1961 msg = email.message_from_file(fp)
1962 container1 = msg.get_payload(0)
1963 eq(container1.get_default_type(), 'message/rfc822')
1964 eq(container1.get_content_type(), 'message/rfc822')
1965 container2 = msg.get_payload(1)
1966 eq(container2.get_default_type(), 'message/rfc822')
1967 eq(container2.get_content_type(), 'message/rfc822')
1968 container1a = container1.get_payload(0)
1969 eq(container1a.get_default_type(), 'text/plain')
1970 eq(container1a.get_content_type(), 'text/plain')
1971 container2a = container2.get_payload(0)
1972 eq(container2a.get_default_type(), 'text/plain')
1973 eq(container2a.get_content_type(), 'text/plain')
1974
1975 def test_default_type_with_explicit_container_type(self):
1976 eq = self.assertEqual
1977 with openfile('msg_28.txt') as fp:
1978 msg = email.message_from_file(fp)
1979 container1 = msg.get_payload(0)
1980 eq(container1.get_default_type(), 'message/rfc822')
1981 eq(container1.get_content_type(), 'message/rfc822')
1982 container2 = msg.get_payload(1)
1983 eq(container2.get_default_type(), 'message/rfc822')
1984 eq(container2.get_content_type(), 'message/rfc822')
1985 container1a = container1.get_payload(0)
1986 eq(container1a.get_default_type(), 'text/plain')
1987 eq(container1a.get_content_type(), 'text/plain')
1988 container2a = container2.get_payload(0)
1989 eq(container2a.get_default_type(), 'text/plain')
1990 eq(container2a.get_content_type(), 'text/plain')
1991
1992 def test_default_type_non_parsed(self):
1993 eq = self.assertEqual
1994 neq = self.ndiffAssertEqual
1995 # Set up container
1996 container = MIMEMultipart('digest', 'BOUNDARY')
1997 container.epilogue = ''
1998 # Set up subparts
1999 subpart1a = MIMEText('message 1\n')
2000 subpart2a = MIMEText('message 2\n')
2001 subpart1 = MIMEMessage(subpart1a)
2002 subpart2 = MIMEMessage(subpart2a)
2003 container.attach(subpart1)
2004 container.attach(subpart2)
2005 eq(subpart1.get_content_type(), 'message/rfc822')
2006 eq(subpart1.get_default_type(), 'message/rfc822')
2007 eq(subpart2.get_content_type(), 'message/rfc822')
2008 eq(subpart2.get_default_type(), 'message/rfc822')
2009 neq(container.as_string(0), '''\
2010Content-Type: multipart/digest; boundary="BOUNDARY"
2011MIME-Version: 1.0
2012
2013--BOUNDARY
2014Content-Type: message/rfc822
2015MIME-Version: 1.0
2016
2017Content-Type: text/plain; charset="us-ascii"
2018MIME-Version: 1.0
2019Content-Transfer-Encoding: 7bit
2020
2021message 1
2022
2023--BOUNDARY
2024Content-Type: message/rfc822
2025MIME-Version: 1.0
2026
2027Content-Type: text/plain; charset="us-ascii"
2028MIME-Version: 1.0
2029Content-Transfer-Encoding: 7bit
2030
2031message 2
2032
2033--BOUNDARY--
2034''')
2035 del subpart1['content-type']
2036 del subpart1['mime-version']
2037 del subpart2['content-type']
2038 del subpart2['mime-version']
2039 eq(subpart1.get_content_type(), 'message/rfc822')
2040 eq(subpart1.get_default_type(), 'message/rfc822')
2041 eq(subpart2.get_content_type(), 'message/rfc822')
2042 eq(subpart2.get_default_type(), 'message/rfc822')
2043 neq(container.as_string(0), '''\
2044Content-Type: multipart/digest; boundary="BOUNDARY"
2045MIME-Version: 1.0
2046
2047--BOUNDARY
2048
2049Content-Type: text/plain; charset="us-ascii"
2050MIME-Version: 1.0
2051Content-Transfer-Encoding: 7bit
2052
2053message 1
2054
2055--BOUNDARY
2056
2057Content-Type: text/plain; charset="us-ascii"
2058MIME-Version: 1.0
2059Content-Transfer-Encoding: 7bit
2060
2061message 2
2062
2063--BOUNDARY--
2064''')
2065
2066 def test_mime_attachments_in_constructor(self):
2067 eq = self.assertEqual
2068 text1 = MIMEText('')
2069 text2 = MIMEText('')
2070 msg = MIMEMultipart(_subparts=(text1, text2))
2071 eq(len(msg.get_payload()), 2)
2072 eq(msg.get_payload(0), text1)
2073 eq(msg.get_payload(1), text2)
2074
Christian Heimes587c2bf2008-01-19 16:21:02 +00002075 def test_default_multipart_constructor(self):
2076 msg = MIMEMultipart()
2077 self.assertTrue(msg.is_multipart())
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002078
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002079
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002080# A general test of parser->model->generator idempotency. IOW, read a message
2081# in, parse it into a message object tree, then without touching the tree,
2082# regenerate the plain text. The original text and the transformed text
2083# should be identical. Note: that we ignore the Unix-From since that may
2084# contain a changed date.
2085class TestIdempotent(TestEmailBase):
2086 def _msgobj(self, filename):
2087 with openfile(filename) as fp:
2088 data = fp.read()
2089 msg = email.message_from_string(data)
2090 return msg, data
2091
2092 def _idempotent(self, msg, text):
2093 eq = self.ndiffAssertEqual
2094 s = StringIO()
2095 g = Generator(s, maxheaderlen=0)
2096 g.flatten(msg)
2097 eq(text, s.getvalue())
2098
2099 def test_parse_text_message(self):
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002100 eq = self.assertEqual
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002101 msg, text = self._msgobj('msg_01.txt')
2102 eq(msg.get_content_type(), 'text/plain')
2103 eq(msg.get_content_maintype(), 'text')
2104 eq(msg.get_content_subtype(), 'plain')
2105 eq(msg.get_params()[1], ('charset', 'us-ascii'))
2106 eq(msg.get_param('charset'), 'us-ascii')
2107 eq(msg.preamble, None)
2108 eq(msg.epilogue, None)
2109 self._idempotent(msg, text)
2110
2111 def test_parse_untyped_message(self):
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002112 eq = self.assertEqual
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002113 msg, text = self._msgobj('msg_03.txt')
2114 eq(msg.get_content_type(), 'text/plain')
2115 eq(msg.get_params(), None)
2116 eq(msg.get_param('charset'), None)
2117 self._idempotent(msg, text)
2118
2119 def test_simple_multipart(self):
2120 msg, text = self._msgobj('msg_04.txt')
2121 self._idempotent(msg, text)
2122
2123 def test_MIME_digest(self):
2124 msg, text = self._msgobj('msg_02.txt')
2125 self._idempotent(msg, text)
2126
2127 def test_long_header(self):
2128 msg, text = self._msgobj('msg_27.txt')
2129 self._idempotent(msg, text)
2130
2131 def test_MIME_digest_with_part_headers(self):
2132 msg, text = self._msgobj('msg_28.txt')
2133 self._idempotent(msg, text)
2134
2135 def test_mixed_with_image(self):
2136 msg, text = self._msgobj('msg_06.txt')
2137 self._idempotent(msg, text)
2138
2139 def test_multipart_report(self):
2140 msg, text = self._msgobj('msg_05.txt')
2141 self._idempotent(msg, text)
2142
2143 def test_dsn(self):
2144 msg, text = self._msgobj('msg_16.txt')
2145 self._idempotent(msg, text)
2146
2147 def test_preamble_epilogue(self):
2148 msg, text = self._msgobj('msg_21.txt')
2149 self._idempotent(msg, text)
2150
2151 def test_multipart_one_part(self):
2152 msg, text = self._msgobj('msg_23.txt')
2153 self._idempotent(msg, text)
2154
2155 def test_multipart_no_parts(self):
2156 msg, text = self._msgobj('msg_24.txt')
2157 self._idempotent(msg, text)
2158
2159 def test_no_start_boundary(self):
2160 msg, text = self._msgobj('msg_31.txt')
2161 self._idempotent(msg, text)
2162
2163 def test_rfc2231_charset(self):
2164 msg, text = self._msgobj('msg_32.txt')
2165 self._idempotent(msg, text)
2166
2167 def test_more_rfc2231_parameters(self):
2168 msg, text = self._msgobj('msg_33.txt')
2169 self._idempotent(msg, text)
2170
2171 def test_text_plain_in_a_multipart_digest(self):
2172 msg, text = self._msgobj('msg_34.txt')
2173 self._idempotent(msg, text)
2174
2175 def test_nested_multipart_mixeds(self):
2176 msg, text = self._msgobj('msg_12a.txt')
2177 self._idempotent(msg, text)
2178
2179 def test_message_external_body_idempotent(self):
2180 msg, text = self._msgobj('msg_36.txt')
2181 self._idempotent(msg, text)
2182
2183 def test_content_type(self):
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002184 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00002185 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002186 # Get a message object and reset the seek pointer for other tests
2187 msg, text = self._msgobj('msg_05.txt')
2188 eq(msg.get_content_type(), 'multipart/report')
2189 # Test the Content-Type: parameters
2190 params = {}
2191 for pk, pv in msg.get_params():
2192 params[pk] = pv
2193 eq(params['report-type'], 'delivery-status')
2194 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
2195 eq(msg.preamble, 'This is a MIME-encapsulated message.\n')
2196 eq(msg.epilogue, '\n')
2197 eq(len(msg.get_payload()), 3)
2198 # Make sure the subparts are what we expect
2199 msg1 = msg.get_payload(0)
2200 eq(msg1.get_content_type(), 'text/plain')
2201 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
2202 msg2 = msg.get_payload(1)
2203 eq(msg2.get_content_type(), 'text/plain')
2204 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
2205 msg3 = msg.get_payload(2)
2206 eq(msg3.get_content_type(), 'message/rfc822')
Georg Brandlab91fde2009-08-13 08:51:18 +00002207 self.assertTrue(isinstance(msg3, Message))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002208 payload = msg3.get_payload()
2209 unless(isinstance(payload, list))
2210 eq(len(payload), 1)
2211 msg4 = payload[0]
2212 unless(isinstance(msg4, Message))
2213 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
2214
2215 def test_parser(self):
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002216 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00002217 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002218 msg, text = self._msgobj('msg_06.txt')
2219 # Check some of the outer headers
2220 eq(msg.get_content_type(), 'message/rfc822')
2221 # Make sure the payload is a list of exactly one sub-Message, and that
2222 # that submessage has a type of text/plain
2223 payload = msg.get_payload()
2224 unless(isinstance(payload, list))
2225 eq(len(payload), 1)
2226 msg1 = payload[0]
Georg Brandlab91fde2009-08-13 08:51:18 +00002227 self.assertTrue(isinstance(msg1, Message))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002228 eq(msg1.get_content_type(), 'text/plain')
Georg Brandlab91fde2009-08-13 08:51:18 +00002229 self.assertTrue(isinstance(msg1.get_payload(), str))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002230 eq(msg1.get_payload(), '\n')
2231
2232
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002233
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002234# Test various other bits of the package's functionality
2235class TestMiscellaneous(TestEmailBase):
2236 def test_message_from_string(self):
2237 with openfile('msg_01.txt') as fp:
2238 text = fp.read()
2239 msg = email.message_from_string(text)
2240 s = StringIO()
2241 # Don't wrap/continue long headers since we're trying to test
2242 # idempotency.
2243 g = Generator(s, maxheaderlen=0)
2244 g.flatten(msg)
2245 self.assertEqual(text, s.getvalue())
2246
2247 def test_message_from_file(self):
2248 with openfile('msg_01.txt') as fp:
2249 text = fp.read()
2250 fp.seek(0)
2251 msg = email.message_from_file(fp)
2252 s = StringIO()
2253 # Don't wrap/continue long headers since we're trying to test
2254 # idempotency.
2255 g = Generator(s, maxheaderlen=0)
2256 g.flatten(msg)
2257 self.assertEqual(text, s.getvalue())
2258
2259 def test_message_from_string_with_class(self):
Georg Brandlab91fde2009-08-13 08:51:18 +00002260 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002261 with openfile('msg_01.txt') as fp:
2262 text = fp.read()
2263
2264 # Create a subclass
2265 class MyMessage(Message):
2266 pass
2267
2268 msg = email.message_from_string(text, MyMessage)
2269 unless(isinstance(msg, MyMessage))
2270 # Try something more complicated
2271 with openfile('msg_02.txt') as fp:
2272 text = fp.read()
2273 msg = email.message_from_string(text, MyMessage)
2274 for subpart in msg.walk():
2275 unless(isinstance(subpart, MyMessage))
2276
2277 def test_message_from_file_with_class(self):
Georg Brandlab91fde2009-08-13 08:51:18 +00002278 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002279 # Create a subclass
2280 class MyMessage(Message):
2281 pass
2282
2283 with openfile('msg_01.txt') as fp:
2284 msg = email.message_from_file(fp, MyMessage)
2285 unless(isinstance(msg, MyMessage))
2286 # Try something more complicated
2287 with openfile('msg_02.txt') as fp:
2288 msg = email.message_from_file(fp, MyMessage)
2289 for subpart in msg.walk():
2290 unless(isinstance(subpart, MyMessage))
2291
2292 def test__all__(self):
2293 module = __import__('email')
2294 # Can't use sorted() here due to Python 2.3 compatibility
2295 all = module.__all__[:]
2296 all.sort()
2297 self.assertEqual(all, [
2298 'base64mime', 'charset', 'encoders', 'errors', 'generator',
2299 'header', 'iterators', 'message', 'message_from_file',
2300 'message_from_string', 'mime', 'parser',
2301 'quoprimime', 'utils',
2302 ])
2303
2304 def test_formatdate(self):
2305 now = time.time()
2306 self.assertEqual(utils.parsedate(utils.formatdate(now))[:6],
2307 time.gmtime(now)[:6])
2308
2309 def test_formatdate_localtime(self):
2310 now = time.time()
2311 self.assertEqual(
2312 utils.parsedate(utils.formatdate(now, localtime=True))[:6],
2313 time.localtime(now)[:6])
2314
2315 def test_formatdate_usegmt(self):
2316 now = time.time()
2317 self.assertEqual(
2318 utils.formatdate(now, localtime=False),
2319 time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
2320 self.assertEqual(
2321 utils.formatdate(now, localtime=False, usegmt=True),
2322 time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
2323
2324 def test_parsedate_none(self):
2325 self.assertEqual(utils.parsedate(''), None)
2326
2327 def test_parsedate_compact(self):
2328 # The FWS after the comma is optional
2329 self.assertEqual(utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2330 utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2331
2332 def test_parsedate_no_dayofweek(self):
2333 eq = self.assertEqual
2334 eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2335 (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
2336
2337 def test_parsedate_compact_no_dayofweek(self):
2338 eq = self.assertEqual
2339 eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2340 (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
2341
2342 def test_parsedate_acceptable_to_time_functions(self):
2343 eq = self.assertEqual
2344 timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
2345 t = int(time.mktime(timetup))
2346 eq(time.localtime(t)[:6], timetup[:6])
2347 eq(int(time.strftime('%Y', timetup)), 2003)
2348 timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
2349 t = int(time.mktime(timetup[:9]))
2350 eq(time.localtime(t)[:6], timetup[:6])
2351 eq(int(time.strftime('%Y', timetup[:9])), 2003)
2352
R. David Murray1061f182010-08-25 01:55:24 +00002353 def test_parsedate_y2k(self):
2354 """Test for parsing a date with a two-digit year.
2355
2356 Parsing a date with a two-digit year should return the correct
2357 four-digit year. RFC822 allows two-digit years, but RFC2822 (which
2358 obsoletes RFC822) requires four-digit years.
2359
2360 """
2361 self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'),
2362 utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'))
2363 self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'),
2364 utils.parsedate_tz('25 Feb 1971 13:47:26 -0800'))
2365
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002366 def test_parseaddr_empty(self):
2367 self.assertEqual(utils.parseaddr('<>'), ('', ''))
2368 self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '')
2369
2370 def test_noquote_dump(self):
2371 self.assertEqual(
2372 utils.formataddr(('A Silly Person', 'person@dom.ain')),
2373 'A Silly Person <person@dom.ain>')
2374
2375 def test_escape_dump(self):
2376 self.assertEqual(
2377 utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
2378 r'"A \(Very\) Silly Person" <person@dom.ain>')
2379 a = r'A \(Special\) Person'
2380 b = 'person@dom.ain'
2381 self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
2382
2383 def test_escape_backslashes(self):
2384 self.assertEqual(
2385 utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
2386 r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
2387 a = r'Arthur \Backslash\ Foobar'
2388 b = 'person@dom.ain'
2389 self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
2390
2391 def test_name_with_dot(self):
2392 x = 'John X. Doe <jxd@example.com>'
2393 y = '"John X. Doe" <jxd@example.com>'
2394 a, b = ('John X. Doe', 'jxd@example.com')
2395 self.assertEqual(utils.parseaddr(x), (a, b))
2396 self.assertEqual(utils.parseaddr(y), (a, b))
2397 # formataddr() quotes the name if there's a dot in it
2398 self.assertEqual(utils.formataddr((a, b)), y)
2399
R. David Murray7f8199a2010-10-02 16:04:44 +00002400 def test_parseaddr_preserves_quoted_pairs_in_addresses(self):
2401 # issue 10005. Note that in the third test the second pair of
2402 # backslashes is not actually a quoted pair because it is not inside a
2403 # comment or quoted string: the address being parsed has a quoted
2404 # string containing a quoted backslash, followed by 'example' and two
2405 # backslashes, followed by another quoted string containing a space and
2406 # the word 'example'. parseaddr copies those two backslashes
2407 # literally. Per rfc5322 this is not technically correct since a \ may
2408 # not appear in an address outside of a quoted string. It is probably
2409 # a sensible Postel interpretation, though.
2410 eq = self.assertEqual
2411 eq(utils.parseaddr('""example" example"@example.com'),
2412 ('', '""example" example"@example.com'))
2413 eq(utils.parseaddr('"\\"example\\" example"@example.com'),
2414 ('', '"\\"example\\" example"@example.com'))
2415 eq(utils.parseaddr('"\\\\"example\\\\" example"@example.com'),
2416 ('', '"\\\\"example\\\\" example"@example.com'))
2417
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002418 def test_multiline_from_comment(self):
2419 x = """\
2420Foo
2421\tBar <foo@example.com>"""
2422 self.assertEqual(utils.parseaddr(x), ('Foo Bar', 'foo@example.com'))
2423
2424 def test_quote_dump(self):
2425 self.assertEqual(
2426 utils.formataddr(('A Silly; Person', 'person@dom.ain')),
2427 r'"A Silly; Person" <person@dom.ain>')
2428
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002429 def test_charset_richcomparisons(self):
2430 eq = self.assertEqual
Georg Brandlab91fde2009-08-13 08:51:18 +00002431 ne = self.assertNotEqual
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002432 cset1 = Charset()
2433 cset2 = Charset()
2434 eq(cset1, 'us-ascii')
2435 eq(cset1, 'US-ASCII')
2436 eq(cset1, 'Us-AsCiI')
2437 eq('us-ascii', cset1)
2438 eq('US-ASCII', cset1)
2439 eq('Us-AsCiI', cset1)
2440 ne(cset1, 'usascii')
2441 ne(cset1, 'USASCII')
2442 ne(cset1, 'UsAsCiI')
2443 ne('usascii', cset1)
2444 ne('USASCII', cset1)
2445 ne('UsAsCiI', cset1)
2446 eq(cset1, cset2)
2447 eq(cset2, cset1)
2448
2449 def test_getaddresses(self):
2450 eq = self.assertEqual
2451 eq(utils.getaddresses(['aperson@dom.ain (Al Person)',
2452 'Bud Person <bperson@dom.ain>']),
2453 [('Al Person', 'aperson@dom.ain'),
2454 ('Bud Person', 'bperson@dom.ain')])
2455
2456 def test_getaddresses_nasty(self):
2457 eq = self.assertEqual
2458 eq(utils.getaddresses(['foo: ;']), [('', '')])
2459 eq(utils.getaddresses(
2460 ['[]*-- =~$']),
2461 [('', ''), ('', ''), ('', '*--')])
2462 eq(utils.getaddresses(
2463 ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
2464 [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
2465
2466 def test_getaddresses_embedded_comment(self):
2467 """Test proper handling of a nested comment"""
2468 eq = self.assertEqual
2469 addrs = utils.getaddresses(['User ((nested comment)) <foo@bar.com>'])
2470 eq(addrs[0][1], 'foo@bar.com')
2471
2472 def test_utils_quote_unquote(self):
2473 eq = self.assertEqual
2474 msg = Message()
2475 msg.add_header('content-disposition', 'attachment',
2476 filename='foo\\wacky"name')
2477 eq(msg.get_filename(), 'foo\\wacky"name')
2478
2479 def test_get_body_encoding_with_bogus_charset(self):
2480 charset = Charset('not a charset')
2481 self.assertEqual(charset.get_body_encoding(), 'base64')
2482
2483 def test_get_body_encoding_with_uppercase_charset(self):
2484 eq = self.assertEqual
2485 msg = Message()
2486 msg['Content-Type'] = 'text/plain; charset=UTF-8'
2487 eq(msg['content-type'], 'text/plain; charset=UTF-8')
2488 charsets = msg.get_charsets()
2489 eq(len(charsets), 1)
2490 eq(charsets[0], 'utf-8')
2491 charset = Charset(charsets[0])
2492 eq(charset.get_body_encoding(), 'base64')
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002493 msg.set_payload(b'hello world', charset=charset)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002494 eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n')
2495 eq(msg.get_payload(decode=True), b'hello world')
2496 eq(msg['content-transfer-encoding'], 'base64')
2497 # Try another one
2498 msg = Message()
2499 msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
2500 charsets = msg.get_charsets()
2501 eq(len(charsets), 1)
2502 eq(charsets[0], 'us-ascii')
2503 charset = Charset(charsets[0])
2504 eq(charset.get_body_encoding(), encoders.encode_7or8bit)
2505 msg.set_payload('hello world', charset=charset)
2506 eq(msg.get_payload(), 'hello world')
2507 eq(msg['content-transfer-encoding'], '7bit')
2508
2509 def test_charsets_case_insensitive(self):
2510 lc = Charset('us-ascii')
2511 uc = Charset('US-ASCII')
2512 self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
2513
2514 def test_partial_falls_inside_message_delivery_status(self):
2515 eq = self.ndiffAssertEqual
2516 # The Parser interface provides chunks of data to FeedParser in 8192
2517 # byte gulps. SF bug #1076485 found one of those chunks inside
2518 # message/delivery-status header block, which triggered an
2519 # unreadline() of NeedMoreData.
2520 msg = self._msgobj('msg_43.txt')
2521 sfp = StringIO()
2522 iterators._structure(msg, sfp)
2523 eq(sfp.getvalue(), """\
2524multipart/report
2525 text/plain
2526 message/delivery-status
2527 text/plain
2528 text/plain
2529 text/plain
2530 text/plain
2531 text/plain
2532 text/plain
2533 text/plain
2534 text/plain
2535 text/plain
2536 text/plain
2537 text/plain
2538 text/plain
2539 text/plain
2540 text/plain
2541 text/plain
2542 text/plain
2543 text/plain
2544 text/plain
2545 text/plain
2546 text/plain
2547 text/plain
2548 text/plain
2549 text/plain
2550 text/plain
2551 text/plain
2552 text/plain
2553 text/rfc822-headers
2554""")
2555
2556
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002557
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002558# Test the iterator/generators
2559class TestIterators(TestEmailBase):
2560 def test_body_line_iterator(self):
2561 eq = self.assertEqual
2562 neq = self.ndiffAssertEqual
2563 # First a simple non-multipart message
2564 msg = self._msgobj('msg_01.txt')
2565 it = iterators.body_line_iterator(msg)
2566 lines = list(it)
2567 eq(len(lines), 6)
2568 neq(EMPTYSTRING.join(lines), msg.get_payload())
2569 # Now a more complicated multipart
2570 msg = self._msgobj('msg_02.txt')
2571 it = iterators.body_line_iterator(msg)
2572 lines = list(it)
2573 eq(len(lines), 43)
2574 with openfile('msg_19.txt') as fp:
2575 neq(EMPTYSTRING.join(lines), fp.read())
2576
2577 def test_typed_subpart_iterator(self):
2578 eq = self.assertEqual
2579 msg = self._msgobj('msg_04.txt')
2580 it = iterators.typed_subpart_iterator(msg, 'text')
2581 lines = []
2582 subparts = 0
2583 for subpart in it:
2584 subparts += 1
2585 lines.append(subpart.get_payload())
2586 eq(subparts, 2)
2587 eq(EMPTYSTRING.join(lines), """\
2588a simple kind of mirror
2589to reflect upon our own
2590a simple kind of mirror
2591to reflect upon our own
2592""")
2593
2594 def test_typed_subpart_iterator_default_type(self):
2595 eq = self.assertEqual
2596 msg = self._msgobj('msg_03.txt')
2597 it = iterators.typed_subpart_iterator(msg, 'text', 'plain')
2598 lines = []
2599 subparts = 0
2600 for subpart in it:
2601 subparts += 1
2602 lines.append(subpart.get_payload())
2603 eq(subparts, 1)
2604 eq(EMPTYSTRING.join(lines), """\
2605
2606Hi,
2607
2608Do you like this message?
2609
2610-Me
2611""")
2612
R. David Murray6d4a06c2010-07-17 01:28:04 +00002613 def test_pushCR_LF(self):
2614 '''FeedParser BufferedSubFile.push() assumed it received complete
2615 line endings. A CR ending one push() followed by a LF starting
2616 the next push() added an empty line.
2617 '''
2618 imt = [
2619 ("a\r \n", 2),
2620 ("b", 0),
2621 ("c\n", 1),
2622 ("", 0),
2623 ("d\r\n", 1),
2624 ("e\r", 0),
2625 ("\nf", 1),
2626 ("\r\n", 1),
2627 ]
2628 from email.feedparser import BufferedSubFile, NeedMoreData
2629 bsf = BufferedSubFile()
2630 om = []
2631 nt = 0
2632 for il, n in imt:
2633 bsf.push(il)
2634 nt += n
2635 n1 = 0
2636 while True:
2637 ol = bsf.readline()
2638 if ol == NeedMoreData:
2639 break
2640 om.append(ol)
2641 n1 += 1
2642 self.assertTrue(n == n1)
2643 self.assertTrue(len(om) == nt)
2644 self.assertTrue(''.join([il for il, n in imt]) == ''.join(om))
2645
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002646
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002647
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002648class TestParsers(TestEmailBase):
2649 def test_header_parser(self):
2650 eq = self.assertEqual
2651 # Parse only the headers of a complex multipart MIME document
2652 with openfile('msg_02.txt') as fp:
2653 msg = HeaderParser().parse(fp)
2654 eq(msg['from'], 'ppp-request@zzz.org')
2655 eq(msg['to'], 'ppp@zzz.org')
2656 eq(msg.get_content_type(), 'multipart/mixed')
Georg Brandlab91fde2009-08-13 08:51:18 +00002657 self.assertFalse(msg.is_multipart())
2658 self.assertTrue(isinstance(msg.get_payload(), str))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002659
2660 def test_whitespace_continuation(self):
2661 eq = self.assertEqual
2662 # This message contains a line after the Subject: header that has only
2663 # whitespace, but it is not empty!
2664 msg = email.message_from_string("""\
2665From: aperson@dom.ain
2666To: bperson@dom.ain
2667Subject: the next line has a space on it
2668\x20
2669Date: Mon, 8 Apr 2002 15:09:19 -0400
2670Message-ID: spam
2671
2672Here's the message body
2673""")
2674 eq(msg['subject'], 'the next line has a space on it\n ')
2675 eq(msg['message-id'], 'spam')
2676 eq(msg.get_payload(), "Here's the message body\n")
2677
2678 def test_whitespace_continuation_last_header(self):
2679 eq = self.assertEqual
2680 # Like the previous test, but the subject line is the last
2681 # header.
2682 msg = email.message_from_string("""\
2683From: aperson@dom.ain
2684To: bperson@dom.ain
2685Date: Mon, 8 Apr 2002 15:09:19 -0400
2686Message-ID: spam
2687Subject: the next line has a space on it
2688\x20
2689
2690Here's the message body
2691""")
2692 eq(msg['subject'], 'the next line has a space on it\n ')
2693 eq(msg['message-id'], 'spam')
2694 eq(msg.get_payload(), "Here's the message body\n")
2695
2696 def test_crlf_separation(self):
2697 eq = self.assertEqual
Guido van Rossum98297ee2007-11-06 21:34:58 +00002698 with openfile('msg_26.txt', newline='\n') as fp:
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002699 msg = Parser().parse(fp)
2700 eq(len(msg.get_payload()), 2)
2701 part1 = msg.get_payload(0)
2702 eq(part1.get_content_type(), 'text/plain')
2703 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
2704 part2 = msg.get_payload(1)
2705 eq(part2.get_content_type(), 'application/riscos')
2706
2707 def test_multipart_digest_with_extra_mime_headers(self):
2708 eq = self.assertEqual
2709 neq = self.ndiffAssertEqual
2710 with openfile('msg_28.txt') as fp:
2711 msg = email.message_from_file(fp)
2712 # Structure is:
2713 # multipart/digest
2714 # message/rfc822
2715 # text/plain
2716 # message/rfc822
2717 # text/plain
2718 eq(msg.is_multipart(), 1)
2719 eq(len(msg.get_payload()), 2)
2720 part1 = msg.get_payload(0)
2721 eq(part1.get_content_type(), 'message/rfc822')
2722 eq(part1.is_multipart(), 1)
2723 eq(len(part1.get_payload()), 1)
2724 part1a = part1.get_payload(0)
2725 eq(part1a.is_multipart(), 0)
2726 eq(part1a.get_content_type(), 'text/plain')
2727 neq(part1a.get_payload(), 'message 1\n')
2728 # next message/rfc822
2729 part2 = msg.get_payload(1)
2730 eq(part2.get_content_type(), 'message/rfc822')
2731 eq(part2.is_multipart(), 1)
2732 eq(len(part2.get_payload()), 1)
2733 part2a = part2.get_payload(0)
2734 eq(part2a.is_multipart(), 0)
2735 eq(part2a.get_content_type(), 'text/plain')
2736 neq(part2a.get_payload(), 'message 2\n')
2737
2738 def test_three_lines(self):
2739 # A bug report by Andrew McNamara
2740 lines = ['From: Andrew Person <aperson@dom.ain',
2741 'Subject: Test',
2742 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2743 msg = email.message_from_string(NL.join(lines))
2744 self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2745
2746 def test_strip_line_feed_and_carriage_return_in_headers(self):
2747 eq = self.assertEqual
2748 # For [ 1002475 ] email message parser doesn't handle \r\n correctly
2749 value1 = 'text'
2750 value2 = 'more text'
2751 m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
2752 value1, value2)
2753 msg = email.message_from_string(m)
2754 eq(msg.get('Header'), value1)
2755 eq(msg.get('Next-Header'), value2)
2756
2757 def test_rfc2822_header_syntax(self):
2758 eq = self.assertEqual
2759 m = '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2760 msg = email.message_from_string(m)
2761 eq(len(msg), 3)
2762 eq(sorted(field for field in msg), ['!"#QUX;~', '>From', 'From'])
2763 eq(msg.get_payload(), 'body')
2764
2765 def test_rfc2822_space_not_allowed_in_header(self):
2766 eq = self.assertEqual
2767 m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2768 msg = email.message_from_string(m)
2769 eq(len(msg.keys()), 0)
2770
2771 def test_rfc2822_one_character_header(self):
2772 eq = self.assertEqual
2773 m = 'A: first header\nB: second header\nCC: third header\n\nbody'
2774 msg = email.message_from_string(m)
2775 headers = msg.keys()
2776 headers.sort()
2777 eq(headers, ['A', 'B', 'CC'])
2778 eq(msg.get_payload(), 'body')
2779
R. David Murray71df9d92010-06-16 02:22:56 +00002780 def test_CRLFLF_at_end_of_part(self):
2781 # issue 5610: feedparser should not eat two chars from body part ending
2782 # with "\r\n\n".
2783 m = (
2784 "From: foo@bar.com\n"
2785 "To: baz\n"
2786 "Mime-Version: 1.0\n"
2787 "Content-Type: multipart/mixed; boundary=BOUNDARY\n"
2788 "\n"
2789 "--BOUNDARY\n"
2790 "Content-Type: text/plain\n"
2791 "\n"
2792 "body ending with CRLF newline\r\n"
2793 "\n"
2794 "--BOUNDARY--\n"
2795 )
2796 msg = email.message_from_string(m)
2797 self.assertTrue(msg.get_payload(0).get_payload().endswith('\r\n'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002798
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002799
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002800class TestBase64(unittest.TestCase):
2801 def test_len(self):
2802 eq = self.assertEqual
Guido van Rossum9604e662007-08-30 03:46:43 +00002803 eq(base64mime.header_length('hello'),
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002804 len(base64mime.body_encode(b'hello', eol='')))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002805 for size in range(15):
2806 if size == 0 : bsize = 0
2807 elif size <= 3 : bsize = 4
2808 elif size <= 6 : bsize = 8
2809 elif size <= 9 : bsize = 12
2810 elif size <= 12: bsize = 16
2811 else : bsize = 20
Guido van Rossum9604e662007-08-30 03:46:43 +00002812 eq(base64mime.header_length('x' * size), bsize)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002813
2814 def test_decode(self):
2815 eq = self.assertEqual
Barry Warsaw2cc1f6d2007-08-30 14:28:55 +00002816 eq(base64mime.decode(''), b'')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002817 eq(base64mime.decode('aGVsbG8='), b'hello')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002818
2819 def test_encode(self):
2820 eq = self.assertEqual
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002821 eq(base64mime.body_encode(b''), b'')
2822 eq(base64mime.body_encode(b'hello'), 'aGVsbG8=\n')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002823 # Test the binary flag
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002824 eq(base64mime.body_encode(b'hello\n'), 'aGVsbG8K\n')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002825 # Test the maxlinelen arg
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002826 eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40), """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002827eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2828eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2829eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2830eHh4eCB4eHh4IA==
2831""")
2832 # Test the eol argument
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002833 eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40, eol='\r\n'),
Barry Warsaw7aa02e62007-08-31 03:26:19 +00002834 """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002835eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2836eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2837eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2838eHh4eCB4eHh4IA==\r
2839""")
2840
2841 def test_header_encode(self):
2842 eq = self.assertEqual
2843 he = base64mime.header_encode
2844 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
Guido van Rossum9604e662007-08-30 03:46:43 +00002845 eq(he('hello\r\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
2846 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002847 # Test the charset option
2848 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
2849 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002850
2851
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00002852
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002853class TestQuopri(unittest.TestCase):
2854 def setUp(self):
2855 # Set of characters (as byte integers) that don't need to be encoded
2856 # in headers.
2857 self.hlit = list(chain(
2858 range(ord('a'), ord('z') + 1),
2859 range(ord('A'), ord('Z') + 1),
2860 range(ord('0'), ord('9') + 1),
Guido van Rossum9604e662007-08-30 03:46:43 +00002861 (c for c in b'!*+-/')))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002862 # Set of characters (as byte integers) that do need to be encoded in
2863 # headers.
2864 self.hnon = [c for c in range(256) if c not in self.hlit]
2865 assert len(self.hlit) + len(self.hnon) == 256
2866 # Set of characters (as byte integers) that don't need to be encoded
2867 # in bodies.
2868 self.blit = list(range(ord(' '), ord('~') + 1))
2869 self.blit.append(ord('\t'))
2870 self.blit.remove(ord('='))
2871 # Set of characters (as byte integers) that do need to be encoded in
2872 # bodies.
2873 self.bnon = [c for c in range(256) if c not in self.blit]
2874 assert len(self.blit) + len(self.bnon) == 256
2875
Guido van Rossum9604e662007-08-30 03:46:43 +00002876 def test_quopri_header_check(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002877 for c in self.hlit:
Georg Brandlab91fde2009-08-13 08:51:18 +00002878 self.assertFalse(quoprimime.header_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002879 'Should not be header quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002880 for c in self.hnon:
Georg Brandlab91fde2009-08-13 08:51:18 +00002881 self.assertTrue(quoprimime.header_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002882 'Should be header quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002883
Guido van Rossum9604e662007-08-30 03:46:43 +00002884 def test_quopri_body_check(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002885 for c in self.blit:
Georg Brandlab91fde2009-08-13 08:51:18 +00002886 self.assertFalse(quoprimime.body_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002887 'Should not be body quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002888 for c in self.bnon:
Georg Brandlab91fde2009-08-13 08:51:18 +00002889 self.assertTrue(quoprimime.body_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002890 'Should be body quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002891
2892 def test_header_quopri_len(self):
2893 eq = self.assertEqual
Guido van Rossum9604e662007-08-30 03:46:43 +00002894 eq(quoprimime.header_length(b'hello'), 5)
2895 # RFC 2047 chrome is not included in header_length().
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002896 eq(len(quoprimime.header_encode(b'hello', charset='xxx')),
Guido van Rossum9604e662007-08-30 03:46:43 +00002897 quoprimime.header_length(b'hello') +
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002898 # =?xxx?q?...?= means 10 extra characters
2899 10)
Guido van Rossum9604e662007-08-30 03:46:43 +00002900 eq(quoprimime.header_length(b'h@e@l@l@o@'), 20)
2901 # RFC 2047 chrome is not included in header_length().
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002902 eq(len(quoprimime.header_encode(b'h@e@l@l@o@', charset='xxx')),
Guido van Rossum9604e662007-08-30 03:46:43 +00002903 quoprimime.header_length(b'h@e@l@l@o@') +
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002904 # =?xxx?q?...?= means 10 extra characters
2905 10)
2906 for c in self.hlit:
Guido van Rossum9604e662007-08-30 03:46:43 +00002907 eq(quoprimime.header_length(bytes([c])), 1,
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002908 'expected length 1 for %r' % chr(c))
2909 for c in self.hnon:
Guido van Rossum9604e662007-08-30 03:46:43 +00002910 # Space is special; it's encoded to _
2911 if c == ord(' '):
2912 continue
2913 eq(quoprimime.header_length(bytes([c])), 3,
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002914 'expected length 3 for %r' % chr(c))
Guido van Rossum9604e662007-08-30 03:46:43 +00002915 eq(quoprimime.header_length(b' '), 1)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002916
2917 def test_body_quopri_len(self):
2918 eq = self.assertEqual
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002919 for c in self.blit:
Guido van Rossum9604e662007-08-30 03:46:43 +00002920 eq(quoprimime.body_length(bytes([c])), 1)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002921 for c in self.bnon:
Guido van Rossum9604e662007-08-30 03:46:43 +00002922 eq(quoprimime.body_length(bytes([c])), 3)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002923
2924 def test_quote_unquote_idempotent(self):
2925 for x in range(256):
2926 c = chr(x)
2927 self.assertEqual(quoprimime.unquote(quoprimime.quote(c)), c)
2928
R David Murrayec1b5b82011-03-23 14:19:05 -04002929 def _test_header_encode(self, header, expected_encoded_header, charset=None):
2930 if charset is None:
2931 encoded_header = quoprimime.header_encode(header)
2932 else:
2933 encoded_header = quoprimime.header_encode(header, charset)
2934 self.assertEqual(encoded_header, expected_encoded_header)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002935
R David Murraycafd79d2011-03-23 15:25:55 -04002936 def test_header_encode_null(self):
2937 self._test_header_encode(b'', '')
2938
R David Murrayec1b5b82011-03-23 14:19:05 -04002939 def test_header_encode_one_word(self):
2940 self._test_header_encode(b'hello', '=?iso-8859-1?q?hello?=')
2941
2942 def test_header_encode_two_lines(self):
2943 self._test_header_encode(b'hello\nworld',
2944 '=?iso-8859-1?q?hello=0Aworld?=')
2945
2946 def test_header_encode_non_ascii(self):
2947 self._test_header_encode(b'hello\xc7there',
2948 '=?iso-8859-1?q?hello=C7there?=')
2949
2950 def test_header_encode_alt_charset(self):
2951 self._test_header_encode(b'hello', '=?iso-8859-2?q?hello?=',
2952 charset='iso-8859-2')
2953
2954 def _test_header_decode(self, encoded_header, expected_decoded_header):
2955 decoded_header = quoprimime.header_decode(encoded_header)
2956 self.assertEqual(decoded_header, expected_decoded_header)
2957
2958 def test_header_decode_null(self):
2959 self._test_header_decode('', '')
2960
2961 def test_header_decode_one_word(self):
2962 self._test_header_decode('hello', 'hello')
2963
2964 def test_header_decode_two_lines(self):
2965 self._test_header_decode('hello=0Aworld', 'hello\nworld')
2966
2967 def test_header_decode_non_ascii(self):
2968 self._test_header_decode('hello=C7there', 'hello\xc7there')
2969
2970 def _test_decode(self, encoded, expected_decoded, eol=None):
2971 if eol is None:
2972 decoded = quoprimime.decode(encoded)
2973 else:
2974 decoded = quoprimime.decode(encoded, eol=eol)
2975 self.assertEqual(decoded, expected_decoded)
2976
2977 def test_decode_null_word(self):
2978 self._test_decode('', '')
2979
2980 def test_decode_null_line_null_word(self):
2981 self._test_decode('\r\n', '\n')
2982
2983 def test_decode_one_word(self):
2984 self._test_decode('hello', 'hello')
2985
2986 def test_decode_one_word_eol(self):
2987 self._test_decode('hello', 'hello', eol='X')
2988
2989 def test_decode_one_line(self):
2990 self._test_decode('hello\r\n', 'hello\n')
2991
2992 def test_decode_one_line_lf(self):
2993 self._test_decode('hello\n', 'hello\n')
2994
R David Murraycafd79d2011-03-23 15:25:55 -04002995 def test_decode_one_line_cr(self):
2996 self._test_decode('hello\r', 'hello\n')
2997
2998 def test_decode_one_line_nl(self):
2999 self._test_decode('hello\n', 'helloX', eol='X')
3000
3001 def test_decode_one_line_crnl(self):
3002 self._test_decode('hello\r\n', 'helloX', eol='X')
3003
R David Murrayec1b5b82011-03-23 14:19:05 -04003004 def test_decode_one_line_one_word(self):
3005 self._test_decode('hello\r\nworld', 'hello\nworld')
3006
3007 def test_decode_one_line_one_word_eol(self):
3008 self._test_decode('hello\r\nworld', 'helloXworld', eol='X')
3009
3010 def test_decode_two_lines(self):
3011 self._test_decode('hello\r\nworld\r\n', 'hello\nworld\n')
3012
R David Murraycafd79d2011-03-23 15:25:55 -04003013 def test_decode_two_lines_eol(self):
3014 self._test_decode('hello\r\nworld\r\n', 'helloXworldX', eol='X')
3015
R David Murrayec1b5b82011-03-23 14:19:05 -04003016 def test_decode_one_long_line(self):
3017 self._test_decode('Spam' * 250, 'Spam' * 250)
3018
3019 def test_decode_one_space(self):
3020 self._test_decode(' ', '')
3021
3022 def test_decode_multiple_spaces(self):
3023 self._test_decode(' ' * 5, '')
3024
3025 def test_decode_one_line_trailing_spaces(self):
3026 self._test_decode('hello \r\n', 'hello\n')
3027
3028 def test_decode_two_lines_trailing_spaces(self):
3029 self._test_decode('hello \r\nworld \r\n', 'hello\nworld\n')
3030
3031 def test_decode_quoted_word(self):
3032 self._test_decode('=22quoted=20words=22', '"quoted words"')
3033
3034 def test_decode_uppercase_quoting(self):
3035 self._test_decode('ab=CD=EF', 'ab\xcd\xef')
3036
3037 def test_decode_lowercase_quoting(self):
3038 self._test_decode('ab=cd=ef', 'ab\xcd\xef')
3039
3040 def test_decode_soft_line_break(self):
3041 self._test_decode('soft line=\r\nbreak', 'soft linebreak')
3042
3043 def test_decode_false_quoting(self):
3044 self._test_decode('A=1,B=A ==> A+B==2', 'A=1,B=A ==> A+B==2')
3045
3046 def _test_encode(self, body, expected_encoded_body, maxlinelen=None, eol=None):
3047 kwargs = {}
3048 if maxlinelen is None:
3049 # Use body_encode's default.
3050 maxlinelen = 76
3051 else:
3052 kwargs['maxlinelen'] = maxlinelen
3053 if eol is None:
3054 # Use body_encode's default.
3055 eol = '\n'
3056 else:
3057 kwargs['eol'] = eol
3058 encoded_body = quoprimime.body_encode(body, **kwargs)
3059 self.assertEqual(encoded_body, expected_encoded_body)
3060 if eol == '\n' or eol == '\r\n':
3061 # We know how to split the result back into lines, so maxlinelen
3062 # can be checked.
3063 for line in encoded_body.splitlines():
3064 self.assertLessEqual(len(line), maxlinelen)
3065
3066 def test_encode_null(self):
3067 self._test_encode('', '')
3068
3069 def test_encode_null_lines(self):
3070 self._test_encode('\n\n', '\n\n')
3071
3072 def test_encode_one_line(self):
3073 self._test_encode('hello\n', 'hello\n')
3074
3075 def test_encode_one_line_crlf(self):
3076 self._test_encode('hello\r\n', 'hello\n')
3077
3078 def test_encode_one_line_eol(self):
3079 self._test_encode('hello\n', 'hello\r\n', eol='\r\n')
3080
3081 def test_encode_one_space(self):
3082 self._test_encode(' ', '=20')
3083
3084 def test_encode_one_line_one_space(self):
3085 self._test_encode(' \n', '=20\n')
3086
R David Murrayb938c8c2011-03-24 12:19:26 -04003087# XXX: body_encode() expect strings, but uses ord(char) from these strings
3088# to index into a 256-entry list. For code points above 255, this will fail.
3089# Should there be a check for 8-bit only ord() values in body, or at least
3090# a comment about the expected input?
3091
3092 def test_encode_two_lines_one_space(self):
3093 self._test_encode(' \n \n', '=20\n=20\n')
3094
R David Murrayec1b5b82011-03-23 14:19:05 -04003095 def test_encode_one_word_trailing_spaces(self):
3096 self._test_encode('hello ', 'hello =20')
3097
3098 def test_encode_one_line_trailing_spaces(self):
3099 self._test_encode('hello \n', 'hello =20\n')
3100
3101 def test_encode_one_word_trailing_tab(self):
3102 self._test_encode('hello \t', 'hello =09')
3103
3104 def test_encode_one_line_trailing_tab(self):
3105 self._test_encode('hello \t\n', 'hello =09\n')
3106
3107 def test_encode_trailing_space_before_maxlinelen(self):
3108 self._test_encode('abcd \n1234', 'abcd =\n\n1234', maxlinelen=6)
3109
R David Murrayb938c8c2011-03-24 12:19:26 -04003110 def test_encode_trailing_space_at_maxlinelen(self):
3111 self._test_encode('abcd \n1234', 'abcd=\n=20\n1234', maxlinelen=5)
3112
R David Murrayec1b5b82011-03-23 14:19:05 -04003113 def test_encode_trailing_space_beyond_maxlinelen(self):
R David Murrayb938c8c2011-03-24 12:19:26 -04003114 self._test_encode('abcd \n1234', 'abc=\nd=20\n1234', maxlinelen=4)
3115
3116 def test_encode_whitespace_lines(self):
3117 self._test_encode(' \n' * 5, '=20\n' * 5)
R David Murrayec1b5b82011-03-23 14:19:05 -04003118
3119 def test_encode_quoted_equals(self):
3120 self._test_encode('a = b', 'a =3D b')
3121
3122 def test_encode_one_long_string(self):
3123 self._test_encode('x' * 100, 'x' * 75 + '=\n' + 'x' * 25)
3124
3125 def test_encode_one_long_line(self):
3126 self._test_encode('x' * 100 + '\n', 'x' * 75 + '=\n' + 'x' * 25 + '\n')
3127
3128 def test_encode_one_very_long_line(self):
3129 self._test_encode('x' * 200 + '\n',
3130 2 * ('x' * 75 + '=\n') + 'x' * 50 + '\n')
3131
3132 def test_encode_one_long_line(self):
3133 self._test_encode('x' * 100 + '\n', 'x' * 75 + '=\n' + 'x' * 25 + '\n')
3134
3135 def test_encode_shortest_maxlinelen(self):
3136 self._test_encode('=' * 5, '=3D=\n' * 4 + '=3D', maxlinelen=4)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003137
R David Murrayb938c8c2011-03-24 12:19:26 -04003138 def test_encode_maxlinelen_too_small(self):
3139 self.assertRaises(ValueError, self._test_encode, '', '', maxlinelen=3)
3140
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003141 def test_encode(self):
3142 eq = self.assertEqual
Guido van Rossum9604e662007-08-30 03:46:43 +00003143 eq(quoprimime.body_encode(''), '')
3144 eq(quoprimime.body_encode('hello'), 'hello')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003145 # Test the binary flag
Guido van Rossum9604e662007-08-30 03:46:43 +00003146 eq(quoprimime.body_encode('hello\r\nworld'), 'hello\nworld')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003147 # Test the maxlinelen arg
Guido van Rossum9604e662007-08-30 03:46:43 +00003148 eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40), """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003149xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
3150 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
3151x xxxx xxxx xxxx xxxx=20""")
3152 # Test the eol argument
Guido van Rossum9604e662007-08-30 03:46:43 +00003153 eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'),
3154 """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003155xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
3156 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
3157x xxxx xxxx xxxx xxxx=20""")
Guido van Rossum9604e662007-08-30 03:46:43 +00003158 eq(quoprimime.body_encode("""\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003159one line
3160
3161two line"""), """\
3162one line
3163
3164two line""")
3165
3166
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00003167
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003168# Test the Charset class
3169class TestCharset(unittest.TestCase):
3170 def tearDown(self):
3171 from email import charset as CharsetModule
3172 try:
3173 del CharsetModule.CHARSETS['fake']
3174 except KeyError:
3175 pass
3176
Guido van Rossum9604e662007-08-30 03:46:43 +00003177 def test_codec_encodeable(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003178 eq = self.assertEqual
3179 # Make sure us-ascii = no Unicode conversion
3180 c = Charset('us-ascii')
Guido van Rossum9604e662007-08-30 03:46:43 +00003181 eq(c.header_encode('Hello World!'), 'Hello World!')
3182 # Test 8-bit idempotency with us-ascii
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003183 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
Guido van Rossum9604e662007-08-30 03:46:43 +00003184 self.assertRaises(UnicodeError, c.header_encode, s)
3185 c = Charset('utf-8')
3186 eq(c.header_encode(s), '=?utf-8?b?wqTCosKkwqTCpMKmwqTCqMKkwqo=?=')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003187
3188 def test_body_encode(self):
3189 eq = self.assertEqual
3190 # Try a charset with QP body encoding
3191 c = Charset('iso-8859-1')
Barry Warsaw7aa02e62007-08-31 03:26:19 +00003192 eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003193 # Try a charset with Base64 body encoding
3194 c = Charset('utf-8')
Martin v. Löwis15b16a32008-12-02 06:00:15 +00003195 eq('aGVsbG8gd29ybGQ=\n', c.body_encode(b'hello world'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003196 # Try a charset with None body encoding
3197 c = Charset('us-ascii')
Barry Warsaw7aa02e62007-08-31 03:26:19 +00003198 eq('hello world', c.body_encode('hello world'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003199 # Try the convert argument, where input codec != output codec
3200 c = Charset('euc-jp')
3201 # With apologies to Tokio Kikuchi ;)
Barry Warsawbef9d212007-08-31 10:55:37 +00003202 # XXX FIXME
3203## try:
3204## eq('\x1b$B5FCO;~IW\x1b(B',
3205## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
3206## eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
3207## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
3208## except LookupError:
3209## # We probably don't have the Japanese codecs installed
3210## pass
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003211 # Testing SF bug #625509, which we have to fake, since there are no
3212 # built-in encodings where the header encoding is QP but the body
3213 # encoding is not.
3214 from email import charset as CharsetModule
3215 CharsetModule.add_charset('fake', CharsetModule.QP, None)
3216 c = Charset('fake')
Barry Warsaw7aa02e62007-08-31 03:26:19 +00003217 eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003218
3219 def test_unicode_charset_name(self):
3220 charset = Charset('us-ascii')
3221 self.assertEqual(str(charset), 'us-ascii')
3222 self.assertRaises(errors.CharsetError, Charset, 'asc\xffii')
3223
3224
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00003225
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003226# Test multilingual MIME headers.
3227class TestHeader(TestEmailBase):
3228 def test_simple(self):
3229 eq = self.ndiffAssertEqual
3230 h = Header('Hello World!')
3231 eq(h.encode(), 'Hello World!')
3232 h.append(' Goodbye World!')
3233 eq(h.encode(), 'Hello World! Goodbye World!')
3234
3235 def test_simple_surprise(self):
3236 eq = self.ndiffAssertEqual
3237 h = Header('Hello World!')
3238 eq(h.encode(), 'Hello World!')
3239 h.append('Goodbye World!')
3240 eq(h.encode(), 'Hello World! Goodbye World!')
3241
3242 def test_header_needs_no_decoding(self):
3243 h = 'no decoding needed'
3244 self.assertEqual(decode_header(h), [(h, None)])
3245
3246 def test_long(self):
3247 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.",
3248 maxlinelen=76)
3249 for l in h.encode(splitchars=' ').split('\n '):
Georg Brandlab91fde2009-08-13 08:51:18 +00003250 self.assertTrue(len(l) <= 76)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003251
3252 def test_multilingual(self):
3253 eq = self.ndiffAssertEqual
3254 g = Charset("iso-8859-1")
3255 cz = Charset("iso-8859-2")
3256 utf8 = Charset("utf-8")
3257 g_head = (b'Die Mieter treten hier ein werden mit einem '
3258 b'Foerderband komfortabel den Korridor entlang, '
3259 b'an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, '
3260 b'gegen die rotierenden Klingen bef\xf6rdert. ')
3261 cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
3262 b'd\xf9vtipu.. ')
3263 utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
3264 '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
3265 '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
3266 '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
3267 '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
3268 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
3269 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
3270 '\u3044\u307e\u3059\u3002')
3271 h = Header(g_head, g)
3272 h.append(cz_head, cz)
3273 h.append(utf8_head, utf8)
Guido van Rossum9604e662007-08-30 03:46:43 +00003274 enc = h.encode(maxlinelen=76)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003275 eq(enc, """\
Guido van Rossum9604e662007-08-30 03:46:43 +00003276=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_kom?=
3277 =?iso-8859-1?q?fortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wand?=
3278 =?iso-8859-1?q?gem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6r?=
3279 =?iso-8859-1?q?dert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003280 =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
3281 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
3282 =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
3283 =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
Guido van Rossum9604e662007-08-30 03:46:43 +00003284 =?utf-8?b?IE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5k?=
3285 =?utf-8?b?IGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIA=?=
3286 =?utf-8?b?44Gj44Gm44GE44G+44GZ44CC?=""")
3287 decoded = decode_header(enc)
3288 eq(len(decoded), 3)
3289 eq(decoded[0], (g_head, 'iso-8859-1'))
3290 eq(decoded[1], (cz_head, 'iso-8859-2'))
3291 eq(decoded[2], (utf8_head.encode('utf-8'), 'utf-8'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003292 ustr = str(h)
Guido van Rossum9604e662007-08-30 03:46:43 +00003293 eq(ustr,
3294 (b'Die Mieter treten hier ein werden mit einem Foerderband '
3295 b'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
3296 b'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
3297 b'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
3298 b'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
3299 b'\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
3300 b'\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
3301 b'\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
3302 b'\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
3303 b'\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
3304 b'\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
3305 b'\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
3306 b'\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
3307 b'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
3308 b'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
3309 b'\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'
3310 ).decode('utf-8'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003311 # Test make_header()
3312 newh = make_header(decode_header(enc))
Guido van Rossum9604e662007-08-30 03:46:43 +00003313 eq(newh, h)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003314
3315 def test_empty_header_encode(self):
3316 h = Header()
3317 self.assertEqual(h.encode(), '')
Barry Warsaw8b3d6592007-08-30 02:10:49 +00003318
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003319 def test_header_ctor_default_args(self):
3320 eq = self.ndiffAssertEqual
3321 h = Header()
3322 eq(h, '')
3323 h.append('foo', Charset('iso-8859-1'))
Guido van Rossum9604e662007-08-30 03:46:43 +00003324 eq(h, 'foo')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003325
3326 def test_explicit_maxlinelen(self):
3327 eq = self.ndiffAssertEqual
3328 hstr = ('A very long line that must get split to something other '
3329 'than at the 76th character boundary to test the non-default '
3330 'behavior')
3331 h = Header(hstr)
3332 eq(h.encode(), '''\
3333A very long line that must get split to something other than at the 76th
3334 character boundary to test the non-default behavior''')
3335 eq(str(h), hstr)
3336 h = Header(hstr, header_name='Subject')
3337 eq(h.encode(), '''\
3338A very long line that must get split to something other than at the
3339 76th character boundary to test the non-default behavior''')
3340 eq(str(h), hstr)
3341 h = Header(hstr, maxlinelen=1024, header_name='Subject')
3342 eq(h.encode(), hstr)
3343 eq(str(h), hstr)
3344
Guido van Rossum9604e662007-08-30 03:46:43 +00003345 def test_quopri_splittable(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003346 eq = self.ndiffAssertEqual
3347 h = Header(charset='iso-8859-1', maxlinelen=20)
Guido van Rossum9604e662007-08-30 03:46:43 +00003348 x = 'xxxx ' * 20
3349 h.append(x)
3350 s = h.encode()
3351 eq(s, """\
3352=?iso-8859-1?q?xxx?=
3353 =?iso-8859-1?q?x_?=
3354 =?iso-8859-1?q?xx?=
3355 =?iso-8859-1?q?xx?=
3356 =?iso-8859-1?q?_x?=
3357 =?iso-8859-1?q?xx?=
3358 =?iso-8859-1?q?x_?=
3359 =?iso-8859-1?q?xx?=
3360 =?iso-8859-1?q?xx?=
3361 =?iso-8859-1?q?_x?=
3362 =?iso-8859-1?q?xx?=
3363 =?iso-8859-1?q?x_?=
3364 =?iso-8859-1?q?xx?=
3365 =?iso-8859-1?q?xx?=
3366 =?iso-8859-1?q?_x?=
3367 =?iso-8859-1?q?xx?=
3368 =?iso-8859-1?q?x_?=
3369 =?iso-8859-1?q?xx?=
3370 =?iso-8859-1?q?xx?=
3371 =?iso-8859-1?q?_x?=
3372 =?iso-8859-1?q?xx?=
3373 =?iso-8859-1?q?x_?=
3374 =?iso-8859-1?q?xx?=
3375 =?iso-8859-1?q?xx?=
3376 =?iso-8859-1?q?_x?=
3377 =?iso-8859-1?q?xx?=
3378 =?iso-8859-1?q?x_?=
3379 =?iso-8859-1?q?xx?=
3380 =?iso-8859-1?q?xx?=
3381 =?iso-8859-1?q?_x?=
3382 =?iso-8859-1?q?xx?=
3383 =?iso-8859-1?q?x_?=
3384 =?iso-8859-1?q?xx?=
3385 =?iso-8859-1?q?xx?=
3386 =?iso-8859-1?q?_x?=
3387 =?iso-8859-1?q?xx?=
3388 =?iso-8859-1?q?x_?=
3389 =?iso-8859-1?q?xx?=
3390 =?iso-8859-1?q?xx?=
3391 =?iso-8859-1?q?_x?=
3392 =?iso-8859-1?q?xx?=
3393 =?iso-8859-1?q?x_?=
3394 =?iso-8859-1?q?xx?=
3395 =?iso-8859-1?q?xx?=
3396 =?iso-8859-1?q?_x?=
3397 =?iso-8859-1?q?xx?=
3398 =?iso-8859-1?q?x_?=
3399 =?iso-8859-1?q?xx?=
3400 =?iso-8859-1?q?xx?=
3401 =?iso-8859-1?q?_?=""")
3402 eq(x, str(make_header(decode_header(s))))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003403 h = Header(charset='iso-8859-1', maxlinelen=40)
3404 h.append('xxxx ' * 20)
Guido van Rossum9604e662007-08-30 03:46:43 +00003405 s = h.encode()
3406 eq(s, """\
3407=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xxx?=
3408 =?iso-8859-1?q?x_xxxx_xxxx_xxxx_xxxx_?=
3409 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
3410 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
3411 =?iso-8859-1?q?_xxxx_xxxx_?=""")
3412 eq(x, str(make_header(decode_header(s))))
3413
3414 def test_base64_splittable(self):
3415 eq = self.ndiffAssertEqual
3416 h = Header(charset='koi8-r', maxlinelen=20)
3417 x = 'xxxx ' * 20
3418 h.append(x)
3419 s = h.encode()
3420 eq(s, """\
3421=?koi8-r?b?eHh4?=
3422 =?koi8-r?b?eCB4?=
3423 =?koi8-r?b?eHh4?=
3424 =?koi8-r?b?IHh4?=
3425 =?koi8-r?b?eHgg?=
3426 =?koi8-r?b?eHh4?=
3427 =?koi8-r?b?eCB4?=
3428 =?koi8-r?b?eHh4?=
3429 =?koi8-r?b?IHh4?=
3430 =?koi8-r?b?eHgg?=
3431 =?koi8-r?b?eHh4?=
3432 =?koi8-r?b?eCB4?=
3433 =?koi8-r?b?eHh4?=
3434 =?koi8-r?b?IHh4?=
3435 =?koi8-r?b?eHgg?=
3436 =?koi8-r?b?eHh4?=
3437 =?koi8-r?b?eCB4?=
3438 =?koi8-r?b?eHh4?=
3439 =?koi8-r?b?IHh4?=
3440 =?koi8-r?b?eHgg?=
3441 =?koi8-r?b?eHh4?=
3442 =?koi8-r?b?eCB4?=
3443 =?koi8-r?b?eHh4?=
3444 =?koi8-r?b?IHh4?=
3445 =?koi8-r?b?eHgg?=
3446 =?koi8-r?b?eHh4?=
3447 =?koi8-r?b?eCB4?=
3448 =?koi8-r?b?eHh4?=
3449 =?koi8-r?b?IHh4?=
3450 =?koi8-r?b?eHgg?=
3451 =?koi8-r?b?eHh4?=
3452 =?koi8-r?b?eCB4?=
3453 =?koi8-r?b?eHh4?=
3454 =?koi8-r?b?IA==?=""")
3455 eq(x, str(make_header(decode_header(s))))
3456 h = Header(charset='koi8-r', maxlinelen=40)
3457 h.append(x)
3458 s = h.encode()
3459 eq(s, """\
3460=?koi8-r?b?eHh4eCB4eHh4IHh4eHggeHh4?=
3461 =?koi8-r?b?eCB4eHh4IHh4eHggeHh4eCB4?=
3462 =?koi8-r?b?eHh4IHh4eHggeHh4eCB4eHh4?=
3463 =?koi8-r?b?IHh4eHggeHh4eCB4eHh4IHh4?=
3464 =?koi8-r?b?eHggeHh4eCB4eHh4IHh4eHgg?=
3465 =?koi8-r?b?eHh4eCB4eHh4IA==?=""")
3466 eq(x, str(make_header(decode_header(s))))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003467
3468 def test_us_ascii_header(self):
3469 eq = self.assertEqual
3470 s = 'hello'
3471 x = decode_header(s)
3472 eq(x, [('hello', None)])
3473 h = make_header(x)
3474 eq(s, h.encode())
3475
3476 def test_string_charset(self):
3477 eq = self.assertEqual
3478 h = Header()
3479 h.append('hello', 'iso-8859-1')
Guido van Rossum9604e662007-08-30 03:46:43 +00003480 eq(h, 'hello')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003481
3482## def test_unicode_error(self):
3483## raises = self.assertRaises
3484## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
3485## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
3486## h = Header()
3487## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
3488## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
3489## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
3490
3491 def test_utf8_shortest(self):
3492 eq = self.assertEqual
3493 h = Header('p\xf6stal', 'utf-8')
3494 eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
3495 h = Header('\u83ca\u5730\u6642\u592b', 'utf-8')
3496 eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
3497
3498 def test_bad_8bit_header(self):
3499 raises = self.assertRaises
3500 eq = self.assertEqual
3501 x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
3502 raises(UnicodeError, Header, x)
3503 h = Header()
3504 raises(UnicodeError, h.append, x)
3505 e = x.decode('utf-8', 'replace')
3506 eq(str(Header(x, errors='replace')), e)
3507 h.append(x, errors='replace')
3508 eq(str(h), e)
3509
3510 def test_encoded_adjacent_nonencoded(self):
3511 eq = self.assertEqual
3512 h = Header()
3513 h.append('hello', 'iso-8859-1')
3514 h.append('world')
3515 s = h.encode()
3516 eq(s, '=?iso-8859-1?q?hello?= world')
3517 h = make_header(decode_header(s))
3518 eq(h.encode(), s)
3519
3520 def test_whitespace_eater(self):
3521 eq = self.assertEqual
3522 s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
3523 parts = decode_header(s)
3524 eq(parts, [(b'Subject:', None), (b'\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), (b'zz.', None)])
3525 hdr = make_header(parts)
3526 eq(hdr.encode(),
3527 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
3528
3529 def test_broken_base64_header(self):
3530 raises = self.assertRaises
R. David Murraye06528c2010-08-03 23:35:44 +00003531 s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3I ?='
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003532 raises(errors.HeaderParseError, decode_header, s)
3533
R. David Murrayf9844c82011-01-05 01:47:38 +00003534 def test_shift_jis_charset(self):
3535 h = Header('æ–‡', charset='shift_jis')
3536 self.assertEqual(h.encode(), '=?iso-2022-jp?b?GyRCSjgbKEI=?=')
3537
R David Murrayde912762011-03-16 18:26:23 -04003538 def test_flatten_header_with_no_value(self):
3539 # Issue 11401 (regression from email 4.x) Note that the space after
3540 # the header doesn't reflect the input, but this is also the way
3541 # email 4.x behaved. At some point it would be nice to fix that.
3542 msg = email.message_from_string("EmptyHeader:")
3543 self.assertEqual(str(msg), "EmptyHeader: \n\n")
3544
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003545
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00003546
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003547# Test RFC 2231 header parameters (en/de)coding
3548class TestRFC2231(TestEmailBase):
3549 def test_get_param(self):
3550 eq = self.assertEqual
3551 msg = self._msgobj('msg_29.txt')
3552 eq(msg.get_param('title'),
3553 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
3554 eq(msg.get_param('title', unquote=False),
3555 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
3556
3557 def test_set_param(self):
3558 eq = self.ndiffAssertEqual
3559 msg = Message()
3560 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3561 charset='us-ascii')
3562 eq(msg.get_param('title'),
3563 ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
3564 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3565 charset='us-ascii', language='en')
3566 eq(msg.get_param('title'),
3567 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
3568 msg = self._msgobj('msg_01.txt')
3569 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3570 charset='us-ascii', language='en')
3571 eq(msg.as_string(maxheaderlen=78), """\
3572Return-Path: <bbb@zzz.org>
3573Delivered-To: bbb@zzz.org
3574Received: by mail.zzz.org (Postfix, from userid 889)
3575\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3576MIME-Version: 1.0
3577Content-Transfer-Encoding: 7bit
3578Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
3579From: bbb@ddd.com (John X. Doe)
3580To: bbb@zzz.org
3581Subject: This is a test message
3582Date: Fri, 4 May 2001 14:05:44 -0400
3583Content-Type: text/plain; charset=us-ascii;
3584 title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3585
3586
3587Hi,
3588
3589Do you like this message?
3590
3591-Me
3592""")
3593
3594 def test_del_param(self):
3595 eq = self.ndiffAssertEqual
3596 msg = self._msgobj('msg_01.txt')
3597 msg.set_param('foo', 'bar', charset='us-ascii', language='en')
3598 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3599 charset='us-ascii', language='en')
3600 msg.del_param('foo', header='Content-Type')
3601 eq(msg.as_string(maxheaderlen=78), """\
3602Return-Path: <bbb@zzz.org>
3603Delivered-To: bbb@zzz.org
3604Received: by mail.zzz.org (Postfix, from userid 889)
3605\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3606MIME-Version: 1.0
3607Content-Transfer-Encoding: 7bit
3608Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
3609From: bbb@ddd.com (John X. Doe)
3610To: bbb@zzz.org
3611Subject: This is a test message
3612Date: Fri, 4 May 2001 14:05:44 -0400
3613Content-Type: text/plain; charset="us-ascii";
3614 title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3615
3616
3617Hi,
3618
3619Do you like this message?
3620
3621-Me
3622""")
3623
3624 def test_rfc2231_get_content_charset(self):
3625 eq = self.assertEqual
3626 msg = self._msgobj('msg_32.txt')
3627 eq(msg.get_content_charset(), 'us-ascii')
3628
3629 def test_rfc2231_no_language_or_charset(self):
3630 m = '''\
3631Content-Transfer-Encoding: 8bit
3632Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
3633Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
3634
3635'''
3636 msg = email.message_from_string(m)
3637 param = msg.get_param('NAME')
Georg Brandlab91fde2009-08-13 08:51:18 +00003638 self.assertFalse(isinstance(param, tuple))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003639 self.assertEqual(
3640 param,
3641 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
3642
3643 def test_rfc2231_no_language_or_charset_in_filename(self):
3644 m = '''\
3645Content-Disposition: inline;
3646\tfilename*0*="''This%20is%20even%20more%20";
3647\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3648\tfilename*2="is it not.pdf"
3649
3650'''
3651 msg = email.message_from_string(m)
3652 self.assertEqual(msg.get_filename(),
3653 'This is even more ***fun*** is it not.pdf')
3654
3655 def test_rfc2231_no_language_or_charset_in_filename_encoded(self):
3656 m = '''\
3657Content-Disposition: inline;
3658\tfilename*0*="''This%20is%20even%20more%20";
3659\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3660\tfilename*2="is it not.pdf"
3661
3662'''
3663 msg = email.message_from_string(m)
3664 self.assertEqual(msg.get_filename(),
3665 'This is even more ***fun*** is it not.pdf')
3666
3667 def test_rfc2231_partly_encoded(self):
3668 m = '''\
3669Content-Disposition: inline;
3670\tfilename*0="''This%20is%20even%20more%20";
3671\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3672\tfilename*2="is it not.pdf"
3673
3674'''
3675 msg = email.message_from_string(m)
3676 self.assertEqual(
3677 msg.get_filename(),
3678 'This%20is%20even%20more%20***fun*** is it not.pdf')
3679
3680 def test_rfc2231_partly_nonencoded(self):
3681 m = '''\
3682Content-Disposition: inline;
3683\tfilename*0="This%20is%20even%20more%20";
3684\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
3685\tfilename*2="is it not.pdf"
3686
3687'''
3688 msg = email.message_from_string(m)
3689 self.assertEqual(
3690 msg.get_filename(),
3691 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
3692
3693 def test_rfc2231_no_language_or_charset_in_boundary(self):
3694 m = '''\
3695Content-Type: multipart/alternative;
3696\tboundary*0*="''This%20is%20even%20more%20";
3697\tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
3698\tboundary*2="is it not.pdf"
3699
3700'''
3701 msg = email.message_from_string(m)
3702 self.assertEqual(msg.get_boundary(),
3703 'This is even more ***fun*** is it not.pdf')
3704
3705 def test_rfc2231_no_language_or_charset_in_charset(self):
3706 # This is a nonsensical charset value, but tests the code anyway
3707 m = '''\
3708Content-Type: text/plain;
3709\tcharset*0*="This%20is%20even%20more%20";
3710\tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
3711\tcharset*2="is it not.pdf"
3712
3713'''
3714 msg = email.message_from_string(m)
3715 self.assertEqual(msg.get_content_charset(),
3716 'this is even more ***fun*** is it not.pdf')
3717
3718 def test_rfc2231_bad_encoding_in_filename(self):
3719 m = '''\
3720Content-Disposition: inline;
3721\tfilename*0*="bogus'xx'This%20is%20even%20more%20";
3722\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3723\tfilename*2="is it not.pdf"
3724
3725'''
3726 msg = email.message_from_string(m)
3727 self.assertEqual(msg.get_filename(),
3728 'This is even more ***fun*** is it not.pdf')
3729
3730 def test_rfc2231_bad_encoding_in_charset(self):
3731 m = """\
3732Content-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
3733
3734"""
3735 msg = email.message_from_string(m)
3736 # This should return None because non-ascii characters in the charset
3737 # are not allowed.
3738 self.assertEqual(msg.get_content_charset(), None)
3739
3740 def test_rfc2231_bad_character_in_charset(self):
3741 m = """\
3742Content-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
3743
3744"""
3745 msg = email.message_from_string(m)
3746 # This should return None because non-ascii characters in the charset
3747 # are not allowed.
3748 self.assertEqual(msg.get_content_charset(), None)
3749
3750 def test_rfc2231_bad_character_in_filename(self):
3751 m = '''\
3752Content-Disposition: inline;
3753\tfilename*0*="ascii'xx'This%20is%20even%20more%20";
3754\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3755\tfilename*2*="is it not.pdf%E2"
3756
3757'''
3758 msg = email.message_from_string(m)
3759 self.assertEqual(msg.get_filename(),
3760 'This is even more ***fun*** is it not.pdf\ufffd')
3761
3762 def test_rfc2231_unknown_encoding(self):
3763 m = """\
3764Content-Transfer-Encoding: 8bit
3765Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
3766
3767"""
3768 msg = email.message_from_string(m)
3769 self.assertEqual(msg.get_filename(), 'myfile.txt')
3770
3771 def test_rfc2231_single_tick_in_filename_extended(self):
3772 eq = self.assertEqual
3773 m = """\
3774Content-Type: application/x-foo;
3775\tname*0*=\"Frank's\"; name*1*=\" Document\"
3776
3777"""
3778 msg = email.message_from_string(m)
3779 charset, language, s = msg.get_param('name')
3780 eq(charset, None)
3781 eq(language, None)
3782 eq(s, "Frank's Document")
3783
3784 def test_rfc2231_single_tick_in_filename(self):
3785 m = """\
3786Content-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
3787
3788"""
3789 msg = email.message_from_string(m)
3790 param = msg.get_param('name')
Georg Brandlab91fde2009-08-13 08:51:18 +00003791 self.assertFalse(isinstance(param, tuple))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003792 self.assertEqual(param, "Frank's Document")
3793
3794 def test_rfc2231_tick_attack_extended(self):
3795 eq = self.assertEqual
3796 m = """\
3797Content-Type: application/x-foo;
3798\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
3799
3800"""
3801 msg = email.message_from_string(m)
3802 charset, language, s = msg.get_param('name')
3803 eq(charset, 'us-ascii')
3804 eq(language, 'en-us')
3805 eq(s, "Frank's Document")
3806
3807 def test_rfc2231_tick_attack(self):
3808 m = """\
3809Content-Type: application/x-foo;
3810\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
3811
3812"""
3813 msg = email.message_from_string(m)
3814 param = msg.get_param('name')
Georg Brandlab91fde2009-08-13 08:51:18 +00003815 self.assertFalse(isinstance(param, tuple))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003816 self.assertEqual(param, "us-ascii'en-us'Frank's Document")
3817
3818 def test_rfc2231_no_extended_values(self):
3819 eq = self.assertEqual
3820 m = """\
3821Content-Type: application/x-foo; name=\"Frank's Document\"
3822
3823"""
3824 msg = email.message_from_string(m)
3825 eq(msg.get_param('name'), "Frank's Document")
3826
3827 def test_rfc2231_encoded_then_unencoded_segments(self):
3828 eq = self.assertEqual
3829 m = """\
3830Content-Type: application/x-foo;
3831\tname*0*=\"us-ascii'en-us'My\";
3832\tname*1=\" Document\";
3833\tname*2*=\" For You\"
3834
3835"""
3836 msg = email.message_from_string(m)
3837 charset, language, s = msg.get_param('name')
3838 eq(charset, 'us-ascii')
3839 eq(language, 'en-us')
3840 eq(s, 'My Document For You')
3841
3842 def test_rfc2231_unencoded_then_encoded_segments(self):
3843 eq = self.assertEqual
3844 m = """\
3845Content-Type: application/x-foo;
3846\tname*0=\"us-ascii'en-us'My\";
3847\tname*1*=\" Document\";
3848\tname*2*=\" For You\"
3849
3850"""
3851 msg = email.message_from_string(m)
3852 charset, language, s = msg.get_param('name')
3853 eq(charset, 'us-ascii')
3854 eq(language, 'en-us')
3855 eq(s, 'My Document For You')
3856
3857
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00003858
R. David Murrayfa606922010-01-16 18:41:00 +00003859# Tests to ensure that signed parts of an email are completely preserved, as
3860# required by RFC1847 section 2.1. Note that these are incomplete, because the
3861# email package does not currently always preserve the body. See issue 1670765.
3862class TestSigned(TestEmailBase):
3863
3864 def _msg_and_obj(self, filename):
3865 with openfile(findfile(filename)) as fp:
3866 original = fp.read()
3867 msg = email.message_from_string(original)
3868 return original, msg
3869
3870 def _signed_parts_eq(self, original, result):
3871 # Extract the first mime part of each message
3872 import re
3873 repart = re.compile(r'^--([^\n]+)\n(.*?)\n--\1$', re.S | re.M)
3874 inpart = repart.search(original).group(2)
3875 outpart = repart.search(result).group(2)
3876 self.assertEqual(outpart, inpart)
3877
3878 def test_long_headers_as_string(self):
3879 original, msg = self._msg_and_obj('msg_45.txt')
3880 result = msg.as_string()
3881 self._signed_parts_eq(original, result)
3882
3883 def test_long_headers_as_string_maxheaderlen(self):
3884 original, msg = self._msg_and_obj('msg_45.txt')
3885 result = msg.as_string(maxheaderlen=60)
3886 self._signed_parts_eq(original, result)
3887
3888 def test_long_headers_flatten(self):
3889 original, msg = self._msg_and_obj('msg_45.txt')
3890 fp = StringIO()
3891 Generator(fp).flatten(msg)
3892 result = fp.getvalue()
3893 self._signed_parts_eq(original, result)
3894
3895
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00003896
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003897def _testclasses():
3898 mod = sys.modules[__name__]
3899 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
3900
3901
3902def suite():
3903 suite = unittest.TestSuite()
3904 for testclass in _testclasses():
3905 suite.addTest(unittest.makeSuite(testclass))
3906 return suite
3907
3908
3909def test_main():
3910 for testclass in _testclasses():
3911 run_unittest(testclass)
3912
3913
Ezio Melotti19f2aeb2010-11-21 01:30:29 +00003914
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003915if __name__ == '__main__':
3916 unittest.main(defaultTest='suite')