blob: 44a47c54fcfe91a99f4ee4560ba1a55ce28b0680 [file] [log] [blame]
Benjamin Peterson46a99002010-01-09 18:45:30 +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
12
13from io import StringIO
14from itertools import chain
15
16import email
17
18from email.charset import Charset
19from email.header import Header, decode_header, make_header
20from email.parser import Parser, HeaderParser
21from email.generator import Generator, DecodedGenerator
22from email.message import Message
23from email.mime.application import MIMEApplication
24from email.mime.audio import MIMEAudio
25from email.mime.text import MIMEText
26from email.mime.image import MIMEImage
27from email.mime.base import MIMEBase
28from email.mime.message import MIMEMessage
29from email.mime.multipart import MIMEMultipart
30from email import utils
31from email import errors
32from email import encoders
33from email import iterators
34from email import base64mime
35from email import quoprimime
36
Benjamin Petersonee8712c2008-05-20 21:35:26 +000037from test.support import findfile, run_unittest
Guido van Rossum8b3febe2007-08-30 01:15:14 +000038from email.test import __file__ as landmark
39
40
41NL = '\n'
42EMPTYSTRING = ''
43SPACE = ' '
44
45
46
47def openfile(filename, *args, **kws):
48 path = os.path.join(os.path.dirname(landmark), 'data', filename)
49 return open(path, *args, **kws)
50
51
52
53# Base test class
54class TestEmailBase(unittest.TestCase):
55 def ndiffAssertEqual(self, first, second):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +000056 """Like assertEqual except use ndiff for readable output."""
Guido van Rossum8b3febe2007-08-30 01:15:14 +000057 if first != second:
58 sfirst = str(first)
59 ssecond = str(second)
60 rfirst = [repr(line) for line in sfirst.splitlines()]
61 rsecond = [repr(line) for line in ssecond.splitlines()]
62 diff = difflib.ndiff(rfirst, rsecond)
63 raise self.failureException(NL + NL.join(diff))
64
65 def _msgobj(self, filename):
66 with openfile(findfile(filename)) as fp:
67 return email.message_from_file(fp)
68
69
70
71# Test various aspects of the Message class's API
72class TestMessageAPI(TestEmailBase):
73 def test_get_all(self):
74 eq = self.assertEqual
75 msg = self._msgobj('msg_20.txt')
76 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
77 eq(msg.get_all('xx', 'n/a'), 'n/a')
78
79 def test_getset_charset(self):
80 eq = self.assertEqual
81 msg = Message()
82 eq(msg.get_charset(), None)
83 charset = Charset('iso-8859-1')
84 msg.set_charset(charset)
85 eq(msg['mime-version'], '1.0')
86 eq(msg.get_content_type(), 'text/plain')
87 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
88 eq(msg.get_param('charset'), 'iso-8859-1')
89 eq(msg['content-transfer-encoding'], 'quoted-printable')
90 eq(msg.get_charset().input_charset, 'iso-8859-1')
91 # Remove the charset
92 msg.set_charset(None)
93 eq(msg.get_charset(), None)
94 eq(msg['content-type'], 'text/plain')
95 # Try adding a charset when there's already MIME headers present
96 msg = Message()
97 msg['MIME-Version'] = '2.0'
98 msg['Content-Type'] = 'text/x-weird'
99 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
100 msg.set_charset(charset)
101 eq(msg['mime-version'], '2.0')
102 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
103 eq(msg['content-transfer-encoding'], 'quinted-puntable')
104
105 def test_set_charset_from_string(self):
106 eq = self.assertEqual
107 msg = Message()
108 msg.set_charset('us-ascii')
109 eq(msg.get_charset().input_charset, 'us-ascii')
110 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
111
112 def test_set_payload_with_charset(self):
113 msg = Message()
114 charset = Charset('iso-8859-1')
115 msg.set_payload('This is a string payload', charset)
116 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
117
118 def test_get_charsets(self):
119 eq = self.assertEqual
120
121 msg = self._msgobj('msg_08.txt')
122 charsets = msg.get_charsets()
123 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
124
125 msg = self._msgobj('msg_09.txt')
126 charsets = msg.get_charsets('dingbat')
127 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
128 'koi8-r'])
129
130 msg = self._msgobj('msg_12.txt')
131 charsets = msg.get_charsets()
132 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
133 'iso-8859-3', 'us-ascii', 'koi8-r'])
134
135 def test_get_filename(self):
136 eq = self.assertEqual
137
138 msg = self._msgobj('msg_04.txt')
139 filenames = [p.get_filename() for p in msg.get_payload()]
140 eq(filenames, ['msg.txt', 'msg.txt'])
141
142 msg = self._msgobj('msg_07.txt')
143 subpart = msg.get_payload(1)
144 eq(subpart.get_filename(), 'dingusfish.gif')
145
146 def test_get_filename_with_name_parameter(self):
147 eq = self.assertEqual
148
149 msg = self._msgobj('msg_44.txt')
150 filenames = [p.get_filename() for p in msg.get_payload()]
151 eq(filenames, ['msg.txt', 'msg.txt'])
152
153 def test_get_boundary(self):
154 eq = self.assertEqual
155 msg = self._msgobj('msg_07.txt')
156 # No quotes!
157 eq(msg.get_boundary(), 'BOUNDARY')
158
159 def test_set_boundary(self):
160 eq = self.assertEqual
161 # This one has no existing boundary parameter, but the Content-Type:
162 # header appears fifth.
163 msg = self._msgobj('msg_01.txt')
164 msg.set_boundary('BOUNDARY')
165 header, value = msg.items()[4]
166 eq(header.lower(), 'content-type')
167 eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
168 # This one has a Content-Type: header, with a boundary, stuck in the
169 # middle of its headers. Make sure the order is preserved; it should
170 # be fifth.
171 msg = self._msgobj('msg_04.txt')
172 msg.set_boundary('BOUNDARY')
173 header, value = msg.items()[4]
174 eq(header.lower(), 'content-type')
175 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
176 # And this one has no Content-Type: header at all.
177 msg = self._msgobj('msg_03.txt')
178 self.assertRaises(errors.HeaderParseError,
179 msg.set_boundary, 'BOUNDARY')
180
R. David Murray57c45ac2010-02-21 04:39:40 +0000181 def test_message_rfc822_only(self):
182 # Issue 7970: message/rfc822 not in multipart parsed by
183 # HeaderParser caused an exception when flattened.
184 fp = openfile(findfile('msg_46.txt'))
185 msgdata = fp.read()
186 parser = HeaderParser()
187 msg = parser.parsestr(msgdata)
188 out = StringIO()
189 gen = Generator(out, True, 0)
190 gen.flatten(msg, False)
191 self.assertEqual(out.getvalue(), msgdata)
192
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000193 def test_get_decoded_payload(self):
194 eq = self.assertEqual
195 msg = self._msgobj('msg_10.txt')
196 # The outer message is a multipart
197 eq(msg.get_payload(decode=True), None)
198 # Subpart 1 is 7bit encoded
199 eq(msg.get_payload(0).get_payload(decode=True),
200 b'This is a 7bit encoded message.\n')
201 # Subpart 2 is quopri
202 eq(msg.get_payload(1).get_payload(decode=True),
203 b'\xa1This is a Quoted Printable encoded message!\n')
204 # Subpart 3 is base64
205 eq(msg.get_payload(2).get_payload(decode=True),
206 b'This is a Base64 encoded message.')
R. David Murray57a4b982010-03-08 02:17:03 +0000207 # Subpart 4 is base64 with a trailing newline, which
208 # used to be stripped (issue 7143).
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000209 eq(msg.get_payload(3).get_payload(decode=True),
R. David Murray57a4b982010-03-08 02:17:03 +0000210 b'This is a Base64 encoded message.\n')
211 # Subpart 5 has no Content-Transfer-Encoding: header.
212 eq(msg.get_payload(4).get_payload(decode=True),
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000213 b'This has no Content-Transfer-Encoding: header.\n')
214
215 def test_get_decoded_uu_payload(self):
216 eq = self.assertEqual
217 msg = Message()
218 msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
219 for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
220 msg['content-transfer-encoding'] = cte
221 eq(msg.get_payload(decode=True), b'hello world')
222 # Now try some bogus data
223 msg.set_payload('foo')
224 eq(msg.get_payload(decode=True), b'foo')
225
226 def test_decoded_generator(self):
227 eq = self.assertEqual
228 msg = self._msgobj('msg_07.txt')
229 with openfile('msg_17.txt') as fp:
230 text = fp.read()
231 s = StringIO()
232 g = DecodedGenerator(s)
233 g.flatten(msg)
234 eq(s.getvalue(), text)
235
236 def test__contains__(self):
237 msg = Message()
238 msg['From'] = 'Me'
239 msg['to'] = 'You'
240 # Check for case insensitivity
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000241 self.assertTrue('from' in msg)
242 self.assertTrue('From' in msg)
243 self.assertTrue('FROM' in msg)
244 self.assertTrue('to' in msg)
245 self.assertTrue('To' in msg)
246 self.assertTrue('TO' in msg)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000247
248 def test_as_string(self):
249 eq = self.ndiffAssertEqual
250 msg = self._msgobj('msg_01.txt')
251 with openfile('msg_01.txt') as fp:
252 text = fp.read()
253 eq(text, str(msg))
254 fullrepr = msg.as_string(unixfrom=True)
255 lines = fullrepr.split('\n')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000256 self.assertTrue(lines[0].startswith('From '))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000257 eq(text, NL.join(lines[1:]))
258
259 def test_bad_param(self):
260 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
261 self.assertEqual(msg.get_param('baz'), '')
262
263 def test_missing_filename(self):
264 msg = email.message_from_string("From: foo\n")
265 self.assertEqual(msg.get_filename(), None)
266
267 def test_bogus_filename(self):
268 msg = email.message_from_string(
269 "Content-Disposition: blarg; filename\n")
270 self.assertEqual(msg.get_filename(), '')
271
272 def test_missing_boundary(self):
273 msg = email.message_from_string("From: foo\n")
274 self.assertEqual(msg.get_boundary(), None)
275
276 def test_get_params(self):
277 eq = self.assertEqual
278 msg = email.message_from_string(
279 'X-Header: foo=one; bar=two; baz=three\n')
280 eq(msg.get_params(header='x-header'),
281 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
282 msg = email.message_from_string(
283 'X-Header: foo; bar=one; baz=two\n')
284 eq(msg.get_params(header='x-header'),
285 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
286 eq(msg.get_params(), None)
287 msg = email.message_from_string(
288 'X-Header: foo; bar="one"; baz=two\n')
289 eq(msg.get_params(header='x-header'),
290 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
291
292 def test_get_param_liberal(self):
293 msg = Message()
294 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
295 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
296
297 def test_get_param(self):
298 eq = self.assertEqual
299 msg = email.message_from_string(
300 "X-Header: foo=one; bar=two; baz=three\n")
301 eq(msg.get_param('bar', header='x-header'), 'two')
302 eq(msg.get_param('quuz', header='x-header'), None)
303 eq(msg.get_param('quuz'), None)
304 msg = email.message_from_string(
305 'X-Header: foo; bar="one"; baz=two\n')
306 eq(msg.get_param('foo', header='x-header'), '')
307 eq(msg.get_param('bar', header='x-header'), 'one')
308 eq(msg.get_param('baz', header='x-header'), 'two')
309 # XXX: We are not RFC-2045 compliant! We cannot parse:
310 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
311 # msg.get_param("weird")
312 # yet.
313
314 def test_get_param_funky_continuation_lines(self):
315 msg = self._msgobj('msg_22.txt')
316 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
317
318 def test_get_param_with_semis_in_quotes(self):
319 msg = email.message_from_string(
320 'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
321 self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
322 self.assertEqual(msg.get_param('name', unquote=False),
323 '"Jim&amp;&amp;Jill"')
324
R. David Murrayd48739f2010-04-14 18:59:18 +0000325 def test_get_param_with_quotes(self):
326 msg = email.message_from_string(
327 'Content-Type: foo; bar*0="baz\\"foobar"; bar*1="\\"baz"')
328 self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
329 msg = email.message_from_string(
330 "Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"")
331 self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
332
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000333 def test_field_containment(self):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000334 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000335 msg = email.message_from_string('Header: exists')
336 unless('header' in msg)
337 unless('Header' in msg)
338 unless('HEADER' in msg)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000339 self.assertFalse('headerx' in msg)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000340
341 def test_set_param(self):
342 eq = self.assertEqual
343 msg = Message()
344 msg.set_param('charset', 'iso-2022-jp')
345 eq(msg.get_param('charset'), 'iso-2022-jp')
346 msg.set_param('importance', 'high value')
347 eq(msg.get_param('importance'), 'high value')
348 eq(msg.get_param('importance', unquote=False), '"high value"')
349 eq(msg.get_params(), [('text/plain', ''),
350 ('charset', 'iso-2022-jp'),
351 ('importance', 'high value')])
352 eq(msg.get_params(unquote=False), [('text/plain', ''),
353 ('charset', '"iso-2022-jp"'),
354 ('importance', '"high value"')])
355 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
356 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
357
358 def test_del_param(self):
359 eq = self.assertEqual
360 msg = self._msgobj('msg_05.txt')
361 eq(msg.get_params(),
362 [('multipart/report', ''), ('report-type', 'delivery-status'),
363 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
364 old_val = msg.get_param("report-type")
365 msg.del_param("report-type")
366 eq(msg.get_params(),
367 [('multipart/report', ''),
368 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
369 msg.set_param("report-type", old_val)
370 eq(msg.get_params(),
371 [('multipart/report', ''),
372 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
373 ('report-type', old_val)])
374
375 def test_del_param_on_other_header(self):
376 msg = Message()
377 msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
378 msg.del_param('filename', 'content-disposition')
379 self.assertEqual(msg['content-disposition'], 'attachment')
380
381 def test_set_type(self):
382 eq = self.assertEqual
383 msg = Message()
384 self.assertRaises(ValueError, msg.set_type, 'text')
385 msg.set_type('text/plain')
386 eq(msg['content-type'], 'text/plain')
387 msg.set_param('charset', 'us-ascii')
388 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
389 msg.set_type('text/html')
390 eq(msg['content-type'], 'text/html; charset="us-ascii"')
391
392 def test_set_type_on_other_header(self):
393 msg = Message()
394 msg['X-Content-Type'] = 'text/plain'
395 msg.set_type('application/octet-stream', 'X-Content-Type')
396 self.assertEqual(msg['x-content-type'], 'application/octet-stream')
397
398 def test_get_content_type_missing(self):
399 msg = Message()
400 self.assertEqual(msg.get_content_type(), 'text/plain')
401
402 def test_get_content_type_missing_with_default_type(self):
403 msg = Message()
404 msg.set_default_type('message/rfc822')
405 self.assertEqual(msg.get_content_type(), 'message/rfc822')
406
407 def test_get_content_type_from_message_implicit(self):
408 msg = self._msgobj('msg_30.txt')
409 self.assertEqual(msg.get_payload(0).get_content_type(),
410 'message/rfc822')
411
412 def test_get_content_type_from_message_explicit(self):
413 msg = self._msgobj('msg_28.txt')
414 self.assertEqual(msg.get_payload(0).get_content_type(),
415 'message/rfc822')
416
417 def test_get_content_type_from_message_text_plain_implicit(self):
418 msg = self._msgobj('msg_03.txt')
419 self.assertEqual(msg.get_content_type(), 'text/plain')
420
421 def test_get_content_type_from_message_text_plain_explicit(self):
422 msg = self._msgobj('msg_01.txt')
423 self.assertEqual(msg.get_content_type(), 'text/plain')
424
425 def test_get_content_maintype_missing(self):
426 msg = Message()
427 self.assertEqual(msg.get_content_maintype(), 'text')
428
429 def test_get_content_maintype_missing_with_default_type(self):
430 msg = Message()
431 msg.set_default_type('message/rfc822')
432 self.assertEqual(msg.get_content_maintype(), 'message')
433
434 def test_get_content_maintype_from_message_implicit(self):
435 msg = self._msgobj('msg_30.txt')
436 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
437
438 def test_get_content_maintype_from_message_explicit(self):
439 msg = self._msgobj('msg_28.txt')
440 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
441
442 def test_get_content_maintype_from_message_text_plain_implicit(self):
443 msg = self._msgobj('msg_03.txt')
444 self.assertEqual(msg.get_content_maintype(), 'text')
445
446 def test_get_content_maintype_from_message_text_plain_explicit(self):
447 msg = self._msgobj('msg_01.txt')
448 self.assertEqual(msg.get_content_maintype(), 'text')
449
450 def test_get_content_subtype_missing(self):
451 msg = Message()
452 self.assertEqual(msg.get_content_subtype(), 'plain')
453
454 def test_get_content_subtype_missing_with_default_type(self):
455 msg = Message()
456 msg.set_default_type('message/rfc822')
457 self.assertEqual(msg.get_content_subtype(), 'rfc822')
458
459 def test_get_content_subtype_from_message_implicit(self):
460 msg = self._msgobj('msg_30.txt')
461 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
462
463 def test_get_content_subtype_from_message_explicit(self):
464 msg = self._msgobj('msg_28.txt')
465 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
466
467 def test_get_content_subtype_from_message_text_plain_implicit(self):
468 msg = self._msgobj('msg_03.txt')
469 self.assertEqual(msg.get_content_subtype(), 'plain')
470
471 def test_get_content_subtype_from_message_text_plain_explicit(self):
472 msg = self._msgobj('msg_01.txt')
473 self.assertEqual(msg.get_content_subtype(), 'plain')
474
475 def test_get_content_maintype_error(self):
476 msg = Message()
477 msg['Content-Type'] = 'no-slash-in-this-string'
478 self.assertEqual(msg.get_content_maintype(), 'text')
479
480 def test_get_content_subtype_error(self):
481 msg = Message()
482 msg['Content-Type'] = 'no-slash-in-this-string'
483 self.assertEqual(msg.get_content_subtype(), 'plain')
484
485 def test_replace_header(self):
486 eq = self.assertEqual
487 msg = Message()
488 msg.add_header('First', 'One')
489 msg.add_header('Second', 'Two')
490 msg.add_header('Third', 'Three')
491 eq(msg.keys(), ['First', 'Second', 'Third'])
492 eq(msg.values(), ['One', 'Two', 'Three'])
493 msg.replace_header('Second', 'Twenty')
494 eq(msg.keys(), ['First', 'Second', 'Third'])
495 eq(msg.values(), ['One', 'Twenty', 'Three'])
496 msg.add_header('First', 'Eleven')
497 msg.replace_header('First', 'One Hundred')
498 eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
499 eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
500 self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
501
502 def test_broken_base64_payload(self):
503 x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
504 msg = Message()
505 msg['content-type'] = 'audio/x-midi'
506 msg['content-transfer-encoding'] = 'base64'
507 msg.set_payload(x)
508 self.assertEqual(msg.get_payload(decode=True),
Guido van Rossum9604e662007-08-30 03:46:43 +0000509 bytes(x, 'raw-unicode-escape'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000510
511
512
513# Test the email.encoders module
514class TestEncoders(unittest.TestCase):
515 def test_encode_empty_payload(self):
516 eq = self.assertEqual
517 msg = Message()
518 msg.set_charset('us-ascii')
519 eq(msg['content-transfer-encoding'], '7bit')
520
521 def test_default_cte(self):
522 eq = self.assertEqual
Ezio Melottic303c122010-04-22 11:57:12 +0000523 # 7bit data and the default us-ascii _charset
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000524 msg = MIMEText('hello world')
525 eq(msg['content-transfer-encoding'], '7bit')
Ezio Melottic303c122010-04-22 11:57:12 +0000526 # Similar, but with 8bit data
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000527 msg = MIMEText('hello \xf8 world')
528 eq(msg['content-transfer-encoding'], '8bit')
529 # And now with a different charset
530 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
531 eq(msg['content-transfer-encoding'], 'quoted-printable')
532
R. David Murraye85200d2010-05-06 01:41:14 +0000533 def test_encode7or8bit(self):
534 # Make sure a charset whose input character set is 8bit but
535 # whose output character set is 7bit gets a transfer-encoding
536 # of 7bit.
537 eq = self.assertEqual
R. David Murray850fc852010-06-03 01:58:28 +0000538 msg = MIMEText('æ–‡', _charset='euc-jp')
R. David Murraye85200d2010-05-06 01:41:14 +0000539 eq(msg['content-transfer-encoding'], '7bit')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000540
541
542# Test long header wrapping
543class TestLongHeaders(TestEmailBase):
544 def test_split_long_continuation(self):
545 eq = self.ndiffAssertEqual
546 msg = email.message_from_string("""\
547Subject: bug demonstration
548\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
549\tmore text
550
551test
552""")
553 sfp = StringIO()
554 g = Generator(sfp)
555 g.flatten(msg)
556 eq(sfp.getvalue(), """\
557Subject: bug demonstration
558\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
559\tmore text
560
561test
562""")
563
564 def test_another_long_almost_unsplittable_header(self):
565 eq = self.ndiffAssertEqual
566 hstr = """\
567bug demonstration
568\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
569\tmore text"""
570 h = Header(hstr, continuation_ws='\t')
571 eq(h.encode(), """\
572bug demonstration
573\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
574\tmore text""")
575 h = Header(hstr.replace('\t', ' '))
576 eq(h.encode(), """\
577bug demonstration
578 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
579 more text""")
580
581 def test_long_nonstring(self):
582 eq = self.ndiffAssertEqual
583 g = Charset("iso-8859-1")
584 cz = Charset("iso-8859-2")
585 utf8 = Charset("utf-8")
586 g_head = (b'Die Mieter treten hier ein werden mit einem Foerderband '
587 b'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
588 b'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
589 b'bef\xf6rdert. ')
590 cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
591 b'd\xf9vtipu.. ')
592 utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
593 '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
594 '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
595 '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
596 '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
597 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
598 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
599 '\u3044\u307e\u3059\u3002')
600 h = Header(g_head, g, header_name='Subject')
601 h.append(cz_head, cz)
602 h.append(utf8_head, utf8)
603 msg = Message()
604 msg['Subject'] = h
605 sfp = StringIO()
606 g = Generator(sfp)
607 g.flatten(msg)
608 eq(sfp.getvalue(), """\
Guido van Rossum9604e662007-08-30 03:46:43 +0000609Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderb?=
610 =?iso-8859-1?q?and_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen?=
611 =?iso-8859-1?q?_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef?=
612 =?iso-8859-1?q?=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hrouti?=
613 =?iso-8859-2?q?ly_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
614 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
615 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn44Gf44KJ?=
616 =?utf-8?b?44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFzIE51bnN0dWNr?=
617 =?utf-8?b?IGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5kIGRhcyBPZGVyIGRp?=
618 =?utf-8?b?ZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIDjgaPjgabjgYTjgb7jgZk=?=
619 =?utf-8?b?44CC?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000620
621""")
Guido van Rossum9604e662007-08-30 03:46:43 +0000622 eq(h.encode(maxlinelen=76), """\
623=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerde?=
624 =?iso-8859-1?q?rband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndis?=
625 =?iso-8859-1?q?chen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klinge?=
626 =?iso-8859-1?q?n_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se?=
627 =?iso-8859-2?q?_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
628 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb?=
629 =?utf-8?b?44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go?=
630 =?utf-8?b?44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBp?=
631 =?utf-8?b?c3QgZGFzIE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWlo?=
632 =?utf-8?b?ZXJodW5kIGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI0=?=
633 =?utf-8?b?44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000634
635 def test_long_header_encode(self):
636 eq = self.ndiffAssertEqual
637 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
638 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
639 header_name='X-Foobar-Spoink-Defrobnit')
640 eq(h.encode(), '''\
641wasnipoop; giraffes="very-long-necked-animals";
642 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
643
644 def test_long_header_encode_with_tab_continuation_is_just_a_hint(self):
645 eq = self.ndiffAssertEqual
646 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
647 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
648 header_name='X-Foobar-Spoink-Defrobnit',
649 continuation_ws='\t')
650 eq(h.encode(), '''\
651wasnipoop; giraffes="very-long-necked-animals";
652 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
653
654 def test_long_header_encode_with_tab_continuation(self):
655 eq = self.ndiffAssertEqual
656 h = Header('wasnipoop; giraffes="very-long-necked-animals";\t'
657 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
658 header_name='X-Foobar-Spoink-Defrobnit',
659 continuation_ws='\t')
660 eq(h.encode(), '''\
661wasnipoop; giraffes="very-long-necked-animals";
662\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
663
664 def test_header_splitter(self):
665 eq = self.ndiffAssertEqual
666 msg = MIMEText('')
667 # It'd be great if we could use add_header() here, but that doesn't
668 # guarantee an order of the parameters.
669 msg['X-Foobar-Spoink-Defrobnit'] = (
670 'wasnipoop; giraffes="very-long-necked-animals"; '
671 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
672 sfp = StringIO()
673 g = Generator(sfp)
674 g.flatten(msg)
675 eq(sfp.getvalue(), '''\
676Content-Type: text/plain; charset="us-ascii"
677MIME-Version: 1.0
678Content-Transfer-Encoding: 7bit
679X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
680 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
681
682''')
683
684 def test_no_semis_header_splitter(self):
685 eq = self.ndiffAssertEqual
686 msg = Message()
687 msg['From'] = 'test@dom.ain'
688 msg['References'] = SPACE.join('<%d@dom.ain>' % i for i in range(10))
689 msg.set_payload('Test')
690 sfp = StringIO()
691 g = Generator(sfp)
692 g.flatten(msg)
693 eq(sfp.getvalue(), """\
694From: test@dom.ain
695References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
696 <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
697
698Test""")
699
700 def test_no_split_long_header(self):
701 eq = self.ndiffAssertEqual
702 hstr = 'References: ' + 'x' * 80
Guido van Rossum9604e662007-08-30 03:46:43 +0000703 h = Header(hstr)
704 # These come on two lines because Headers are really field value
705 # classes and don't really know about their field names.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000706 eq(h.encode(), """\
Guido van Rossum9604e662007-08-30 03:46:43 +0000707References:
708 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
709 h = Header('x' * 80)
710 eq(h.encode(), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000711
712 def test_splitting_multiple_long_lines(self):
713 eq = self.ndiffAssertEqual
714 hstr = """\
715from 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)
716\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)
717\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)
718"""
719 h = Header(hstr, continuation_ws='\t')
720 eq(h.encode(), """\
721from babylon.socal-raves.org (localhost [127.0.0.1]);
722 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
723 for <mailman-admin@babylon.socal-raves.org>;
724 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
725\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
726 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
727 for <mailman-admin@babylon.socal-raves.org>;
728 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
729\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
730 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
731 for <mailman-admin@babylon.socal-raves.org>;
732 Sat, 2 Feb 2002 17:00:06 -0800 (PST)""")
733
734 def test_splitting_first_line_only_is_long(self):
735 eq = self.ndiffAssertEqual
736 hstr = """\
737from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
738\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
739\tid 17k4h5-00034i-00
740\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
741 h = Header(hstr, maxlinelen=78, header_name='Received',
742 continuation_ws='\t')
743 eq(h.encode(), """\
744from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
745 helo=cthulhu.gerg.ca)
746\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
747\tid 17k4h5-00034i-00
748\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
749
750 def test_long_8bit_header(self):
751 eq = self.ndiffAssertEqual
752 msg = Message()
753 h = Header('Britische Regierung gibt', 'iso-8859-1',
754 header_name='Subject')
755 h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
Guido van Rossum9604e662007-08-30 03:46:43 +0000756 eq(h.encode(maxlinelen=76), """\
757=?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
758 =?iso-8859-1?q?hore-Windkraftprojekte?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000759 msg['Subject'] = h
Guido van Rossum9604e662007-08-30 03:46:43 +0000760 eq(msg.as_string(maxheaderlen=76), """\
761Subject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
762 =?iso-8859-1?q?hore-Windkraftprojekte?=
763
764""")
765 eq(msg.as_string(maxheaderlen=0), """\
766Subject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offshore-Windkraftprojekte?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000767
768""")
769
770 def test_long_8bit_header_no_charset(self):
771 eq = self.ndiffAssertEqual
772 msg = Message()
Barry Warsaw8c571042007-08-30 19:17:18 +0000773 header_string = ('Britische Regierung gibt gr\xfcnes Licht '
774 'f\xfcr Offshore-Windkraftprojekte '
775 '<a-very-long-address@example.com>')
776 msg['Reply-To'] = header_string
777 self.assertRaises(UnicodeEncodeError, msg.as_string)
778 msg = Message()
779 msg['Reply-To'] = Header(header_string, 'utf-8',
780 header_name='Reply-To')
781 eq(msg.as_string(maxheaderlen=78), """\
782Reply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?=
783 =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000784
785""")
786
787 def test_long_to_header(self):
788 eq = self.ndiffAssertEqual
789 to = ('"Someone Test #A" <someone@eecs.umich.edu>,'
790 '<someone@eecs.umich.edu>,'
791 '"Someone Test #B" <someone@umich.edu>, '
792 '"Someone Test #C" <someone@eecs.umich.edu>, '
793 '"Someone Test #D" <someone@eecs.umich.edu>')
794 msg = Message()
795 msg['To'] = to
796 eq(msg.as_string(maxheaderlen=78), '''\
Guido van Rossum9604e662007-08-30 03:46:43 +0000797To: "Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,
Barry Warsaw70d61ce2009-03-30 23:12:30 +0000798 "Someone Test #B" <someone@umich.edu>,
Guido van Rossum9604e662007-08-30 03:46:43 +0000799 "Someone Test #C" <someone@eecs.umich.edu>,
800 "Someone Test #D" <someone@eecs.umich.edu>
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000801
802''')
803
804 def test_long_line_after_append(self):
805 eq = self.ndiffAssertEqual
806 s = 'This is an example of string which has almost the limit of header length.'
807 h = Header(s)
808 h.append('Add another line.')
Guido van Rossum9604e662007-08-30 03:46:43 +0000809 eq(h.encode(maxlinelen=76), """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000810This is an example of string which has almost the limit of header length.
811 Add another line.""")
812
813 def test_shorter_line_with_append(self):
814 eq = self.ndiffAssertEqual
815 s = 'This is a shorter line.'
816 h = Header(s)
817 h.append('Add another sentence. (Surprise?)')
818 eq(h.encode(),
819 'This is a shorter line. Add another sentence. (Surprise?)')
820
821 def test_long_field_name(self):
822 eq = self.ndiffAssertEqual
823 fn = 'X-Very-Very-Very-Long-Header-Name'
Guido van Rossum9604e662007-08-30 03:46:43 +0000824 gs = ('Die Mieter treten hier ein werden mit einem Foerderband '
825 'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
826 'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
827 'bef\xf6rdert. ')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000828 h = Header(gs, 'iso-8859-1', header_name=fn)
829 # BAW: this seems broken because the first line is too long
Guido van Rossum9604e662007-08-30 03:46:43 +0000830 eq(h.encode(maxlinelen=76), """\
831=?iso-8859-1?q?Die_Mieter_treten_hier_e?=
832 =?iso-8859-1?q?in_werden_mit_einem_Foerderband_komfortabel_den_Korridor_e?=
833 =?iso-8859-1?q?ntlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_ge?=
834 =?iso-8859-1?q?gen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000835
836 def test_long_received_header(self):
837 h = ('from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) '
838 'by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; '
839 'Wed, 05 Mar 2003 18:10:18 -0700')
840 msg = Message()
841 msg['Received-1'] = Header(h, continuation_ws='\t')
842 msg['Received-2'] = h
Barry Warsawbef9d212007-08-31 10:55:37 +0000843 # This should be splitting on spaces not semicolons.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000844 self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
Barry Warsawbef9d212007-08-31 10:55:37 +0000845Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
846 Wed, 05 Mar 2003 18:10:18 -0700
847Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
848 Wed, 05 Mar 2003 18:10:18 -0700
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000849
850""")
851
852 def test_string_headerinst_eq(self):
853 h = ('<15975.17901.207240.414604@sgigritzmann1.mathematik.'
854 'tu-muenchen.de> (David Bremner\'s message of '
855 '"Thu, 6 Mar 2003 13:58:21 +0100")')
856 msg = Message()
857 msg['Received-1'] = Header(h, header_name='Received-1',
858 continuation_ws='\t')
859 msg['Received-2'] = h
Barry Warsawbef9d212007-08-31 10:55:37 +0000860 # XXX This should be splitting on spaces not commas.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000861 self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
Barry Warsawbef9d212007-08-31 10:55:37 +0000862Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu,
863 6 Mar 2003 13:58:21 +0100\")
864Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu,
865 6 Mar 2003 13:58:21 +0100\")
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000866
867""")
868
869 def test_long_unbreakable_lines_with_continuation(self):
870 eq = self.ndiffAssertEqual
871 msg = Message()
872 t = """\
873iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
874 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
875 msg['Face-1'] = t
876 msg['Face-2'] = Header(t, header_name='Face-2')
Barry Warsawbef9d212007-08-31 10:55:37 +0000877 # XXX This splitting is all wrong. It the first value line should be
878 # snug against the field name.
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000879 eq(msg.as_string(maxheaderlen=78), """\
Barry Warsawc5a6a302007-08-31 11:19:21 +0000880Face-1:\x20
Barry Warsaw70d61ce2009-03-30 23:12:30 +0000881 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000882 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
Barry Warsawc5a6a302007-08-31 11:19:21 +0000883Face-2:\x20
Barry Warsawbef9d212007-08-31 10:55:37 +0000884 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000885 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
886
887""")
888
889 def test_another_long_multiline_header(self):
890 eq = self.ndiffAssertEqual
891 m = ('Received: from siimage.com '
892 '([172.25.1.3]) by zima.siliconimage.com with '
Guido van Rossum9604e662007-08-30 03:46:43 +0000893 'Microsoft SMTPSVC(5.0.2195.4905); '
894 'Wed, 16 Oct 2002 07:41:11 -0700')
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000895 msg = email.message_from_string(m)
896 eq(msg.as_string(maxheaderlen=78), '''\
Barry Warsawbef9d212007-08-31 10:55:37 +0000897Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
898 Wed, 16 Oct 2002 07:41:11 -0700
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000899
900''')
901
902 def test_long_lines_with_different_header(self):
903 eq = self.ndiffAssertEqual
904 h = ('List-Unsubscribe: '
905 '<http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,'
906 ' <mailto:spamassassin-talk-request@lists.sourceforge.net'
907 '?subject=unsubscribe>')
908 msg = Message()
909 msg['List'] = h
910 msg['List'] = Header(h, header_name='List')
911 eq(msg.as_string(maxheaderlen=78), """\
912List: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
Barry Warsawbef9d212007-08-31 10:55:37 +0000913 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000914List: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
Barry Warsawbef9d212007-08-31 10:55:37 +0000915 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000916
917""")
918
919
920
921# Test mangling of "From " lines in the body of a message
922class TestFromMangling(unittest.TestCase):
923 def setUp(self):
924 self.msg = Message()
925 self.msg['From'] = 'aaa@bbb.org'
926 self.msg.set_payload("""\
927From the desk of A.A.A.:
928Blah blah blah
929""")
930
931 def test_mangled_from(self):
932 s = StringIO()
933 g = Generator(s, mangle_from_=True)
934 g.flatten(self.msg)
935 self.assertEqual(s.getvalue(), """\
936From: aaa@bbb.org
937
938>From the desk of A.A.A.:
939Blah blah blah
940""")
941
942 def test_dont_mangle_from(self):
943 s = StringIO()
944 g = Generator(s, mangle_from_=False)
945 g.flatten(self.msg)
946 self.assertEqual(s.getvalue(), """\
947From: aaa@bbb.org
948
949From the desk of A.A.A.:
950Blah blah blah
951""")
952
953
954
955# Test the basic MIMEAudio class
956class TestMIMEAudio(unittest.TestCase):
957 def setUp(self):
958 # Make sure we pick up the audiotest.au that lives in email/test/data.
959 # In Python, there's an audiotest.au living in Lib/test but that isn't
960 # included in some binary distros that don't include the test
961 # package. The trailing empty string on the .join() is significant
962 # since findfile() will do a dirname().
963 datadir = os.path.join(os.path.dirname(landmark), 'data', '')
964 with open(findfile('audiotest.au', datadir), 'rb') as fp:
965 self._audiodata = fp.read()
966 self._au = MIMEAudio(self._audiodata)
967
968 def test_guess_minor_type(self):
969 self.assertEqual(self._au.get_content_type(), 'audio/basic')
970
971 def test_encoding(self):
972 payload = self._au.get_payload()
Georg Brandl706824f2009-06-04 09:42:55 +0000973 self.assertEqual(base64.decodebytes(payload), self._audiodata)
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000974
975 def test_checkSetMinor(self):
976 au = MIMEAudio(self._audiodata, 'fish')
977 self.assertEqual(au.get_content_type(), 'audio/fish')
978
979 def test_add_header(self):
980 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000981 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +0000982 self._au.add_header('Content-Disposition', 'attachment',
983 filename='audiotest.au')
984 eq(self._au['content-disposition'],
985 'attachment; filename="audiotest.au"')
986 eq(self._au.get_params(header='content-disposition'),
987 [('attachment', ''), ('filename', 'audiotest.au')])
988 eq(self._au.get_param('filename', header='content-disposition'),
989 'audiotest.au')
990 missing = []
991 eq(self._au.get_param('attachment', header='content-disposition'), '')
992 unless(self._au.get_param('foo', failobj=missing,
993 header='content-disposition') is missing)
994 # Try some missing stuff
995 unless(self._au.get_param('foobar', missing) is missing)
996 unless(self._au.get_param('attachment', missing,
997 header='foobar') is missing)
998
999
1000
1001# Test the basic MIMEImage class
1002class TestMIMEImage(unittest.TestCase):
1003 def setUp(self):
1004 with openfile('PyBanner048.gif', 'rb') as fp:
1005 self._imgdata = fp.read()
1006 self._im = MIMEImage(self._imgdata)
1007
1008 def test_guess_minor_type(self):
1009 self.assertEqual(self._im.get_content_type(), 'image/gif')
1010
1011 def test_encoding(self):
1012 payload = self._im.get_payload()
Georg Brandl706824f2009-06-04 09:42:55 +00001013 self.assertEqual(base64.decodebytes(payload), self._imgdata)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001014
1015 def test_checkSetMinor(self):
1016 im = MIMEImage(self._imgdata, 'fish')
1017 self.assertEqual(im.get_content_type(), 'image/fish')
1018
1019 def test_add_header(self):
1020 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001021 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001022 self._im.add_header('Content-Disposition', 'attachment',
1023 filename='dingusfish.gif')
1024 eq(self._im['content-disposition'],
1025 'attachment; filename="dingusfish.gif"')
1026 eq(self._im.get_params(header='content-disposition'),
1027 [('attachment', ''), ('filename', 'dingusfish.gif')])
1028 eq(self._im.get_param('filename', header='content-disposition'),
1029 'dingusfish.gif')
1030 missing = []
1031 eq(self._im.get_param('attachment', header='content-disposition'), '')
1032 unless(self._im.get_param('foo', failobj=missing,
1033 header='content-disposition') is missing)
1034 # Try some missing stuff
1035 unless(self._im.get_param('foobar', missing) is missing)
1036 unless(self._im.get_param('attachment', missing,
1037 header='foobar') is missing)
1038
1039
1040
1041# Test the basic MIMEApplication class
1042class TestMIMEApplication(unittest.TestCase):
1043 def test_headers(self):
1044 eq = self.assertEqual
Barry Warsaw8b2af272007-08-31 03:04:26 +00001045 msg = MIMEApplication(b'\xfa\xfb\xfc\xfd\xfe\xff')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001046 eq(msg.get_content_type(), 'application/octet-stream')
1047 eq(msg['content-transfer-encoding'], 'base64')
1048
1049 def test_body(self):
1050 eq = self.assertEqual
Barry Warsaw8c571042007-08-30 19:17:18 +00001051 bytes = b'\xfa\xfb\xfc\xfd\xfe\xff'
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001052 msg = MIMEApplication(bytes)
Barry Warsaw8c571042007-08-30 19:17:18 +00001053 eq(msg.get_payload(), b'+vv8/f7/')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001054 eq(msg.get_payload(decode=True), bytes)
1055
1056
1057
1058# Test the basic MIMEText class
1059class TestMIMEText(unittest.TestCase):
1060 def setUp(self):
1061 self._msg = MIMEText('hello there')
1062
1063 def test_types(self):
1064 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001065 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001066 eq(self._msg.get_content_type(), 'text/plain')
1067 eq(self._msg.get_param('charset'), 'us-ascii')
1068 missing = []
1069 unless(self._msg.get_param('foobar', missing) is missing)
1070 unless(self._msg.get_param('charset', missing, header='foobar')
1071 is missing)
1072
1073 def test_payload(self):
1074 self.assertEqual(self._msg.get_payload(), 'hello there')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001075 self.assertTrue(not self._msg.is_multipart())
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001076
1077 def test_charset(self):
1078 eq = self.assertEqual
1079 msg = MIMEText('hello there', _charset='us-ascii')
1080 eq(msg.get_charset().input_charset, 'us-ascii')
1081 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1082
R. David Murray850fc852010-06-03 01:58:28 +00001083 def test_7bit_input(self):
1084 eq = self.assertEqual
1085 msg = MIMEText('hello there', _charset='us-ascii')
1086 eq(msg.get_charset().input_charset, 'us-ascii')
1087 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1088
1089 def test_7bit_input_no_charset(self):
1090 eq = self.assertEqual
1091 msg = MIMEText('hello there')
1092 eq(msg.get_charset(), 'us-ascii')
1093 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1094 self.assertTrue('hello there' in msg.as_string())
1095
1096 def test_utf8_input(self):
1097 teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1098 eq = self.assertEqual
1099 msg = MIMEText(teststr, _charset='utf-8')
1100 eq(msg.get_charset().output_charset, 'utf-8')
1101 eq(msg['content-type'], 'text/plain; charset="utf-8"')
1102 eq(msg.get_payload(decode=True), teststr.encode('utf-8'))
1103
1104 @unittest.skip("can't fix because of backward compat in email5, "
1105 "will fix in email6")
1106 def test_utf8_input_no_charset(self):
1107 teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1108 self.assertRaises(UnicodeEncodeError, MIMEText, teststr)
1109
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001110
1111
1112# Test complicated multipart/* messages
1113class TestMultipart(TestEmailBase):
1114 def setUp(self):
1115 with openfile('PyBanner048.gif', 'rb') as fp:
1116 data = fp.read()
1117 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
1118 image = MIMEImage(data, name='dingusfish.gif')
1119 image.add_header('content-disposition', 'attachment',
1120 filename='dingusfish.gif')
1121 intro = MIMEText('''\
1122Hi there,
1123
1124This is the dingus fish.
1125''')
1126 container.attach(intro)
1127 container.attach(image)
1128 container['From'] = 'Barry <barry@digicool.com>'
1129 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1130 container['Subject'] = 'Here is your dingus fish'
1131
1132 now = 987809702.54848599
1133 timetuple = time.localtime(now)
1134 if timetuple[-1] == 0:
1135 tzsecs = time.timezone
1136 else:
1137 tzsecs = time.altzone
1138 if tzsecs > 0:
1139 sign = '-'
1140 else:
1141 sign = '+'
1142 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
1143 container['Date'] = time.strftime(
1144 '%a, %d %b %Y %H:%M:%S',
1145 time.localtime(now)) + tzoffset
1146 self._msg = container
1147 self._im = image
1148 self._txt = intro
1149
1150 def test_hierarchy(self):
1151 # convenience
1152 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001153 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001154 raises = self.assertRaises
1155 # tests
1156 m = self._msg
1157 unless(m.is_multipart())
1158 eq(m.get_content_type(), 'multipart/mixed')
1159 eq(len(m.get_payload()), 2)
1160 raises(IndexError, m.get_payload, 2)
1161 m0 = m.get_payload(0)
1162 m1 = m.get_payload(1)
1163 unless(m0 is self._txt)
1164 unless(m1 is self._im)
1165 eq(m.get_payload(), [m0, m1])
1166 unless(not m0.is_multipart())
1167 unless(not m1.is_multipart())
1168
1169 def test_empty_multipart_idempotent(self):
1170 text = """\
1171Content-Type: multipart/mixed; boundary="BOUNDARY"
1172MIME-Version: 1.0
1173Subject: A subject
1174To: aperson@dom.ain
1175From: bperson@dom.ain
1176
1177
1178--BOUNDARY
1179
1180
1181--BOUNDARY--
1182"""
1183 msg = Parser().parsestr(text)
1184 self.ndiffAssertEqual(text, msg.as_string())
1185
1186 def test_no_parts_in_a_multipart_with_none_epilogue(self):
1187 outer = MIMEBase('multipart', 'mixed')
1188 outer['Subject'] = 'A subject'
1189 outer['To'] = 'aperson@dom.ain'
1190 outer['From'] = 'bperson@dom.ain'
1191 outer.set_boundary('BOUNDARY')
1192 self.ndiffAssertEqual(outer.as_string(), '''\
1193Content-Type: multipart/mixed; boundary="BOUNDARY"
1194MIME-Version: 1.0
1195Subject: A subject
1196To: aperson@dom.ain
1197From: bperson@dom.ain
1198
1199--BOUNDARY
1200
1201--BOUNDARY--''')
1202
1203 def test_no_parts_in_a_multipart_with_empty_epilogue(self):
1204 outer = MIMEBase('multipart', 'mixed')
1205 outer['Subject'] = 'A subject'
1206 outer['To'] = 'aperson@dom.ain'
1207 outer['From'] = 'bperson@dom.ain'
1208 outer.preamble = ''
1209 outer.epilogue = ''
1210 outer.set_boundary('BOUNDARY')
1211 self.ndiffAssertEqual(outer.as_string(), '''\
1212Content-Type: multipart/mixed; boundary="BOUNDARY"
1213MIME-Version: 1.0
1214Subject: A subject
1215To: aperson@dom.ain
1216From: bperson@dom.ain
1217
1218
1219--BOUNDARY
1220
1221--BOUNDARY--
1222''')
1223
1224 def test_one_part_in_a_multipart(self):
1225 eq = self.ndiffAssertEqual
1226 outer = MIMEBase('multipart', 'mixed')
1227 outer['Subject'] = 'A subject'
1228 outer['To'] = 'aperson@dom.ain'
1229 outer['From'] = 'bperson@dom.ain'
1230 outer.set_boundary('BOUNDARY')
1231 msg = MIMEText('hello world')
1232 outer.attach(msg)
1233 eq(outer.as_string(), '''\
1234Content-Type: multipart/mixed; boundary="BOUNDARY"
1235MIME-Version: 1.0
1236Subject: A subject
1237To: aperson@dom.ain
1238From: bperson@dom.ain
1239
1240--BOUNDARY
1241Content-Type: text/plain; charset="us-ascii"
1242MIME-Version: 1.0
1243Content-Transfer-Encoding: 7bit
1244
1245hello world
1246--BOUNDARY--''')
1247
1248 def test_seq_parts_in_a_multipart_with_empty_preamble(self):
1249 eq = self.ndiffAssertEqual
1250 outer = MIMEBase('multipart', 'mixed')
1251 outer['Subject'] = 'A subject'
1252 outer['To'] = 'aperson@dom.ain'
1253 outer['From'] = 'bperson@dom.ain'
1254 outer.preamble = ''
1255 msg = MIMEText('hello world')
1256 outer.attach(msg)
1257 outer.set_boundary('BOUNDARY')
1258 eq(outer.as_string(), '''\
1259Content-Type: multipart/mixed; boundary="BOUNDARY"
1260MIME-Version: 1.0
1261Subject: A subject
1262To: aperson@dom.ain
1263From: bperson@dom.ain
1264
1265
1266--BOUNDARY
1267Content-Type: text/plain; charset="us-ascii"
1268MIME-Version: 1.0
1269Content-Transfer-Encoding: 7bit
1270
1271hello world
1272--BOUNDARY--''')
1273
1274
1275 def test_seq_parts_in_a_multipart_with_none_preamble(self):
1276 eq = self.ndiffAssertEqual
1277 outer = MIMEBase('multipart', 'mixed')
1278 outer['Subject'] = 'A subject'
1279 outer['To'] = 'aperson@dom.ain'
1280 outer['From'] = 'bperson@dom.ain'
1281 outer.preamble = None
1282 msg = MIMEText('hello world')
1283 outer.attach(msg)
1284 outer.set_boundary('BOUNDARY')
1285 eq(outer.as_string(), '''\
1286Content-Type: multipart/mixed; boundary="BOUNDARY"
1287MIME-Version: 1.0
1288Subject: A subject
1289To: aperson@dom.ain
1290From: bperson@dom.ain
1291
1292--BOUNDARY
1293Content-Type: text/plain; charset="us-ascii"
1294MIME-Version: 1.0
1295Content-Transfer-Encoding: 7bit
1296
1297hello world
1298--BOUNDARY--''')
1299
1300
1301 def test_seq_parts_in_a_multipart_with_none_epilogue(self):
1302 eq = self.ndiffAssertEqual
1303 outer = MIMEBase('multipart', 'mixed')
1304 outer['Subject'] = 'A subject'
1305 outer['To'] = 'aperson@dom.ain'
1306 outer['From'] = 'bperson@dom.ain'
1307 outer.epilogue = None
1308 msg = MIMEText('hello world')
1309 outer.attach(msg)
1310 outer.set_boundary('BOUNDARY')
1311 eq(outer.as_string(), '''\
1312Content-Type: multipart/mixed; boundary="BOUNDARY"
1313MIME-Version: 1.0
1314Subject: A subject
1315To: aperson@dom.ain
1316From: bperson@dom.ain
1317
1318--BOUNDARY
1319Content-Type: text/plain; charset="us-ascii"
1320MIME-Version: 1.0
1321Content-Transfer-Encoding: 7bit
1322
1323hello world
1324--BOUNDARY--''')
1325
1326
1327 def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
1328 eq = self.ndiffAssertEqual
1329 outer = MIMEBase('multipart', 'mixed')
1330 outer['Subject'] = 'A subject'
1331 outer['To'] = 'aperson@dom.ain'
1332 outer['From'] = 'bperson@dom.ain'
1333 outer.epilogue = ''
1334 msg = MIMEText('hello world')
1335 outer.attach(msg)
1336 outer.set_boundary('BOUNDARY')
1337 eq(outer.as_string(), '''\
1338Content-Type: multipart/mixed; boundary="BOUNDARY"
1339MIME-Version: 1.0
1340Subject: A subject
1341To: aperson@dom.ain
1342From: bperson@dom.ain
1343
1344--BOUNDARY
1345Content-Type: text/plain; charset="us-ascii"
1346MIME-Version: 1.0
1347Content-Transfer-Encoding: 7bit
1348
1349hello world
1350--BOUNDARY--
1351''')
1352
1353
1354 def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
1355 eq = self.ndiffAssertEqual
1356 outer = MIMEBase('multipart', 'mixed')
1357 outer['Subject'] = 'A subject'
1358 outer['To'] = 'aperson@dom.ain'
1359 outer['From'] = 'bperson@dom.ain'
1360 outer.epilogue = '\n'
1361 msg = MIMEText('hello world')
1362 outer.attach(msg)
1363 outer.set_boundary('BOUNDARY')
1364 eq(outer.as_string(), '''\
1365Content-Type: multipart/mixed; boundary="BOUNDARY"
1366MIME-Version: 1.0
1367Subject: A subject
1368To: aperson@dom.ain
1369From: bperson@dom.ain
1370
1371--BOUNDARY
1372Content-Type: text/plain; charset="us-ascii"
1373MIME-Version: 1.0
1374Content-Transfer-Encoding: 7bit
1375
1376hello world
1377--BOUNDARY--
1378
1379''')
1380
1381 def test_message_external_body(self):
1382 eq = self.assertEqual
1383 msg = self._msgobj('msg_36.txt')
1384 eq(len(msg.get_payload()), 2)
1385 msg1 = msg.get_payload(1)
1386 eq(msg1.get_content_type(), 'multipart/alternative')
1387 eq(len(msg1.get_payload()), 2)
1388 for subpart in msg1.get_payload():
1389 eq(subpart.get_content_type(), 'message/external-body')
1390 eq(len(subpart.get_payload()), 1)
1391 subsubpart = subpart.get_payload(0)
1392 eq(subsubpart.get_content_type(), 'text/plain')
1393
1394 def test_double_boundary(self):
1395 # msg_37.txt is a multipart that contains two dash-boundary's in a
1396 # row. Our interpretation of RFC 2046 calls for ignoring the second
1397 # and subsequent boundaries.
1398 msg = self._msgobj('msg_37.txt')
1399 self.assertEqual(len(msg.get_payload()), 3)
1400
1401 def test_nested_inner_contains_outer_boundary(self):
1402 eq = self.ndiffAssertEqual
1403 # msg_38.txt has an inner part that contains outer boundaries. My
1404 # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
1405 # these are illegal and should be interpreted as unterminated inner
1406 # parts.
1407 msg = self._msgobj('msg_38.txt')
1408 sfp = StringIO()
1409 iterators._structure(msg, sfp)
1410 eq(sfp.getvalue(), """\
1411multipart/mixed
1412 multipart/mixed
1413 multipart/alternative
1414 text/plain
1415 text/plain
1416 text/plain
1417 text/plain
1418""")
1419
1420 def test_nested_with_same_boundary(self):
1421 eq = self.ndiffAssertEqual
1422 # msg 39.txt is similarly evil in that it's got inner parts that use
1423 # the same boundary as outer parts. Again, I believe the way this is
1424 # parsed is closest to the spirit of RFC 2046
1425 msg = self._msgobj('msg_39.txt')
1426 sfp = StringIO()
1427 iterators._structure(msg, sfp)
1428 eq(sfp.getvalue(), """\
1429multipart/mixed
1430 multipart/mixed
1431 multipart/alternative
1432 application/octet-stream
1433 application/octet-stream
1434 text/plain
1435""")
1436
1437 def test_boundary_in_non_multipart(self):
1438 msg = self._msgobj('msg_40.txt')
1439 self.assertEqual(msg.as_string(), '''\
1440MIME-Version: 1.0
1441Content-Type: text/html; boundary="--961284236552522269"
1442
1443----961284236552522269
1444Content-Type: text/html;
1445Content-Transfer-Encoding: 7Bit
1446
1447<html></html>
1448
1449----961284236552522269--
1450''')
1451
1452 def test_boundary_with_leading_space(self):
1453 eq = self.assertEqual
1454 msg = email.message_from_string('''\
1455MIME-Version: 1.0
1456Content-Type: multipart/mixed; boundary=" XXXX"
1457
1458-- XXXX
1459Content-Type: text/plain
1460
1461
1462-- XXXX
1463Content-Type: text/plain
1464
1465-- XXXX--
1466''')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001467 self.assertTrue(msg.is_multipart())
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001468 eq(msg.get_boundary(), ' XXXX')
1469 eq(len(msg.get_payload()), 2)
1470
1471 def test_boundary_without_trailing_newline(self):
1472 m = Parser().parsestr("""\
1473Content-Type: multipart/mixed; boundary="===============0012394164=="
1474MIME-Version: 1.0
1475
1476--===============0012394164==
1477Content-Type: image/file1.jpg
1478MIME-Version: 1.0
1479Content-Transfer-Encoding: base64
1480
1481YXNkZg==
1482--===============0012394164==--""")
1483 self.assertEquals(m.get_payload(0).get_payload(), 'YXNkZg==')
1484
1485
1486
1487# Test some badly formatted messages
1488class TestNonConformant(TestEmailBase):
1489 def test_parse_missing_minor_type(self):
1490 eq = self.assertEqual
1491 msg = self._msgobj('msg_14.txt')
1492 eq(msg.get_content_type(), 'text/plain')
1493 eq(msg.get_content_maintype(), 'text')
1494 eq(msg.get_content_subtype(), 'plain')
1495
1496 def test_same_boundary_inner_outer(self):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001497 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001498 msg = self._msgobj('msg_15.txt')
1499 # XXX We can probably eventually do better
1500 inner = msg.get_payload(0)
1501 unless(hasattr(inner, 'defects'))
1502 self.assertEqual(len(inner.defects), 1)
1503 unless(isinstance(inner.defects[0],
1504 errors.StartBoundaryNotFoundDefect))
1505
1506 def test_multipart_no_boundary(self):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001507 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001508 msg = self._msgobj('msg_25.txt')
1509 unless(isinstance(msg.get_payload(), str))
1510 self.assertEqual(len(msg.defects), 2)
1511 unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
1512 unless(isinstance(msg.defects[1],
1513 errors.MultipartInvariantViolationDefect))
1514
1515 def test_invalid_content_type(self):
1516 eq = self.assertEqual
1517 neq = self.ndiffAssertEqual
1518 msg = Message()
1519 # RFC 2045, $5.2 says invalid yields text/plain
1520 msg['Content-Type'] = 'text'
1521 eq(msg.get_content_maintype(), 'text')
1522 eq(msg.get_content_subtype(), 'plain')
1523 eq(msg.get_content_type(), 'text/plain')
1524 # Clear the old value and try something /really/ invalid
1525 del msg['content-type']
1526 msg['Content-Type'] = 'foo'
1527 eq(msg.get_content_maintype(), 'text')
1528 eq(msg.get_content_subtype(), 'plain')
1529 eq(msg.get_content_type(), 'text/plain')
1530 # Still, make sure that the message is idempotently generated
1531 s = StringIO()
1532 g = Generator(s)
1533 g.flatten(msg)
1534 neq(s.getvalue(), 'Content-Type: foo\n\n')
1535
1536 def test_no_start_boundary(self):
1537 eq = self.ndiffAssertEqual
1538 msg = self._msgobj('msg_31.txt')
1539 eq(msg.get_payload(), """\
1540--BOUNDARY
1541Content-Type: text/plain
1542
1543message 1
1544
1545--BOUNDARY
1546Content-Type: text/plain
1547
1548message 2
1549
1550--BOUNDARY--
1551""")
1552
1553 def test_no_separating_blank_line(self):
1554 eq = self.ndiffAssertEqual
1555 msg = self._msgobj('msg_35.txt')
1556 eq(msg.as_string(), """\
1557From: aperson@dom.ain
1558To: bperson@dom.ain
1559Subject: here's something interesting
1560
1561counter to RFC 2822, there's no separating newline here
1562""")
1563
1564 def test_lying_multipart(self):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001565 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001566 msg = self._msgobj('msg_41.txt')
1567 unless(hasattr(msg, 'defects'))
1568 self.assertEqual(len(msg.defects), 2)
1569 unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
1570 unless(isinstance(msg.defects[1],
1571 errors.MultipartInvariantViolationDefect))
1572
1573 def test_missing_start_boundary(self):
1574 outer = self._msgobj('msg_42.txt')
1575 # The message structure is:
1576 #
1577 # multipart/mixed
1578 # text/plain
1579 # message/rfc822
1580 # multipart/mixed [*]
1581 #
1582 # [*] This message is missing its start boundary
1583 bad = outer.get_payload(1).get_payload(0)
1584 self.assertEqual(len(bad.defects), 1)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001585 self.assertTrue(isinstance(bad.defects[0],
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001586 errors.StartBoundaryNotFoundDefect))
1587
1588 def test_first_line_is_continuation_header(self):
1589 eq = self.assertEqual
1590 m = ' Line 1\nLine 2\nLine 3'
1591 msg = email.message_from_string(m)
1592 eq(msg.keys(), [])
1593 eq(msg.get_payload(), 'Line 2\nLine 3')
1594 eq(len(msg.defects), 1)
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001595 self.assertTrue(isinstance(msg.defects[0],
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001596 errors.FirstHeaderLineIsContinuationDefect))
1597 eq(msg.defects[0].line, ' Line 1\n')
1598
1599
1600
1601# Test RFC 2047 header encoding and decoding
Guido van Rossum9604e662007-08-30 03:46:43 +00001602class TestRFC2047(TestEmailBase):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001603 def test_rfc2047_multiline(self):
1604 eq = self.assertEqual
1605 s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1606 foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1607 dh = decode_header(s)
1608 eq(dh, [
1609 (b'Re:', None),
1610 (b'r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1611 (b'baz foo bar', None),
1612 (b'r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1613 header = make_header(dh)
1614 eq(str(header),
1615 'Re: r\xe4ksm\xf6rg\xe5s baz foo bar r\xe4ksm\xf6rg\xe5s')
Barry Warsaw00b34222007-08-31 02:35:00 +00001616 self.ndiffAssertEqual(header.encode(maxlinelen=76), """\
Guido van Rossum9604e662007-08-30 03:46:43 +00001617Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?=
1618 =?mac-iceland?q?=9Arg=8Cs?=""")
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001619
1620 def test_whitespace_eater_unicode(self):
1621 eq = self.assertEqual
1622 s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1623 dh = decode_header(s)
1624 eq(dh, [(b'Andr\xe9', 'iso-8859-1'),
1625 (b'Pirard <pirard@dom.ain>', None)])
1626 header = str(make_header(dh))
1627 eq(header, 'Andr\xe9 Pirard <pirard@dom.ain>')
1628
1629 def test_whitespace_eater_unicode_2(self):
1630 eq = self.assertEqual
1631 s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1632 dh = decode_header(s)
1633 eq(dh, [(b'The', None), (b'quick brown fox', 'iso-8859-1'),
1634 (b'jumped over the', None), (b'lazy dog', 'iso-8859-1')])
1635 hu = str(make_header(dh))
1636 eq(hu, 'The quick brown fox jumped over the lazy dog')
1637
1638 def test_rfc2047_missing_whitespace(self):
1639 s = 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
1640 dh = decode_header(s)
1641 self.assertEqual(dh, [(s, None)])
1642
1643 def test_rfc2047_with_whitespace(self):
1644 s = 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
1645 dh = decode_header(s)
1646 self.assertEqual(dh, [(b'Sm', None), (b'\xf6', 'iso-8859-1'),
1647 (b'rg', None), (b'\xe5', 'iso-8859-1'),
1648 (b'sbord', None)])
1649
1650
1651
1652# Test the MIMEMessage class
1653class TestMIMEMessage(TestEmailBase):
1654 def setUp(self):
1655 with openfile('msg_11.txt') as fp:
1656 self._text = fp.read()
1657
1658 def test_type_error(self):
1659 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
1660
1661 def test_valid_argument(self):
1662 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001663 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001664 subject = 'A sub-message'
1665 m = Message()
1666 m['Subject'] = subject
1667 r = MIMEMessage(m)
1668 eq(r.get_content_type(), 'message/rfc822')
1669 payload = r.get_payload()
1670 unless(isinstance(payload, list))
1671 eq(len(payload), 1)
1672 subpart = payload[0]
1673 unless(subpart is m)
1674 eq(subpart['subject'], subject)
1675
1676 def test_bad_multipart(self):
1677 eq = self.assertEqual
1678 msg1 = Message()
1679 msg1['Subject'] = 'subpart 1'
1680 msg2 = Message()
1681 msg2['Subject'] = 'subpart 2'
1682 r = MIMEMessage(msg1)
1683 self.assertRaises(errors.MultipartConversionError, r.attach, msg2)
1684
1685 def test_generate(self):
1686 # First craft the message to be encapsulated
1687 m = Message()
1688 m['Subject'] = 'An enclosed message'
1689 m.set_payload('Here is the body of the message.\n')
1690 r = MIMEMessage(m)
1691 r['Subject'] = 'The enclosing message'
1692 s = StringIO()
1693 g = Generator(s)
1694 g.flatten(r)
1695 self.assertEqual(s.getvalue(), """\
1696Content-Type: message/rfc822
1697MIME-Version: 1.0
1698Subject: The enclosing message
1699
1700Subject: An enclosed message
1701
1702Here is the body of the message.
1703""")
1704
1705 def test_parse_message_rfc822(self):
1706 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001707 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001708 msg = self._msgobj('msg_11.txt')
1709 eq(msg.get_content_type(), 'message/rfc822')
1710 payload = msg.get_payload()
1711 unless(isinstance(payload, list))
1712 eq(len(payload), 1)
1713 submsg = payload[0]
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001714 self.assertTrue(isinstance(submsg, Message))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001715 eq(submsg['subject'], 'An enclosed message')
1716 eq(submsg.get_payload(), 'Here is the body of the message.\n')
1717
1718 def test_dsn(self):
1719 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00001720 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001721 # msg 16 is a Delivery Status Notification, see RFC 1894
1722 msg = self._msgobj('msg_16.txt')
1723 eq(msg.get_content_type(), 'multipart/report')
1724 unless(msg.is_multipart())
1725 eq(len(msg.get_payload()), 3)
1726 # Subpart 1 is a text/plain, human readable section
1727 subpart = msg.get_payload(0)
1728 eq(subpart.get_content_type(), 'text/plain')
1729 eq(subpart.get_payload(), """\
1730This report relates to a message you sent with the following header fields:
1731
1732 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1733 Date: Sun, 23 Sep 2001 20:10:55 -0700
1734 From: "Ian T. Henry" <henryi@oxy.edu>
1735 To: SoCal Raves <scr@socal-raves.org>
1736 Subject: [scr] yeah for Ians!!
1737
1738Your message cannot be delivered to the following recipients:
1739
1740 Recipient address: jangel1@cougar.noc.ucla.edu
1741 Reason: recipient reached disk quota
1742
1743""")
1744 # Subpart 2 contains the machine parsable DSN information. It
1745 # consists of two blocks of headers, represented by two nested Message
1746 # objects.
1747 subpart = msg.get_payload(1)
1748 eq(subpart.get_content_type(), 'message/delivery-status')
1749 eq(len(subpart.get_payload()), 2)
1750 # message/delivery-status should treat each block as a bunch of
1751 # headers, i.e. a bunch of Message objects.
1752 dsn1 = subpart.get_payload(0)
1753 unless(isinstance(dsn1, Message))
1754 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1755 eq(dsn1.get_param('dns', header='reporting-mta'), '')
1756 # Try a missing one <wink>
1757 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1758 dsn2 = subpart.get_payload(1)
1759 unless(isinstance(dsn2, Message))
1760 eq(dsn2['action'], 'failed')
1761 eq(dsn2.get_params(header='original-recipient'),
1762 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1763 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1764 # Subpart 3 is the original message
1765 subpart = msg.get_payload(2)
1766 eq(subpart.get_content_type(), 'message/rfc822')
1767 payload = subpart.get_payload()
1768 unless(isinstance(payload, list))
1769 eq(len(payload), 1)
1770 subsubpart = payload[0]
1771 unless(isinstance(subsubpart, Message))
1772 eq(subsubpart.get_content_type(), 'text/plain')
1773 eq(subsubpart['message-id'],
1774 '<002001c144a6$8752e060$56104586@oxy.edu>')
1775
1776 def test_epilogue(self):
1777 eq = self.ndiffAssertEqual
1778 with openfile('msg_21.txt') as fp:
1779 text = fp.read()
1780 msg = Message()
1781 msg['From'] = 'aperson@dom.ain'
1782 msg['To'] = 'bperson@dom.ain'
1783 msg['Subject'] = 'Test'
1784 msg.preamble = 'MIME message'
1785 msg.epilogue = 'End of MIME message\n'
1786 msg1 = MIMEText('One')
1787 msg2 = MIMEText('Two')
1788 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1789 msg.attach(msg1)
1790 msg.attach(msg2)
1791 sfp = StringIO()
1792 g = Generator(sfp)
1793 g.flatten(msg)
1794 eq(sfp.getvalue(), text)
1795
1796 def test_no_nl_preamble(self):
1797 eq = self.ndiffAssertEqual
1798 msg = Message()
1799 msg['From'] = 'aperson@dom.ain'
1800 msg['To'] = 'bperson@dom.ain'
1801 msg['Subject'] = 'Test'
1802 msg.preamble = 'MIME message'
1803 msg.epilogue = ''
1804 msg1 = MIMEText('One')
1805 msg2 = MIMEText('Two')
1806 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1807 msg.attach(msg1)
1808 msg.attach(msg2)
1809 eq(msg.as_string(), """\
1810From: aperson@dom.ain
1811To: bperson@dom.ain
1812Subject: Test
1813Content-Type: multipart/mixed; boundary="BOUNDARY"
1814
1815MIME message
1816--BOUNDARY
1817Content-Type: text/plain; charset="us-ascii"
1818MIME-Version: 1.0
1819Content-Transfer-Encoding: 7bit
1820
1821One
1822--BOUNDARY
1823Content-Type: text/plain; charset="us-ascii"
1824MIME-Version: 1.0
1825Content-Transfer-Encoding: 7bit
1826
1827Two
1828--BOUNDARY--
1829""")
1830
1831 def test_default_type(self):
1832 eq = self.assertEqual
1833 with openfile('msg_30.txt') as fp:
1834 msg = email.message_from_file(fp)
1835 container1 = msg.get_payload(0)
1836 eq(container1.get_default_type(), 'message/rfc822')
1837 eq(container1.get_content_type(), 'message/rfc822')
1838 container2 = msg.get_payload(1)
1839 eq(container2.get_default_type(), 'message/rfc822')
1840 eq(container2.get_content_type(), 'message/rfc822')
1841 container1a = container1.get_payload(0)
1842 eq(container1a.get_default_type(), 'text/plain')
1843 eq(container1a.get_content_type(), 'text/plain')
1844 container2a = container2.get_payload(0)
1845 eq(container2a.get_default_type(), 'text/plain')
1846 eq(container2a.get_content_type(), 'text/plain')
1847
1848 def test_default_type_with_explicit_container_type(self):
1849 eq = self.assertEqual
1850 with openfile('msg_28.txt') as fp:
1851 msg = email.message_from_file(fp)
1852 container1 = msg.get_payload(0)
1853 eq(container1.get_default_type(), 'message/rfc822')
1854 eq(container1.get_content_type(), 'message/rfc822')
1855 container2 = msg.get_payload(1)
1856 eq(container2.get_default_type(), 'message/rfc822')
1857 eq(container2.get_content_type(), 'message/rfc822')
1858 container1a = container1.get_payload(0)
1859 eq(container1a.get_default_type(), 'text/plain')
1860 eq(container1a.get_content_type(), 'text/plain')
1861 container2a = container2.get_payload(0)
1862 eq(container2a.get_default_type(), 'text/plain')
1863 eq(container2a.get_content_type(), 'text/plain')
1864
1865 def test_default_type_non_parsed(self):
1866 eq = self.assertEqual
1867 neq = self.ndiffAssertEqual
1868 # Set up container
1869 container = MIMEMultipart('digest', 'BOUNDARY')
1870 container.epilogue = ''
1871 # Set up subparts
1872 subpart1a = MIMEText('message 1\n')
1873 subpart2a = MIMEText('message 2\n')
1874 subpart1 = MIMEMessage(subpart1a)
1875 subpart2 = MIMEMessage(subpart2a)
1876 container.attach(subpart1)
1877 container.attach(subpart2)
1878 eq(subpart1.get_content_type(), 'message/rfc822')
1879 eq(subpart1.get_default_type(), 'message/rfc822')
1880 eq(subpart2.get_content_type(), 'message/rfc822')
1881 eq(subpart2.get_default_type(), 'message/rfc822')
1882 neq(container.as_string(0), '''\
1883Content-Type: multipart/digest; boundary="BOUNDARY"
1884MIME-Version: 1.0
1885
1886--BOUNDARY
1887Content-Type: message/rfc822
1888MIME-Version: 1.0
1889
1890Content-Type: text/plain; charset="us-ascii"
1891MIME-Version: 1.0
1892Content-Transfer-Encoding: 7bit
1893
1894message 1
1895
1896--BOUNDARY
1897Content-Type: message/rfc822
1898MIME-Version: 1.0
1899
1900Content-Type: text/plain; charset="us-ascii"
1901MIME-Version: 1.0
1902Content-Transfer-Encoding: 7bit
1903
1904message 2
1905
1906--BOUNDARY--
1907''')
1908 del subpart1['content-type']
1909 del subpart1['mime-version']
1910 del subpart2['content-type']
1911 del subpart2['mime-version']
1912 eq(subpart1.get_content_type(), 'message/rfc822')
1913 eq(subpart1.get_default_type(), 'message/rfc822')
1914 eq(subpart2.get_content_type(), 'message/rfc822')
1915 eq(subpart2.get_default_type(), 'message/rfc822')
1916 neq(container.as_string(0), '''\
1917Content-Type: multipart/digest; boundary="BOUNDARY"
1918MIME-Version: 1.0
1919
1920--BOUNDARY
1921
1922Content-Type: text/plain; charset="us-ascii"
1923MIME-Version: 1.0
1924Content-Transfer-Encoding: 7bit
1925
1926message 1
1927
1928--BOUNDARY
1929
1930Content-Type: text/plain; charset="us-ascii"
1931MIME-Version: 1.0
1932Content-Transfer-Encoding: 7bit
1933
1934message 2
1935
1936--BOUNDARY--
1937''')
1938
1939 def test_mime_attachments_in_constructor(self):
1940 eq = self.assertEqual
1941 text1 = MIMEText('')
1942 text2 = MIMEText('')
1943 msg = MIMEMultipart(_subparts=(text1, text2))
1944 eq(len(msg.get_payload()), 2)
1945 eq(msg.get_payload(0), text1)
1946 eq(msg.get_payload(1), text2)
1947
Christian Heimes587c2bf2008-01-19 16:21:02 +00001948 def test_default_multipart_constructor(self):
1949 msg = MIMEMultipart()
1950 self.assertTrue(msg.is_multipart())
Guido van Rossum8b3febe2007-08-30 01:15:14 +00001951
1952
1953# A general test of parser->model->generator idempotency. IOW, read a message
1954# in, parse it into a message object tree, then without touching the tree,
1955# regenerate the plain text. The original text and the transformed text
1956# should be identical. Note: that we ignore the Unix-From since that may
1957# contain a changed date.
1958class TestIdempotent(TestEmailBase):
1959 def _msgobj(self, filename):
1960 with openfile(filename) as fp:
1961 data = fp.read()
1962 msg = email.message_from_string(data)
1963 return msg, data
1964
1965 def _idempotent(self, msg, text):
1966 eq = self.ndiffAssertEqual
1967 s = StringIO()
1968 g = Generator(s, maxheaderlen=0)
1969 g.flatten(msg)
1970 eq(text, s.getvalue())
1971
1972 def test_parse_text_message(self):
1973 eq = self.assertEquals
1974 msg, text = self._msgobj('msg_01.txt')
1975 eq(msg.get_content_type(), 'text/plain')
1976 eq(msg.get_content_maintype(), 'text')
1977 eq(msg.get_content_subtype(), 'plain')
1978 eq(msg.get_params()[1], ('charset', 'us-ascii'))
1979 eq(msg.get_param('charset'), 'us-ascii')
1980 eq(msg.preamble, None)
1981 eq(msg.epilogue, None)
1982 self._idempotent(msg, text)
1983
1984 def test_parse_untyped_message(self):
1985 eq = self.assertEquals
1986 msg, text = self._msgobj('msg_03.txt')
1987 eq(msg.get_content_type(), 'text/plain')
1988 eq(msg.get_params(), None)
1989 eq(msg.get_param('charset'), None)
1990 self._idempotent(msg, text)
1991
1992 def test_simple_multipart(self):
1993 msg, text = self._msgobj('msg_04.txt')
1994 self._idempotent(msg, text)
1995
1996 def test_MIME_digest(self):
1997 msg, text = self._msgobj('msg_02.txt')
1998 self._idempotent(msg, text)
1999
2000 def test_long_header(self):
2001 msg, text = self._msgobj('msg_27.txt')
2002 self._idempotent(msg, text)
2003
2004 def test_MIME_digest_with_part_headers(self):
2005 msg, text = self._msgobj('msg_28.txt')
2006 self._idempotent(msg, text)
2007
2008 def test_mixed_with_image(self):
2009 msg, text = self._msgobj('msg_06.txt')
2010 self._idempotent(msg, text)
2011
2012 def test_multipart_report(self):
2013 msg, text = self._msgobj('msg_05.txt')
2014 self._idempotent(msg, text)
2015
2016 def test_dsn(self):
2017 msg, text = self._msgobj('msg_16.txt')
2018 self._idempotent(msg, text)
2019
2020 def test_preamble_epilogue(self):
2021 msg, text = self._msgobj('msg_21.txt')
2022 self._idempotent(msg, text)
2023
2024 def test_multipart_one_part(self):
2025 msg, text = self._msgobj('msg_23.txt')
2026 self._idempotent(msg, text)
2027
2028 def test_multipart_no_parts(self):
2029 msg, text = self._msgobj('msg_24.txt')
2030 self._idempotent(msg, text)
2031
2032 def test_no_start_boundary(self):
2033 msg, text = self._msgobj('msg_31.txt')
2034 self._idempotent(msg, text)
2035
2036 def test_rfc2231_charset(self):
2037 msg, text = self._msgobj('msg_32.txt')
2038 self._idempotent(msg, text)
2039
2040 def test_more_rfc2231_parameters(self):
2041 msg, text = self._msgobj('msg_33.txt')
2042 self._idempotent(msg, text)
2043
2044 def test_text_plain_in_a_multipart_digest(self):
2045 msg, text = self._msgobj('msg_34.txt')
2046 self._idempotent(msg, text)
2047
2048 def test_nested_multipart_mixeds(self):
2049 msg, text = self._msgobj('msg_12a.txt')
2050 self._idempotent(msg, text)
2051
2052 def test_message_external_body_idempotent(self):
2053 msg, text = self._msgobj('msg_36.txt')
2054 self._idempotent(msg, text)
2055
2056 def test_content_type(self):
2057 eq = self.assertEquals
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002058 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002059 # Get a message object and reset the seek pointer for other tests
2060 msg, text = self._msgobj('msg_05.txt')
2061 eq(msg.get_content_type(), 'multipart/report')
2062 # Test the Content-Type: parameters
2063 params = {}
2064 for pk, pv in msg.get_params():
2065 params[pk] = pv
2066 eq(params['report-type'], 'delivery-status')
2067 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
2068 eq(msg.preamble, 'This is a MIME-encapsulated message.\n')
2069 eq(msg.epilogue, '\n')
2070 eq(len(msg.get_payload()), 3)
2071 # Make sure the subparts are what we expect
2072 msg1 = msg.get_payload(0)
2073 eq(msg1.get_content_type(), 'text/plain')
2074 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
2075 msg2 = msg.get_payload(1)
2076 eq(msg2.get_content_type(), 'text/plain')
2077 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
2078 msg3 = msg.get_payload(2)
2079 eq(msg3.get_content_type(), 'message/rfc822')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002080 self.assertTrue(isinstance(msg3, Message))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002081 payload = msg3.get_payload()
2082 unless(isinstance(payload, list))
2083 eq(len(payload), 1)
2084 msg4 = payload[0]
2085 unless(isinstance(msg4, Message))
2086 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
2087
2088 def test_parser(self):
2089 eq = self.assertEquals
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002090 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002091 msg, text = self._msgobj('msg_06.txt')
2092 # Check some of the outer headers
2093 eq(msg.get_content_type(), 'message/rfc822')
2094 # Make sure the payload is a list of exactly one sub-Message, and that
2095 # that submessage has a type of text/plain
2096 payload = msg.get_payload()
2097 unless(isinstance(payload, list))
2098 eq(len(payload), 1)
2099 msg1 = payload[0]
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002100 self.assertTrue(isinstance(msg1, Message))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002101 eq(msg1.get_content_type(), 'text/plain')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002102 self.assertTrue(isinstance(msg1.get_payload(), str))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002103 eq(msg1.get_payload(), '\n')
2104
2105
2106
2107# Test various other bits of the package's functionality
2108class TestMiscellaneous(TestEmailBase):
2109 def test_message_from_string(self):
2110 with openfile('msg_01.txt') as fp:
2111 text = fp.read()
2112 msg = email.message_from_string(text)
2113 s = StringIO()
2114 # Don't wrap/continue long headers since we're trying to test
2115 # idempotency.
2116 g = Generator(s, maxheaderlen=0)
2117 g.flatten(msg)
2118 self.assertEqual(text, s.getvalue())
2119
2120 def test_message_from_file(self):
2121 with openfile('msg_01.txt') as fp:
2122 text = fp.read()
2123 fp.seek(0)
2124 msg = email.message_from_file(fp)
2125 s = StringIO()
2126 # Don't wrap/continue long headers since we're trying to test
2127 # idempotency.
2128 g = Generator(s, maxheaderlen=0)
2129 g.flatten(msg)
2130 self.assertEqual(text, s.getvalue())
2131
2132 def test_message_from_string_with_class(self):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002133 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002134 with openfile('msg_01.txt') as fp:
2135 text = fp.read()
2136
2137 # Create a subclass
2138 class MyMessage(Message):
2139 pass
2140
2141 msg = email.message_from_string(text, MyMessage)
2142 unless(isinstance(msg, MyMessage))
2143 # Try something more complicated
2144 with openfile('msg_02.txt') as fp:
2145 text = fp.read()
2146 msg = email.message_from_string(text, MyMessage)
2147 for subpart in msg.walk():
2148 unless(isinstance(subpart, MyMessage))
2149
2150 def test_message_from_file_with_class(self):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002151 unless = self.assertTrue
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002152 # Create a subclass
2153 class MyMessage(Message):
2154 pass
2155
2156 with openfile('msg_01.txt') as fp:
2157 msg = email.message_from_file(fp, MyMessage)
2158 unless(isinstance(msg, MyMessage))
2159 # Try something more complicated
2160 with openfile('msg_02.txt') as fp:
2161 msg = email.message_from_file(fp, MyMessage)
2162 for subpart in msg.walk():
2163 unless(isinstance(subpart, MyMessage))
2164
2165 def test__all__(self):
2166 module = __import__('email')
2167 # Can't use sorted() here due to Python 2.3 compatibility
2168 all = module.__all__[:]
2169 all.sort()
2170 self.assertEqual(all, [
2171 'base64mime', 'charset', 'encoders', 'errors', 'generator',
2172 'header', 'iterators', 'message', 'message_from_file',
2173 'message_from_string', 'mime', 'parser',
2174 'quoprimime', 'utils',
2175 ])
2176
2177 def test_formatdate(self):
2178 now = time.time()
2179 self.assertEqual(utils.parsedate(utils.formatdate(now))[:6],
2180 time.gmtime(now)[:6])
2181
2182 def test_formatdate_localtime(self):
2183 now = time.time()
2184 self.assertEqual(
2185 utils.parsedate(utils.formatdate(now, localtime=True))[:6],
2186 time.localtime(now)[:6])
2187
2188 def test_formatdate_usegmt(self):
2189 now = time.time()
2190 self.assertEqual(
2191 utils.formatdate(now, localtime=False),
2192 time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
2193 self.assertEqual(
2194 utils.formatdate(now, localtime=False, usegmt=True),
2195 time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
2196
2197 def test_parsedate_none(self):
2198 self.assertEqual(utils.parsedate(''), None)
2199
2200 def test_parsedate_compact(self):
2201 # The FWS after the comma is optional
2202 self.assertEqual(utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2203 utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2204
2205 def test_parsedate_no_dayofweek(self):
2206 eq = self.assertEqual
2207 eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2208 (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
2209
2210 def test_parsedate_compact_no_dayofweek(self):
2211 eq = self.assertEqual
2212 eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2213 (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
2214
2215 def test_parsedate_acceptable_to_time_functions(self):
2216 eq = self.assertEqual
2217 timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
2218 t = int(time.mktime(timetup))
2219 eq(time.localtime(t)[:6], timetup[:6])
2220 eq(int(time.strftime('%Y', timetup)), 2003)
2221 timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
2222 t = int(time.mktime(timetup[:9]))
2223 eq(time.localtime(t)[:6], timetup[:6])
2224 eq(int(time.strftime('%Y', timetup[:9])), 2003)
2225
2226 def test_parseaddr_empty(self):
2227 self.assertEqual(utils.parseaddr('<>'), ('', ''))
2228 self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '')
2229
2230 def test_noquote_dump(self):
2231 self.assertEqual(
2232 utils.formataddr(('A Silly Person', 'person@dom.ain')),
2233 'A Silly Person <person@dom.ain>')
2234
2235 def test_escape_dump(self):
2236 self.assertEqual(
2237 utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
2238 r'"A \(Very\) Silly Person" <person@dom.ain>')
2239 a = r'A \(Special\) Person'
2240 b = 'person@dom.ain'
2241 self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
2242
2243 def test_escape_backslashes(self):
2244 self.assertEqual(
2245 utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
2246 r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
2247 a = r'Arthur \Backslash\ Foobar'
2248 b = 'person@dom.ain'
2249 self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
2250
2251 def test_name_with_dot(self):
2252 x = 'John X. Doe <jxd@example.com>'
2253 y = '"John X. Doe" <jxd@example.com>'
2254 a, b = ('John X. Doe', 'jxd@example.com')
2255 self.assertEqual(utils.parseaddr(x), (a, b))
2256 self.assertEqual(utils.parseaddr(y), (a, b))
2257 # formataddr() quotes the name if there's a dot in it
2258 self.assertEqual(utils.formataddr((a, b)), y)
2259
2260 def test_multiline_from_comment(self):
2261 x = """\
2262Foo
2263\tBar <foo@example.com>"""
2264 self.assertEqual(utils.parseaddr(x), ('Foo Bar', 'foo@example.com'))
2265
2266 def test_quote_dump(self):
2267 self.assertEqual(
2268 utils.formataddr(('A Silly; Person', 'person@dom.ain')),
2269 r'"A Silly; Person" <person@dom.ain>')
2270
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002271 def test_charset_richcomparisons(self):
2272 eq = self.assertEqual
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002273 ne = self.assertNotEqual
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002274 cset1 = Charset()
2275 cset2 = Charset()
2276 eq(cset1, 'us-ascii')
2277 eq(cset1, 'US-ASCII')
2278 eq(cset1, 'Us-AsCiI')
2279 eq('us-ascii', cset1)
2280 eq('US-ASCII', cset1)
2281 eq('Us-AsCiI', cset1)
2282 ne(cset1, 'usascii')
2283 ne(cset1, 'USASCII')
2284 ne(cset1, 'UsAsCiI')
2285 ne('usascii', cset1)
2286 ne('USASCII', cset1)
2287 ne('UsAsCiI', cset1)
2288 eq(cset1, cset2)
2289 eq(cset2, cset1)
2290
2291 def test_getaddresses(self):
2292 eq = self.assertEqual
2293 eq(utils.getaddresses(['aperson@dom.ain (Al Person)',
2294 'Bud Person <bperson@dom.ain>']),
2295 [('Al Person', 'aperson@dom.ain'),
2296 ('Bud Person', 'bperson@dom.ain')])
2297
2298 def test_getaddresses_nasty(self):
2299 eq = self.assertEqual
2300 eq(utils.getaddresses(['foo: ;']), [('', '')])
2301 eq(utils.getaddresses(
2302 ['[]*-- =~$']),
2303 [('', ''), ('', ''), ('', '*--')])
2304 eq(utils.getaddresses(
2305 ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
2306 [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
2307
2308 def test_getaddresses_embedded_comment(self):
2309 """Test proper handling of a nested comment"""
2310 eq = self.assertEqual
2311 addrs = utils.getaddresses(['User ((nested comment)) <foo@bar.com>'])
2312 eq(addrs[0][1], 'foo@bar.com')
2313
2314 def test_utils_quote_unquote(self):
2315 eq = self.assertEqual
2316 msg = Message()
2317 msg.add_header('content-disposition', 'attachment',
2318 filename='foo\\wacky"name')
2319 eq(msg.get_filename(), 'foo\\wacky"name')
2320
2321 def test_get_body_encoding_with_bogus_charset(self):
2322 charset = Charset('not a charset')
2323 self.assertEqual(charset.get_body_encoding(), 'base64')
2324
2325 def test_get_body_encoding_with_uppercase_charset(self):
2326 eq = self.assertEqual
2327 msg = Message()
2328 msg['Content-Type'] = 'text/plain; charset=UTF-8'
2329 eq(msg['content-type'], 'text/plain; charset=UTF-8')
2330 charsets = msg.get_charsets()
2331 eq(len(charsets), 1)
2332 eq(charsets[0], 'utf-8')
2333 charset = Charset(charsets[0])
2334 eq(charset.get_body_encoding(), 'base64')
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002335 msg.set_payload(b'hello world', charset=charset)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002336 eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n')
2337 eq(msg.get_payload(decode=True), b'hello world')
2338 eq(msg['content-transfer-encoding'], 'base64')
2339 # Try another one
2340 msg = Message()
2341 msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
2342 charsets = msg.get_charsets()
2343 eq(len(charsets), 1)
2344 eq(charsets[0], 'us-ascii')
2345 charset = Charset(charsets[0])
2346 eq(charset.get_body_encoding(), encoders.encode_7or8bit)
2347 msg.set_payload('hello world', charset=charset)
2348 eq(msg.get_payload(), 'hello world')
2349 eq(msg['content-transfer-encoding'], '7bit')
2350
2351 def test_charsets_case_insensitive(self):
2352 lc = Charset('us-ascii')
2353 uc = Charset('US-ASCII')
2354 self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
2355
2356 def test_partial_falls_inside_message_delivery_status(self):
2357 eq = self.ndiffAssertEqual
2358 # The Parser interface provides chunks of data to FeedParser in 8192
2359 # byte gulps. SF bug #1076485 found one of those chunks inside
2360 # message/delivery-status header block, which triggered an
2361 # unreadline() of NeedMoreData.
2362 msg = self._msgobj('msg_43.txt')
2363 sfp = StringIO()
2364 iterators._structure(msg, sfp)
2365 eq(sfp.getvalue(), """\
2366multipart/report
2367 text/plain
2368 message/delivery-status
2369 text/plain
2370 text/plain
2371 text/plain
2372 text/plain
2373 text/plain
2374 text/plain
2375 text/plain
2376 text/plain
2377 text/plain
2378 text/plain
2379 text/plain
2380 text/plain
2381 text/plain
2382 text/plain
2383 text/plain
2384 text/plain
2385 text/plain
2386 text/plain
2387 text/plain
2388 text/plain
2389 text/plain
2390 text/plain
2391 text/plain
2392 text/plain
2393 text/plain
2394 text/plain
2395 text/rfc822-headers
2396""")
2397
2398
2399
2400# Test the iterator/generators
2401class TestIterators(TestEmailBase):
2402 def test_body_line_iterator(self):
2403 eq = self.assertEqual
2404 neq = self.ndiffAssertEqual
2405 # First a simple non-multipart message
2406 msg = self._msgobj('msg_01.txt')
2407 it = iterators.body_line_iterator(msg)
2408 lines = list(it)
2409 eq(len(lines), 6)
2410 neq(EMPTYSTRING.join(lines), msg.get_payload())
2411 # Now a more complicated multipart
2412 msg = self._msgobj('msg_02.txt')
2413 it = iterators.body_line_iterator(msg)
2414 lines = list(it)
2415 eq(len(lines), 43)
2416 with openfile('msg_19.txt') as fp:
2417 neq(EMPTYSTRING.join(lines), fp.read())
2418
2419 def test_typed_subpart_iterator(self):
2420 eq = self.assertEqual
2421 msg = self._msgobj('msg_04.txt')
2422 it = iterators.typed_subpart_iterator(msg, 'text')
2423 lines = []
2424 subparts = 0
2425 for subpart in it:
2426 subparts += 1
2427 lines.append(subpart.get_payload())
2428 eq(subparts, 2)
2429 eq(EMPTYSTRING.join(lines), """\
2430a simple kind of mirror
2431to reflect upon our own
2432a simple kind of mirror
2433to reflect upon our own
2434""")
2435
2436 def test_typed_subpart_iterator_default_type(self):
2437 eq = self.assertEqual
2438 msg = self._msgobj('msg_03.txt')
2439 it = iterators.typed_subpart_iterator(msg, 'text', 'plain')
2440 lines = []
2441 subparts = 0
2442 for subpart in it:
2443 subparts += 1
2444 lines.append(subpart.get_payload())
2445 eq(subparts, 1)
2446 eq(EMPTYSTRING.join(lines), """\
2447
2448Hi,
2449
2450Do you like this message?
2451
2452-Me
2453""")
2454
2455
2456
2457class TestParsers(TestEmailBase):
2458 def test_header_parser(self):
2459 eq = self.assertEqual
2460 # Parse only the headers of a complex multipart MIME document
2461 with openfile('msg_02.txt') as fp:
2462 msg = HeaderParser().parse(fp)
2463 eq(msg['from'], 'ppp-request@zzz.org')
2464 eq(msg['to'], 'ppp@zzz.org')
2465 eq(msg.get_content_type(), 'multipart/mixed')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002466 self.assertFalse(msg.is_multipart())
2467 self.assertTrue(isinstance(msg.get_payload(), str))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002468
2469 def test_whitespace_continuation(self):
2470 eq = self.assertEqual
2471 # This message contains a line after the Subject: header that has only
2472 # whitespace, but it is not empty!
2473 msg = email.message_from_string("""\
2474From: aperson@dom.ain
2475To: bperson@dom.ain
2476Subject: the next line has a space on it
2477\x20
2478Date: Mon, 8 Apr 2002 15:09:19 -0400
2479Message-ID: spam
2480
2481Here's the message body
2482""")
2483 eq(msg['subject'], 'the next line has a space on it\n ')
2484 eq(msg['message-id'], 'spam')
2485 eq(msg.get_payload(), "Here's the message body\n")
2486
2487 def test_whitespace_continuation_last_header(self):
2488 eq = self.assertEqual
2489 # Like the previous test, but the subject line is the last
2490 # header.
2491 msg = email.message_from_string("""\
2492From: aperson@dom.ain
2493To: bperson@dom.ain
2494Date: Mon, 8 Apr 2002 15:09:19 -0400
2495Message-ID: spam
2496Subject: the next line has a space on it
2497\x20
2498
2499Here's the message body
2500""")
2501 eq(msg['subject'], 'the next line has a space on it\n ')
2502 eq(msg['message-id'], 'spam')
2503 eq(msg.get_payload(), "Here's the message body\n")
2504
2505 def test_crlf_separation(self):
2506 eq = self.assertEqual
Guido van Rossum98297ee2007-11-06 21:34:58 +00002507 with openfile('msg_26.txt', newline='\n') as fp:
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002508 msg = Parser().parse(fp)
2509 eq(len(msg.get_payload()), 2)
2510 part1 = msg.get_payload(0)
2511 eq(part1.get_content_type(), 'text/plain')
2512 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
2513 part2 = msg.get_payload(1)
2514 eq(part2.get_content_type(), 'application/riscos')
2515
2516 def test_multipart_digest_with_extra_mime_headers(self):
2517 eq = self.assertEqual
2518 neq = self.ndiffAssertEqual
2519 with openfile('msg_28.txt') as fp:
2520 msg = email.message_from_file(fp)
2521 # Structure is:
2522 # multipart/digest
2523 # message/rfc822
2524 # text/plain
2525 # message/rfc822
2526 # text/plain
2527 eq(msg.is_multipart(), 1)
2528 eq(len(msg.get_payload()), 2)
2529 part1 = msg.get_payload(0)
2530 eq(part1.get_content_type(), 'message/rfc822')
2531 eq(part1.is_multipart(), 1)
2532 eq(len(part1.get_payload()), 1)
2533 part1a = part1.get_payload(0)
2534 eq(part1a.is_multipart(), 0)
2535 eq(part1a.get_content_type(), 'text/plain')
2536 neq(part1a.get_payload(), 'message 1\n')
2537 # next message/rfc822
2538 part2 = msg.get_payload(1)
2539 eq(part2.get_content_type(), 'message/rfc822')
2540 eq(part2.is_multipart(), 1)
2541 eq(len(part2.get_payload()), 1)
2542 part2a = part2.get_payload(0)
2543 eq(part2a.is_multipart(), 0)
2544 eq(part2a.get_content_type(), 'text/plain')
2545 neq(part2a.get_payload(), 'message 2\n')
2546
2547 def test_three_lines(self):
2548 # A bug report by Andrew McNamara
2549 lines = ['From: Andrew Person <aperson@dom.ain',
2550 'Subject: Test',
2551 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2552 msg = email.message_from_string(NL.join(lines))
2553 self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2554
2555 def test_strip_line_feed_and_carriage_return_in_headers(self):
2556 eq = self.assertEqual
2557 # For [ 1002475 ] email message parser doesn't handle \r\n correctly
2558 value1 = 'text'
2559 value2 = 'more text'
2560 m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
2561 value1, value2)
2562 msg = email.message_from_string(m)
2563 eq(msg.get('Header'), value1)
2564 eq(msg.get('Next-Header'), value2)
2565
2566 def test_rfc2822_header_syntax(self):
2567 eq = self.assertEqual
2568 m = '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2569 msg = email.message_from_string(m)
2570 eq(len(msg), 3)
2571 eq(sorted(field for field in msg), ['!"#QUX;~', '>From', 'From'])
2572 eq(msg.get_payload(), 'body')
2573
2574 def test_rfc2822_space_not_allowed_in_header(self):
2575 eq = self.assertEqual
2576 m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2577 msg = email.message_from_string(m)
2578 eq(len(msg.keys()), 0)
2579
2580 def test_rfc2822_one_character_header(self):
2581 eq = self.assertEqual
2582 m = 'A: first header\nB: second header\nCC: third header\n\nbody'
2583 msg = email.message_from_string(m)
2584 headers = msg.keys()
2585 headers.sort()
2586 eq(headers, ['A', 'B', 'CC'])
2587 eq(msg.get_payload(), 'body')
2588
2589
2590
2591class TestBase64(unittest.TestCase):
2592 def test_len(self):
2593 eq = self.assertEqual
Guido van Rossum9604e662007-08-30 03:46:43 +00002594 eq(base64mime.header_length('hello'),
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002595 len(base64mime.body_encode(b'hello', eol='')))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002596 for size in range(15):
2597 if size == 0 : bsize = 0
2598 elif size <= 3 : bsize = 4
2599 elif size <= 6 : bsize = 8
2600 elif size <= 9 : bsize = 12
2601 elif size <= 12: bsize = 16
2602 else : bsize = 20
Guido van Rossum9604e662007-08-30 03:46:43 +00002603 eq(base64mime.header_length('x' * size), bsize)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002604
2605 def test_decode(self):
2606 eq = self.assertEqual
Barry Warsaw2cc1f6d2007-08-30 14:28:55 +00002607 eq(base64mime.decode(''), b'')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002608 eq(base64mime.decode('aGVsbG8='), b'hello')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002609
2610 def test_encode(self):
2611 eq = self.assertEqual
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002612 eq(base64mime.body_encode(b''), b'')
2613 eq(base64mime.body_encode(b'hello'), 'aGVsbG8=\n')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002614 # Test the binary flag
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002615 eq(base64mime.body_encode(b'hello\n'), 'aGVsbG8K\n')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002616 # Test the maxlinelen arg
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002617 eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40), """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002618eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2619eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2620eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2621eHh4eCB4eHh4IA==
2622""")
2623 # Test the eol argument
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002624 eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40, eol='\r\n'),
Barry Warsaw7aa02e62007-08-31 03:26:19 +00002625 """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002626eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2627eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2628eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2629eHh4eCB4eHh4IA==\r
2630""")
2631
2632 def test_header_encode(self):
2633 eq = self.assertEqual
2634 he = base64mime.header_encode
2635 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
Guido van Rossum9604e662007-08-30 03:46:43 +00002636 eq(he('hello\r\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
2637 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002638 # Test the charset option
2639 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
2640 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002641
2642
2643
2644class TestQuopri(unittest.TestCase):
2645 def setUp(self):
2646 # Set of characters (as byte integers) that don't need to be encoded
2647 # in headers.
2648 self.hlit = list(chain(
2649 range(ord('a'), ord('z') + 1),
2650 range(ord('A'), ord('Z') + 1),
2651 range(ord('0'), ord('9') + 1),
Guido van Rossum9604e662007-08-30 03:46:43 +00002652 (c for c in b'!*+-/')))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002653 # Set of characters (as byte integers) that do need to be encoded in
2654 # headers.
2655 self.hnon = [c for c in range(256) if c not in self.hlit]
2656 assert len(self.hlit) + len(self.hnon) == 256
2657 # Set of characters (as byte integers) that don't need to be encoded
2658 # in bodies.
2659 self.blit = list(range(ord(' '), ord('~') + 1))
2660 self.blit.append(ord('\t'))
2661 self.blit.remove(ord('='))
2662 # Set of characters (as byte integers) that do need to be encoded in
2663 # bodies.
2664 self.bnon = [c for c in range(256) if c not in self.blit]
2665 assert len(self.blit) + len(self.bnon) == 256
2666
Guido van Rossum9604e662007-08-30 03:46:43 +00002667 def test_quopri_header_check(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002668 for c in self.hlit:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002669 self.assertFalse(quoprimime.header_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002670 'Should not be header quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002671 for c in self.hnon:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002672 self.assertTrue(quoprimime.header_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002673 'Should be header quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002674
Guido van Rossum9604e662007-08-30 03:46:43 +00002675 def test_quopri_body_check(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002676 for c in self.blit:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002677 self.assertFalse(quoprimime.body_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002678 'Should not be body quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002679 for c in self.bnon:
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002680 self.assertTrue(quoprimime.body_check(c),
Guido van Rossum9604e662007-08-30 03:46:43 +00002681 'Should be body quopri encoded: %s' % chr(c))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002682
2683 def test_header_quopri_len(self):
2684 eq = self.assertEqual
Guido van Rossum9604e662007-08-30 03:46:43 +00002685 eq(quoprimime.header_length(b'hello'), 5)
2686 # RFC 2047 chrome is not included in header_length().
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002687 eq(len(quoprimime.header_encode(b'hello', charset='xxx')),
Guido van Rossum9604e662007-08-30 03:46:43 +00002688 quoprimime.header_length(b'hello') +
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002689 # =?xxx?q?...?= means 10 extra characters
2690 10)
Guido van Rossum9604e662007-08-30 03:46:43 +00002691 eq(quoprimime.header_length(b'h@e@l@l@o@'), 20)
2692 # RFC 2047 chrome is not included in header_length().
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002693 eq(len(quoprimime.header_encode(b'h@e@l@l@o@', charset='xxx')),
Guido van Rossum9604e662007-08-30 03:46:43 +00002694 quoprimime.header_length(b'h@e@l@l@o@') +
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002695 # =?xxx?q?...?= means 10 extra characters
2696 10)
2697 for c in self.hlit:
Guido van Rossum9604e662007-08-30 03:46:43 +00002698 eq(quoprimime.header_length(bytes([c])), 1,
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002699 'expected length 1 for %r' % chr(c))
2700 for c in self.hnon:
Guido van Rossum9604e662007-08-30 03:46:43 +00002701 # Space is special; it's encoded to _
2702 if c == ord(' '):
2703 continue
2704 eq(quoprimime.header_length(bytes([c])), 3,
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002705 'expected length 3 for %r' % chr(c))
Guido van Rossum9604e662007-08-30 03:46:43 +00002706 eq(quoprimime.header_length(b' '), 1)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002707
2708 def test_body_quopri_len(self):
2709 eq = self.assertEqual
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002710 for c in self.blit:
Guido van Rossum9604e662007-08-30 03:46:43 +00002711 eq(quoprimime.body_length(bytes([c])), 1)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002712 for c in self.bnon:
Guido van Rossum9604e662007-08-30 03:46:43 +00002713 eq(quoprimime.body_length(bytes([c])), 3)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002714
2715 def test_quote_unquote_idempotent(self):
2716 for x in range(256):
2717 c = chr(x)
2718 self.assertEqual(quoprimime.unquote(quoprimime.quote(c)), c)
2719
2720 def test_header_encode(self):
2721 eq = self.assertEqual
2722 he = quoprimime.header_encode
2723 eq(he(b'hello'), '=?iso-8859-1?q?hello?=')
2724 eq(he(b'hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
2725 eq(he(b'hello\nworld'), '=?iso-8859-1?q?hello=0Aworld?=')
2726 # Test a non-ASCII character
2727 eq(he(b'hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
2728
2729 def test_decode(self):
2730 eq = self.assertEqual
2731 eq(quoprimime.decode(''), '')
2732 eq(quoprimime.decode('hello'), 'hello')
2733 eq(quoprimime.decode('hello', 'X'), 'hello')
2734 eq(quoprimime.decode('hello\nworld', 'X'), 'helloXworld')
2735
2736 def test_encode(self):
2737 eq = self.assertEqual
Guido van Rossum9604e662007-08-30 03:46:43 +00002738 eq(quoprimime.body_encode(''), '')
2739 eq(quoprimime.body_encode('hello'), 'hello')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002740 # Test the binary flag
Guido van Rossum9604e662007-08-30 03:46:43 +00002741 eq(quoprimime.body_encode('hello\r\nworld'), 'hello\nworld')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002742 # Test the maxlinelen arg
Guido van Rossum9604e662007-08-30 03:46:43 +00002743 eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40), """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002744xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
2745 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
2746x xxxx xxxx xxxx xxxx=20""")
2747 # Test the eol argument
Guido van Rossum9604e662007-08-30 03:46:43 +00002748 eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'),
2749 """\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002750xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
2751 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
2752x xxxx xxxx xxxx xxxx=20""")
Guido van Rossum9604e662007-08-30 03:46:43 +00002753 eq(quoprimime.body_encode("""\
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002754one line
2755
2756two line"""), """\
2757one line
2758
2759two line""")
2760
2761
2762
2763# Test the Charset class
2764class TestCharset(unittest.TestCase):
2765 def tearDown(self):
2766 from email import charset as CharsetModule
2767 try:
2768 del CharsetModule.CHARSETS['fake']
2769 except KeyError:
2770 pass
2771
Guido van Rossum9604e662007-08-30 03:46:43 +00002772 def test_codec_encodeable(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002773 eq = self.assertEqual
2774 # Make sure us-ascii = no Unicode conversion
2775 c = Charset('us-ascii')
Guido van Rossum9604e662007-08-30 03:46:43 +00002776 eq(c.header_encode('Hello World!'), 'Hello World!')
2777 # Test 8-bit idempotency with us-ascii
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002778 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
Guido van Rossum9604e662007-08-30 03:46:43 +00002779 self.assertRaises(UnicodeError, c.header_encode, s)
2780 c = Charset('utf-8')
2781 eq(c.header_encode(s), '=?utf-8?b?wqTCosKkwqTCpMKmwqTCqMKkwqo=?=')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002782
2783 def test_body_encode(self):
2784 eq = self.assertEqual
2785 # Try a charset with QP body encoding
2786 c = Charset('iso-8859-1')
Barry Warsaw7aa02e62007-08-31 03:26:19 +00002787 eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002788 # Try a charset with Base64 body encoding
2789 c = Charset('utf-8')
Martin v. Löwis15b16a32008-12-02 06:00:15 +00002790 eq('aGVsbG8gd29ybGQ=\n', c.body_encode(b'hello world'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002791 # Try a charset with None body encoding
2792 c = Charset('us-ascii')
Barry Warsaw7aa02e62007-08-31 03:26:19 +00002793 eq('hello world', c.body_encode('hello world'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002794 # Try the convert argument, where input codec != output codec
2795 c = Charset('euc-jp')
2796 # With apologies to Tokio Kikuchi ;)
Barry Warsawbef9d212007-08-31 10:55:37 +00002797 # XXX FIXME
2798## try:
2799## eq('\x1b$B5FCO;~IW\x1b(B',
2800## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
2801## eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
2802## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
2803## except LookupError:
2804## # We probably don't have the Japanese codecs installed
2805## pass
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002806 # Testing SF bug #625509, which we have to fake, since there are no
2807 # built-in encodings where the header encoding is QP but the body
2808 # encoding is not.
2809 from email import charset as CharsetModule
2810 CharsetModule.add_charset('fake', CharsetModule.QP, None)
2811 c = Charset('fake')
Barry Warsaw7aa02e62007-08-31 03:26:19 +00002812 eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002813
2814 def test_unicode_charset_name(self):
2815 charset = Charset('us-ascii')
2816 self.assertEqual(str(charset), 'us-ascii')
2817 self.assertRaises(errors.CharsetError, Charset, 'asc\xffii')
2818
2819
2820
2821# Test multilingual MIME headers.
2822class TestHeader(TestEmailBase):
2823 def test_simple(self):
2824 eq = self.ndiffAssertEqual
2825 h = Header('Hello World!')
2826 eq(h.encode(), 'Hello World!')
2827 h.append(' Goodbye World!')
2828 eq(h.encode(), 'Hello World! Goodbye World!')
2829
2830 def test_simple_surprise(self):
2831 eq = self.ndiffAssertEqual
2832 h = Header('Hello World!')
2833 eq(h.encode(), 'Hello World!')
2834 h.append('Goodbye World!')
2835 eq(h.encode(), 'Hello World! Goodbye World!')
2836
2837 def test_header_needs_no_decoding(self):
2838 h = 'no decoding needed'
2839 self.assertEqual(decode_header(h), [(h, None)])
2840
2841 def test_long(self):
2842 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.",
2843 maxlinelen=76)
2844 for l in h.encode(splitchars=' ').split('\n '):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00002845 self.assertTrue(len(l) <= 76)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002846
2847 def test_multilingual(self):
2848 eq = self.ndiffAssertEqual
2849 g = Charset("iso-8859-1")
2850 cz = Charset("iso-8859-2")
2851 utf8 = Charset("utf-8")
2852 g_head = (b'Die Mieter treten hier ein werden mit einem '
2853 b'Foerderband komfortabel den Korridor entlang, '
2854 b'an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, '
2855 b'gegen die rotierenden Klingen bef\xf6rdert. ')
2856 cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
2857 b'd\xf9vtipu.. ')
2858 utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
2859 '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
2860 '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
2861 '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
2862 '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
2863 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
2864 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
2865 '\u3044\u307e\u3059\u3002')
2866 h = Header(g_head, g)
2867 h.append(cz_head, cz)
2868 h.append(utf8_head, utf8)
Guido van Rossum9604e662007-08-30 03:46:43 +00002869 enc = h.encode(maxlinelen=76)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002870 eq(enc, """\
Guido van Rossum9604e662007-08-30 03:46:43 +00002871=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_kom?=
2872 =?iso-8859-1?q?fortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wand?=
2873 =?iso-8859-1?q?gem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6r?=
2874 =?iso-8859-1?q?dert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002875 =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
2876 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
2877 =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
2878 =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
Guido van Rossum9604e662007-08-30 03:46:43 +00002879 =?utf-8?b?IE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5k?=
2880 =?utf-8?b?IGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIA=?=
2881 =?utf-8?b?44Gj44Gm44GE44G+44GZ44CC?=""")
2882 decoded = decode_header(enc)
2883 eq(len(decoded), 3)
2884 eq(decoded[0], (g_head, 'iso-8859-1'))
2885 eq(decoded[1], (cz_head, 'iso-8859-2'))
2886 eq(decoded[2], (utf8_head.encode('utf-8'), 'utf-8'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002887 ustr = str(h)
Guido van Rossum9604e662007-08-30 03:46:43 +00002888 eq(ustr,
2889 (b'Die Mieter treten hier ein werden mit einem Foerderband '
2890 b'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
2891 b'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
2892 b'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
2893 b'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
2894 b'\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
2895 b'\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
2896 b'\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
2897 b'\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
2898 b'\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
2899 b'\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
2900 b'\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
2901 b'\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
2902 b'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
2903 b'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
2904 b'\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'
2905 ).decode('utf-8'))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002906 # Test make_header()
2907 newh = make_header(decode_header(enc))
Guido van Rossum9604e662007-08-30 03:46:43 +00002908 eq(newh, h)
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002909
2910 def test_empty_header_encode(self):
2911 h = Header()
2912 self.assertEqual(h.encode(), '')
Barry Warsaw8b3d6592007-08-30 02:10:49 +00002913
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002914 def test_header_ctor_default_args(self):
2915 eq = self.ndiffAssertEqual
2916 h = Header()
2917 eq(h, '')
2918 h.append('foo', Charset('iso-8859-1'))
Guido van Rossum9604e662007-08-30 03:46:43 +00002919 eq(h, 'foo')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002920
2921 def test_explicit_maxlinelen(self):
2922 eq = self.ndiffAssertEqual
2923 hstr = ('A very long line that must get split to something other '
2924 'than at the 76th character boundary to test the non-default '
2925 'behavior')
2926 h = Header(hstr)
2927 eq(h.encode(), '''\
2928A very long line that must get split to something other than at the 76th
2929 character boundary to test the non-default behavior''')
2930 eq(str(h), hstr)
2931 h = Header(hstr, header_name='Subject')
2932 eq(h.encode(), '''\
2933A very long line that must get split to something other than at the
2934 76th character boundary to test the non-default behavior''')
2935 eq(str(h), hstr)
2936 h = Header(hstr, maxlinelen=1024, header_name='Subject')
2937 eq(h.encode(), hstr)
2938 eq(str(h), hstr)
2939
Guido van Rossum9604e662007-08-30 03:46:43 +00002940 def test_quopri_splittable(self):
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002941 eq = self.ndiffAssertEqual
2942 h = Header(charset='iso-8859-1', maxlinelen=20)
Guido van Rossum9604e662007-08-30 03:46:43 +00002943 x = 'xxxx ' * 20
2944 h.append(x)
2945 s = h.encode()
2946 eq(s, """\
2947=?iso-8859-1?q?xxx?=
2948 =?iso-8859-1?q?x_?=
2949 =?iso-8859-1?q?xx?=
2950 =?iso-8859-1?q?xx?=
2951 =?iso-8859-1?q?_x?=
2952 =?iso-8859-1?q?xx?=
2953 =?iso-8859-1?q?x_?=
2954 =?iso-8859-1?q?xx?=
2955 =?iso-8859-1?q?xx?=
2956 =?iso-8859-1?q?_x?=
2957 =?iso-8859-1?q?xx?=
2958 =?iso-8859-1?q?x_?=
2959 =?iso-8859-1?q?xx?=
2960 =?iso-8859-1?q?xx?=
2961 =?iso-8859-1?q?_x?=
2962 =?iso-8859-1?q?xx?=
2963 =?iso-8859-1?q?x_?=
2964 =?iso-8859-1?q?xx?=
2965 =?iso-8859-1?q?xx?=
2966 =?iso-8859-1?q?_x?=
2967 =?iso-8859-1?q?xx?=
2968 =?iso-8859-1?q?x_?=
2969 =?iso-8859-1?q?xx?=
2970 =?iso-8859-1?q?xx?=
2971 =?iso-8859-1?q?_x?=
2972 =?iso-8859-1?q?xx?=
2973 =?iso-8859-1?q?x_?=
2974 =?iso-8859-1?q?xx?=
2975 =?iso-8859-1?q?xx?=
2976 =?iso-8859-1?q?_x?=
2977 =?iso-8859-1?q?xx?=
2978 =?iso-8859-1?q?x_?=
2979 =?iso-8859-1?q?xx?=
2980 =?iso-8859-1?q?xx?=
2981 =?iso-8859-1?q?_x?=
2982 =?iso-8859-1?q?xx?=
2983 =?iso-8859-1?q?x_?=
2984 =?iso-8859-1?q?xx?=
2985 =?iso-8859-1?q?xx?=
2986 =?iso-8859-1?q?_x?=
2987 =?iso-8859-1?q?xx?=
2988 =?iso-8859-1?q?x_?=
2989 =?iso-8859-1?q?xx?=
2990 =?iso-8859-1?q?xx?=
2991 =?iso-8859-1?q?_x?=
2992 =?iso-8859-1?q?xx?=
2993 =?iso-8859-1?q?x_?=
2994 =?iso-8859-1?q?xx?=
2995 =?iso-8859-1?q?xx?=
2996 =?iso-8859-1?q?_?=""")
2997 eq(x, str(make_header(decode_header(s))))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00002998 h = Header(charset='iso-8859-1', maxlinelen=40)
2999 h.append('xxxx ' * 20)
Guido van Rossum9604e662007-08-30 03:46:43 +00003000 s = h.encode()
3001 eq(s, """\
3002=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xxx?=
3003 =?iso-8859-1?q?x_xxxx_xxxx_xxxx_xxxx_?=
3004 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
3005 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
3006 =?iso-8859-1?q?_xxxx_xxxx_?=""")
3007 eq(x, str(make_header(decode_header(s))))
3008
3009 def test_base64_splittable(self):
3010 eq = self.ndiffAssertEqual
3011 h = Header(charset='koi8-r', maxlinelen=20)
3012 x = 'xxxx ' * 20
3013 h.append(x)
3014 s = h.encode()
3015 eq(s, """\
3016=?koi8-r?b?eHh4?=
3017 =?koi8-r?b?eCB4?=
3018 =?koi8-r?b?eHh4?=
3019 =?koi8-r?b?IHh4?=
3020 =?koi8-r?b?eHgg?=
3021 =?koi8-r?b?eHh4?=
3022 =?koi8-r?b?eCB4?=
3023 =?koi8-r?b?eHh4?=
3024 =?koi8-r?b?IHh4?=
3025 =?koi8-r?b?eHgg?=
3026 =?koi8-r?b?eHh4?=
3027 =?koi8-r?b?eCB4?=
3028 =?koi8-r?b?eHh4?=
3029 =?koi8-r?b?IHh4?=
3030 =?koi8-r?b?eHgg?=
3031 =?koi8-r?b?eHh4?=
3032 =?koi8-r?b?eCB4?=
3033 =?koi8-r?b?eHh4?=
3034 =?koi8-r?b?IHh4?=
3035 =?koi8-r?b?eHgg?=
3036 =?koi8-r?b?eHh4?=
3037 =?koi8-r?b?eCB4?=
3038 =?koi8-r?b?eHh4?=
3039 =?koi8-r?b?IHh4?=
3040 =?koi8-r?b?eHgg?=
3041 =?koi8-r?b?eHh4?=
3042 =?koi8-r?b?eCB4?=
3043 =?koi8-r?b?eHh4?=
3044 =?koi8-r?b?IHh4?=
3045 =?koi8-r?b?eHgg?=
3046 =?koi8-r?b?eHh4?=
3047 =?koi8-r?b?eCB4?=
3048 =?koi8-r?b?eHh4?=
3049 =?koi8-r?b?IA==?=""")
3050 eq(x, str(make_header(decode_header(s))))
3051 h = Header(charset='koi8-r', maxlinelen=40)
3052 h.append(x)
3053 s = h.encode()
3054 eq(s, """\
3055=?koi8-r?b?eHh4eCB4eHh4IHh4eHggeHh4?=
3056 =?koi8-r?b?eCB4eHh4IHh4eHggeHh4eCB4?=
3057 =?koi8-r?b?eHh4IHh4eHggeHh4eCB4eHh4?=
3058 =?koi8-r?b?IHh4eHggeHh4eCB4eHh4IHh4?=
3059 =?koi8-r?b?eHggeHh4eCB4eHh4IHh4eHgg?=
3060 =?koi8-r?b?eHh4eCB4eHh4IA==?=""")
3061 eq(x, str(make_header(decode_header(s))))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003062
3063 def test_us_ascii_header(self):
3064 eq = self.assertEqual
3065 s = 'hello'
3066 x = decode_header(s)
3067 eq(x, [('hello', None)])
3068 h = make_header(x)
3069 eq(s, h.encode())
3070
3071 def test_string_charset(self):
3072 eq = self.assertEqual
3073 h = Header()
3074 h.append('hello', 'iso-8859-1')
Guido van Rossum9604e662007-08-30 03:46:43 +00003075 eq(h, 'hello')
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003076
3077## def test_unicode_error(self):
3078## raises = self.assertRaises
3079## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
3080## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
3081## h = Header()
3082## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
3083## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
3084## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
3085
3086 def test_utf8_shortest(self):
3087 eq = self.assertEqual
3088 h = Header('p\xf6stal', 'utf-8')
3089 eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
3090 h = Header('\u83ca\u5730\u6642\u592b', 'utf-8')
3091 eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
3092
3093 def test_bad_8bit_header(self):
3094 raises = self.assertRaises
3095 eq = self.assertEqual
3096 x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
3097 raises(UnicodeError, Header, x)
3098 h = Header()
3099 raises(UnicodeError, h.append, x)
3100 e = x.decode('utf-8', 'replace')
3101 eq(str(Header(x, errors='replace')), e)
3102 h.append(x, errors='replace')
3103 eq(str(h), e)
3104
3105 def test_encoded_adjacent_nonencoded(self):
3106 eq = self.assertEqual
3107 h = Header()
3108 h.append('hello', 'iso-8859-1')
3109 h.append('world')
3110 s = h.encode()
3111 eq(s, '=?iso-8859-1?q?hello?= world')
3112 h = make_header(decode_header(s))
3113 eq(h.encode(), s)
3114
3115 def test_whitespace_eater(self):
3116 eq = self.assertEqual
3117 s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
3118 parts = decode_header(s)
3119 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)])
3120 hdr = make_header(parts)
3121 eq(hdr.encode(),
3122 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
3123
3124 def test_broken_base64_header(self):
3125 raises = self.assertRaises
3126 s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ?='
3127 raises(errors.HeaderParseError, decode_header, s)
3128
3129
3130
3131# Test RFC 2231 header parameters (en/de)coding
3132class TestRFC2231(TestEmailBase):
3133 def test_get_param(self):
3134 eq = self.assertEqual
3135 msg = self._msgobj('msg_29.txt')
3136 eq(msg.get_param('title'),
3137 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
3138 eq(msg.get_param('title', unquote=False),
3139 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
3140
3141 def test_set_param(self):
3142 eq = self.ndiffAssertEqual
3143 msg = Message()
3144 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3145 charset='us-ascii')
3146 eq(msg.get_param('title'),
3147 ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
3148 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3149 charset='us-ascii', language='en')
3150 eq(msg.get_param('title'),
3151 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
3152 msg = self._msgobj('msg_01.txt')
3153 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3154 charset='us-ascii', language='en')
3155 eq(msg.as_string(maxheaderlen=78), """\
3156Return-Path: <bbb@zzz.org>
3157Delivered-To: bbb@zzz.org
3158Received: by mail.zzz.org (Postfix, from userid 889)
3159\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3160MIME-Version: 1.0
3161Content-Transfer-Encoding: 7bit
3162Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
3163From: bbb@ddd.com (John X. Doe)
3164To: bbb@zzz.org
3165Subject: This is a test message
3166Date: Fri, 4 May 2001 14:05:44 -0400
3167Content-Type: text/plain; charset=us-ascii;
3168 title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3169
3170
3171Hi,
3172
3173Do you like this message?
3174
3175-Me
3176""")
3177
3178 def test_del_param(self):
3179 eq = self.ndiffAssertEqual
3180 msg = self._msgobj('msg_01.txt')
3181 msg.set_param('foo', 'bar', charset='us-ascii', language='en')
3182 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3183 charset='us-ascii', language='en')
3184 msg.del_param('foo', header='Content-Type')
3185 eq(msg.as_string(maxheaderlen=78), """\
3186Return-Path: <bbb@zzz.org>
3187Delivered-To: bbb@zzz.org
3188Received: by mail.zzz.org (Postfix, from userid 889)
3189\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3190MIME-Version: 1.0
3191Content-Transfer-Encoding: 7bit
3192Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
3193From: bbb@ddd.com (John X. Doe)
3194To: bbb@zzz.org
3195Subject: This is a test message
3196Date: Fri, 4 May 2001 14:05:44 -0400
3197Content-Type: text/plain; charset="us-ascii";
3198 title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3199
3200
3201Hi,
3202
3203Do you like this message?
3204
3205-Me
3206""")
3207
3208 def test_rfc2231_get_content_charset(self):
3209 eq = self.assertEqual
3210 msg = self._msgobj('msg_32.txt')
3211 eq(msg.get_content_charset(), 'us-ascii')
3212
3213 def test_rfc2231_no_language_or_charset(self):
3214 m = '''\
3215Content-Transfer-Encoding: 8bit
3216Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
3217Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
3218
3219'''
3220 msg = email.message_from_string(m)
3221 param = msg.get_param('NAME')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00003222 self.assertFalse(isinstance(param, tuple))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003223 self.assertEqual(
3224 param,
3225 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
3226
3227 def test_rfc2231_no_language_or_charset_in_filename(self):
3228 m = '''\
3229Content-Disposition: inline;
3230\tfilename*0*="''This%20is%20even%20more%20";
3231\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3232\tfilename*2="is it not.pdf"
3233
3234'''
3235 msg = email.message_from_string(m)
3236 self.assertEqual(msg.get_filename(),
3237 'This is even more ***fun*** is it not.pdf')
3238
3239 def test_rfc2231_no_language_or_charset_in_filename_encoded(self):
3240 m = '''\
3241Content-Disposition: inline;
3242\tfilename*0*="''This%20is%20even%20more%20";
3243\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3244\tfilename*2="is it not.pdf"
3245
3246'''
3247 msg = email.message_from_string(m)
3248 self.assertEqual(msg.get_filename(),
3249 'This is even more ***fun*** is it not.pdf')
3250
3251 def test_rfc2231_partly_encoded(self):
3252 m = '''\
3253Content-Disposition: inline;
3254\tfilename*0="''This%20is%20even%20more%20";
3255\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3256\tfilename*2="is it not.pdf"
3257
3258'''
3259 msg = email.message_from_string(m)
3260 self.assertEqual(
3261 msg.get_filename(),
3262 'This%20is%20even%20more%20***fun*** is it not.pdf')
3263
3264 def test_rfc2231_partly_nonencoded(self):
3265 m = '''\
3266Content-Disposition: inline;
3267\tfilename*0="This%20is%20even%20more%20";
3268\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
3269\tfilename*2="is it not.pdf"
3270
3271'''
3272 msg = email.message_from_string(m)
3273 self.assertEqual(
3274 msg.get_filename(),
3275 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
3276
3277 def test_rfc2231_no_language_or_charset_in_boundary(self):
3278 m = '''\
3279Content-Type: multipart/alternative;
3280\tboundary*0*="''This%20is%20even%20more%20";
3281\tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
3282\tboundary*2="is it not.pdf"
3283
3284'''
3285 msg = email.message_from_string(m)
3286 self.assertEqual(msg.get_boundary(),
3287 'This is even more ***fun*** is it not.pdf')
3288
3289 def test_rfc2231_no_language_or_charset_in_charset(self):
3290 # This is a nonsensical charset value, but tests the code anyway
3291 m = '''\
3292Content-Type: text/plain;
3293\tcharset*0*="This%20is%20even%20more%20";
3294\tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
3295\tcharset*2="is it not.pdf"
3296
3297'''
3298 msg = email.message_from_string(m)
3299 self.assertEqual(msg.get_content_charset(),
3300 'this is even more ***fun*** is it not.pdf')
3301
3302 def test_rfc2231_bad_encoding_in_filename(self):
3303 m = '''\
3304Content-Disposition: inline;
3305\tfilename*0*="bogus'xx'This%20is%20even%20more%20";
3306\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3307\tfilename*2="is it not.pdf"
3308
3309'''
3310 msg = email.message_from_string(m)
3311 self.assertEqual(msg.get_filename(),
3312 'This is even more ***fun*** is it not.pdf')
3313
3314 def test_rfc2231_bad_encoding_in_charset(self):
3315 m = """\
3316Content-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
3317
3318"""
3319 msg = email.message_from_string(m)
3320 # This should return None because non-ascii characters in the charset
3321 # are not allowed.
3322 self.assertEqual(msg.get_content_charset(), None)
3323
3324 def test_rfc2231_bad_character_in_charset(self):
3325 m = """\
3326Content-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
3327
3328"""
3329 msg = email.message_from_string(m)
3330 # This should return None because non-ascii characters in the charset
3331 # are not allowed.
3332 self.assertEqual(msg.get_content_charset(), None)
3333
3334 def test_rfc2231_bad_character_in_filename(self):
3335 m = '''\
3336Content-Disposition: inline;
3337\tfilename*0*="ascii'xx'This%20is%20even%20more%20";
3338\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3339\tfilename*2*="is it not.pdf%E2"
3340
3341'''
3342 msg = email.message_from_string(m)
3343 self.assertEqual(msg.get_filename(),
3344 'This is even more ***fun*** is it not.pdf\ufffd')
3345
3346 def test_rfc2231_unknown_encoding(self):
3347 m = """\
3348Content-Transfer-Encoding: 8bit
3349Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
3350
3351"""
3352 msg = email.message_from_string(m)
3353 self.assertEqual(msg.get_filename(), 'myfile.txt')
3354
3355 def test_rfc2231_single_tick_in_filename_extended(self):
3356 eq = self.assertEqual
3357 m = """\
3358Content-Type: application/x-foo;
3359\tname*0*=\"Frank's\"; name*1*=\" Document\"
3360
3361"""
3362 msg = email.message_from_string(m)
3363 charset, language, s = msg.get_param('name')
3364 eq(charset, None)
3365 eq(language, None)
3366 eq(s, "Frank's Document")
3367
3368 def test_rfc2231_single_tick_in_filename(self):
3369 m = """\
3370Content-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
3371
3372"""
3373 msg = email.message_from_string(m)
3374 param = msg.get_param('name')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00003375 self.assertFalse(isinstance(param, tuple))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003376 self.assertEqual(param, "Frank's Document")
3377
3378 def test_rfc2231_tick_attack_extended(self):
3379 eq = self.assertEqual
3380 m = """\
3381Content-Type: application/x-foo;
3382\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
3383
3384"""
3385 msg = email.message_from_string(m)
3386 charset, language, s = msg.get_param('name')
3387 eq(charset, 'us-ascii')
3388 eq(language, 'en-us')
3389 eq(s, "Frank's Document")
3390
3391 def test_rfc2231_tick_attack(self):
3392 m = """\
3393Content-Type: application/x-foo;
3394\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
3395
3396"""
3397 msg = email.message_from_string(m)
3398 param = msg.get_param('name')
Benjamin Petersonc9c0f202009-06-30 23:06:06 +00003399 self.assertFalse(isinstance(param, tuple))
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003400 self.assertEqual(param, "us-ascii'en-us'Frank's Document")
3401
3402 def test_rfc2231_no_extended_values(self):
3403 eq = self.assertEqual
3404 m = """\
3405Content-Type: application/x-foo; name=\"Frank's Document\"
3406
3407"""
3408 msg = email.message_from_string(m)
3409 eq(msg.get_param('name'), "Frank's Document")
3410
3411 def test_rfc2231_encoded_then_unencoded_segments(self):
3412 eq = self.assertEqual
3413 m = """\
3414Content-Type: application/x-foo;
3415\tname*0*=\"us-ascii'en-us'My\";
3416\tname*1=\" Document\";
3417\tname*2*=\" For You\"
3418
3419"""
3420 msg = email.message_from_string(m)
3421 charset, language, s = msg.get_param('name')
3422 eq(charset, 'us-ascii')
3423 eq(language, 'en-us')
3424 eq(s, 'My Document For You')
3425
3426 def test_rfc2231_unencoded_then_encoded_segments(self):
3427 eq = self.assertEqual
3428 m = """\
3429Content-Type: application/x-foo;
3430\tname*0=\"us-ascii'en-us'My\";
3431\tname*1*=\" Document\";
3432\tname*2*=\" For You\"
3433
3434"""
3435 msg = email.message_from_string(m)
3436 charset, language, s = msg.get_param('name')
3437 eq(charset, 'us-ascii')
3438 eq(language, 'en-us')
3439 eq(s, 'My Document For You')
3440
3441
3442
R. David Murraya8f480f2010-01-16 18:30:03 +00003443# Tests to ensure that signed parts of an email are completely preserved, as
3444# required by RFC1847 section 2.1. Note that these are incomplete, because the
3445# email package does not currently always preserve the body. See issue 1670765.
3446class TestSigned(TestEmailBase):
3447
3448 def _msg_and_obj(self, filename):
3449 with openfile(findfile(filename)) as fp:
3450 original = fp.read()
3451 msg = email.message_from_string(original)
3452 return original, msg
3453
3454 def _signed_parts_eq(self, original, result):
3455 # Extract the first mime part of each message
3456 import re
3457 repart = re.compile(r'^--([^\n]+)\n(.*?)\n--\1$', re.S | re.M)
3458 inpart = repart.search(original).group(2)
3459 outpart = repart.search(result).group(2)
3460 self.assertEqual(outpart, inpart)
3461
3462 def test_long_headers_as_string(self):
3463 original, msg = self._msg_and_obj('msg_45.txt')
3464 result = msg.as_string()
3465 self._signed_parts_eq(original, result)
3466
3467 def test_long_headers_as_string_maxheaderlen(self):
3468 original, msg = self._msg_and_obj('msg_45.txt')
3469 result = msg.as_string(maxheaderlen=60)
3470 self._signed_parts_eq(original, result)
3471
3472 def test_long_headers_flatten(self):
3473 original, msg = self._msg_and_obj('msg_45.txt')
3474 fp = StringIO()
3475 Generator(fp).flatten(msg)
3476 result = fp.getvalue()
3477 self._signed_parts_eq(original, result)
3478
3479
3480
Guido van Rossum8b3febe2007-08-30 01:15:14 +00003481def _testclasses():
3482 mod = sys.modules[__name__]
3483 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
3484
3485
3486def suite():
3487 suite = unittest.TestSuite()
3488 for testclass in _testclasses():
3489 suite.addTest(unittest.makeSuite(testclass))
3490 return suite
3491
3492
3493def test_main():
3494 for testclass in _testclasses():
3495 run_unittest(testclass)
3496
3497
3498
3499if __name__ == '__main__':
3500 unittest.main(defaultTest='suite')