1 # Copyright (C) 2001-2007 Python Software Foundation
2 # Contact: email-sig@python.org
3 # email package unit tests
12 from cStringIO
import StringIO
16 from email
.charset
import Charset
17 from email
.header
import Header
, decode_header
, make_header
18 from email
.parser
import Parser
, HeaderParser
19 from email
.generator
import Generator
, DecodedGenerator
20 from email
.message
import Message
21 from email
.mime
.application
import MIMEApplication
22 from email
.mime
.audio
import MIMEAudio
23 from email
.mime
.text
import MIMEText
24 from email
.mime
.image
import MIMEImage
25 from email
.mime
.base
import MIMEBase
26 from email
.mime
.message
import MIMEMessage
27 from email
.mime
.multipart
import MIMEMultipart
28 from email
import utils
29 from email
import errors
30 from email
import encoders
31 from email
import iterators
32 from email
import base64mime
33 from email
import quoprimime
35 from test
.test_support
import findfile
, run_unittest
36 from email
.test
import __file__
as landmark
45 def openfile(filename
, mode
='r'):
46 path
= os
.path
.join(os
.path
.dirname(landmark
), 'data', filename
)
47 return open(path
, mode
)
52 class TestEmailBase(unittest
.TestCase
):
53 def ndiffAssertEqual(self
, first
, second
):
54 """Like assertEqual except use ndiff for readable output."""
58 diff
= difflib
.ndiff(sfirst
.splitlines(), ssecond
.splitlines())
60 print >> fp
, NL
, NL
.join(diff
)
61 raise self
.failureException
, fp
.getvalue()
63 def _msgobj(self
, filename
):
64 fp
= openfile(findfile(filename
))
66 msg
= email
.message_from_file(fp
)
73 # Test various aspects of the Message class's API
74 class TestMessageAPI(TestEmailBase
):
75 def test_get_all(self
):
77 msg
= self
._msgobj
('msg_20.txt')
78 eq(msg
.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
79 eq(msg
.get_all('xx', 'n/a'), 'n/a')
81 def test_getset_charset(self
):
84 eq(msg
.get_charset(), None)
85 charset
= Charset('iso-8859-1')
86 msg
.set_charset(charset
)
87 eq(msg
['mime-version'], '1.0')
88 eq(msg
.get_content_type(), 'text/plain')
89 eq(msg
['content-type'], 'text/plain; charset="iso-8859-1"')
90 eq(msg
.get_param('charset'), 'iso-8859-1')
91 eq(msg
['content-transfer-encoding'], 'quoted-printable')
92 eq(msg
.get_charset().input_charset
, 'iso-8859-1')
95 eq(msg
.get_charset(), None)
96 eq(msg
['content-type'], 'text/plain')
97 # Try adding a charset when there's already MIME headers present
99 msg
['MIME-Version'] = '2.0'
100 msg
['Content-Type'] = 'text/x-weird'
101 msg
['Content-Transfer-Encoding'] = 'quinted-puntable'
102 msg
.set_charset(charset
)
103 eq(msg
['mime-version'], '2.0')
104 eq(msg
['content-type'], 'text/x-weird; charset="iso-8859-1"')
105 eq(msg
['content-transfer-encoding'], 'quinted-puntable')
107 def test_set_charset_from_string(self
):
108 eq
= self
.assertEqual
110 msg
.set_charset('us-ascii')
111 eq(msg
.get_charset().input_charset
, 'us-ascii')
112 eq(msg
['content-type'], 'text/plain; charset="us-ascii"')
114 def test_set_payload_with_charset(self
):
116 charset
= Charset('iso-8859-1')
117 msg
.set_payload('This is a string payload', charset
)
118 self
.assertEqual(msg
.get_charset().input_charset
, 'iso-8859-1')
120 def test_get_charsets(self
):
121 eq
= self
.assertEqual
123 msg
= self
._msgobj
('msg_08.txt')
124 charsets
= msg
.get_charsets()
125 eq(charsets
, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
127 msg
= self
._msgobj
('msg_09.txt')
128 charsets
= msg
.get_charsets('dingbat')
129 eq(charsets
, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
132 msg
= self
._msgobj
('msg_12.txt')
133 charsets
= msg
.get_charsets()
134 eq(charsets
, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
135 'iso-8859-3', 'us-ascii', 'koi8-r'])
137 def test_get_filename(self
):
138 eq
= self
.assertEqual
140 msg
= self
._msgobj
('msg_04.txt')
141 filenames
= [p
.get_filename() for p
in msg
.get_payload()]
142 eq(filenames
, ['msg.txt', 'msg.txt'])
144 msg
= self
._msgobj
('msg_07.txt')
145 subpart
= msg
.get_payload(1)
146 eq(subpart
.get_filename(), 'dingusfish.gif')
148 def test_get_filename_with_name_parameter(self
):
149 eq
= self
.assertEqual
151 msg
= self
._msgobj
('msg_44.txt')
152 filenames
= [p
.get_filename() for p
in msg
.get_payload()]
153 eq(filenames
, ['msg.txt', 'msg.txt'])
155 def test_get_boundary(self
):
156 eq
= self
.assertEqual
157 msg
= self
._msgobj
('msg_07.txt')
159 eq(msg
.get_boundary(), 'BOUNDARY')
161 def test_set_boundary(self
):
162 eq
= self
.assertEqual
163 # This one has no existing boundary parameter, but the Content-Type:
164 # header appears fifth.
165 msg
= self
._msgobj
('msg_01.txt')
166 msg
.set_boundary('BOUNDARY')
167 header
, value
= msg
.items()[4]
168 eq(header
.lower(), 'content-type')
169 eq(value
, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
170 # This one has a Content-Type: header, with a boundary, stuck in the
171 # middle of its headers. Make sure the order is preserved; it should
173 msg
= self
._msgobj
('msg_04.txt')
174 msg
.set_boundary('BOUNDARY')
175 header
, value
= msg
.items()[4]
176 eq(header
.lower(), 'content-type')
177 eq(value
, 'multipart/mixed; boundary="BOUNDARY"')
178 # And this one has no Content-Type: header at all.
179 msg
= self
._msgobj
('msg_03.txt')
180 self
.assertRaises(errors
.HeaderParseError
,
181 msg
.set_boundary
, 'BOUNDARY')
183 def test_get_decoded_payload(self
):
184 eq
= self
.assertEqual
185 msg
= self
._msgobj
('msg_10.txt')
186 # The outer message is a multipart
187 eq(msg
.get_payload(decode
=True), None)
188 # Subpart 1 is 7bit encoded
189 eq(msg
.get_payload(0).get_payload(decode
=True),
190 'This is a 7bit encoded message.\n')
191 # Subpart 2 is quopri
192 eq(msg
.get_payload(1).get_payload(decode
=True),
193 '\xa1This is a Quoted Printable encoded message!\n')
194 # Subpart 3 is base64
195 eq(msg
.get_payload(2).get_payload(decode
=True),
196 'This is a Base64 encoded message.')
197 # Subpart 4 is base64 with a trailing newline, which
198 # used to be stripped (issue 7143).
199 eq(msg
.get_payload(3).get_payload(decode
=True),
200 'This is a Base64 encoded message.\n')
201 # Subpart 5 has no Content-Transfer-Encoding: header.
202 eq(msg
.get_payload(4).get_payload(decode
=True),
203 'This has no Content-Transfer-Encoding: header.\n')
205 def test_get_decoded_uu_payload(self
):
206 eq
= self
.assertEqual
208 msg
.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
209 for cte
in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
210 msg
['content-transfer-encoding'] = cte
211 eq(msg
.get_payload(decode
=True), 'hello world')
212 # Now try some bogus data
213 msg
.set_payload('foo')
214 eq(msg
.get_payload(decode
=True), 'foo')
216 def test_decoded_generator(self
):
217 eq
= self
.assertEqual
218 msg
= self
._msgobj
('msg_07.txt')
219 fp
= openfile('msg_17.txt')
225 g
= DecodedGenerator(s
)
227 eq(s
.getvalue(), text
)
229 def test__contains__(self
):
233 # Check for case insensitivity
234 self
.assertTrue('from' in msg
)
235 self
.assertTrue('From' in msg
)
236 self
.assertTrue('FROM' in msg
)
237 self
.assertTrue('to' in msg
)
238 self
.assertTrue('To' in msg
)
239 self
.assertTrue('TO' in msg
)
241 def test_as_string(self
):
242 eq
= self
.assertEqual
243 msg
= self
._msgobj
('msg_01.txt')
244 fp
= openfile('msg_01.txt')
246 # BAW 30-Mar-2009 Evil be here. So, the generator is broken with
247 # respect to long line breaking. It's also not idempotent when a
248 # header from a parsed message is continued with tabs rather than
249 # spaces. Before we fixed bug 1974 it was reversedly broken,
250 # i.e. headers that were continued with spaces got continued with
251 # tabs. For Python 2.x there's really no good fix and in Python
252 # 3.x all this stuff is re-written to be right(er). Chris Withers
253 # convinced me that using space as the default continuation
254 # character is less bad for more applications.
255 text
= fp
.read().replace('\t', ' ')
258 self
.ndiffAssertEqual(text
, msg
.as_string())
260 lines
= fullrepr
.split('\n')
261 self
.assertTrue(lines
[0].startswith('From '))
262 eq(text
, NL
.join(lines
[1:]))
264 def test_bad_param(self
):
265 msg
= email
.message_from_string("Content-Type: blarg; baz; boo\n")
266 self
.assertEqual(msg
.get_param('baz'), '')
268 def test_missing_filename(self
):
269 msg
= email
.message_from_string("From: foo\n")
270 self
.assertEqual(msg
.get_filename(), None)
272 def test_bogus_filename(self
):
273 msg
= email
.message_from_string(
274 "Content-Disposition: blarg; filename\n")
275 self
.assertEqual(msg
.get_filename(), '')
277 def test_missing_boundary(self
):
278 msg
= email
.message_from_string("From: foo\n")
279 self
.assertEqual(msg
.get_boundary(), None)
281 def test_get_params(self
):
282 eq
= self
.assertEqual
283 msg
= email
.message_from_string(
284 'X-Header: foo=one; bar=two; baz=three\n')
285 eq(msg
.get_params(header
='x-header'),
286 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
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 eq(msg
.get_params(), None)
292 msg
= email
.message_from_string(
293 'X-Header: foo; bar="one"; baz=two\n')
294 eq(msg
.get_params(header
='x-header'),
295 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
297 def test_get_param_liberal(self
):
299 msg
['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
300 self
.assertEqual(msg
.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
302 def test_get_param(self
):
303 eq
= self
.assertEqual
304 msg
= email
.message_from_string(
305 "X-Header: foo=one; bar=two; baz=three\n")
306 eq(msg
.get_param('bar', header
='x-header'), 'two')
307 eq(msg
.get_param('quuz', header
='x-header'), None)
308 eq(msg
.get_param('quuz'), None)
309 msg
= email
.message_from_string(
310 'X-Header: foo; bar="one"; baz=two\n')
311 eq(msg
.get_param('foo', header
='x-header'), '')
312 eq(msg
.get_param('bar', header
='x-header'), 'one')
313 eq(msg
.get_param('baz', header
='x-header'), 'two')
314 # XXX: We are not RFC-2045 compliant! We cannot parse:
315 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
316 # msg.get_param("weird")
319 def test_get_param_funky_continuation_lines(self
):
320 msg
= self
._msgobj
('msg_22.txt')
321 self
.assertEqual(msg
.get_payload(1).get_param('name'), 'wibble.JPG')
323 def test_get_param_with_semis_in_quotes(self
):
324 msg
= email
.message_from_string(
325 'Content-Type: image/pjpeg; name="Jim&&Jill"\n')
326 self
.assertEqual(msg
.get_param('name'), 'Jim&&Jill')
327 self
.assertEqual(msg
.get_param('name', unquote
=False),
328 '"Jim&&Jill"')
330 def test_has_key(self
):
331 msg
= email
.message_from_string('Header: exists')
332 self
.assertTrue(msg
.has_key('header'))
333 self
.assertTrue(msg
.has_key('Header'))
334 self
.assertTrue(msg
.has_key('HEADER'))
335 self
.assertFalse(msg
.has_key('headeri'))
337 def test_set_param(self
):
338 eq
= self
.assertEqual
340 msg
.set_param('charset', 'iso-2022-jp')
341 eq(msg
.get_param('charset'), 'iso-2022-jp')
342 msg
.set_param('importance', 'high value')
343 eq(msg
.get_param('importance'), 'high value')
344 eq(msg
.get_param('importance', unquote
=False), '"high value"')
345 eq(msg
.get_params(), [('text/plain', ''),
346 ('charset', 'iso-2022-jp'),
347 ('importance', 'high value')])
348 eq(msg
.get_params(unquote
=False), [('text/plain', ''),
349 ('charset', '"iso-2022-jp"'),
350 ('importance', '"high value"')])
351 msg
.set_param('charset', 'iso-9999-xx', header
='X-Jimmy')
352 eq(msg
.get_param('charset', header
='X-Jimmy'), 'iso-9999-xx')
354 def test_del_param(self
):
355 eq
= self
.assertEqual
356 msg
= self
._msgobj
('msg_05.txt')
358 [('multipart/report', ''), ('report-type', 'delivery-status'),
359 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
360 old_val
= msg
.get_param("report-type")
361 msg
.del_param("report-type")
363 [('multipart/report', ''),
364 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
365 msg
.set_param("report-type", old_val
)
367 [('multipart/report', ''),
368 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
369 ('report-type', old_val
)])
371 def test_del_param_on_other_header(self
):
373 msg
.add_header('Content-Disposition', 'attachment', filename
='bud.gif')
374 msg
.del_param('filename', 'content-disposition')
375 self
.assertEqual(msg
['content-disposition'], 'attachment')
377 def test_set_type(self
):
378 eq
= self
.assertEqual
380 self
.assertRaises(ValueError, msg
.set_type
, 'text')
381 msg
.set_type('text/plain')
382 eq(msg
['content-type'], 'text/plain')
383 msg
.set_param('charset', 'us-ascii')
384 eq(msg
['content-type'], 'text/plain; charset="us-ascii"')
385 msg
.set_type('text/html')
386 eq(msg
['content-type'], 'text/html; charset="us-ascii"')
388 def test_set_type_on_other_header(self
):
390 msg
['X-Content-Type'] = 'text/plain'
391 msg
.set_type('application/octet-stream', 'X-Content-Type')
392 self
.assertEqual(msg
['x-content-type'], 'application/octet-stream')
394 def test_get_content_type_missing(self
):
396 self
.assertEqual(msg
.get_content_type(), 'text/plain')
398 def test_get_content_type_missing_with_default_type(self
):
400 msg
.set_default_type('message/rfc822')
401 self
.assertEqual(msg
.get_content_type(), 'message/rfc822')
403 def test_get_content_type_from_message_implicit(self
):
404 msg
= self
._msgobj
('msg_30.txt')
405 self
.assertEqual(msg
.get_payload(0).get_content_type(),
408 def test_get_content_type_from_message_explicit(self
):
409 msg
= self
._msgobj
('msg_28.txt')
410 self
.assertEqual(msg
.get_payload(0).get_content_type(),
413 def test_get_content_type_from_message_text_plain_implicit(self
):
414 msg
= self
._msgobj
('msg_03.txt')
415 self
.assertEqual(msg
.get_content_type(), 'text/plain')
417 def test_get_content_type_from_message_text_plain_explicit(self
):
418 msg
= self
._msgobj
('msg_01.txt')
419 self
.assertEqual(msg
.get_content_type(), 'text/plain')
421 def test_get_content_maintype_missing(self
):
423 self
.assertEqual(msg
.get_content_maintype(), 'text')
425 def test_get_content_maintype_missing_with_default_type(self
):
427 msg
.set_default_type('message/rfc822')
428 self
.assertEqual(msg
.get_content_maintype(), 'message')
430 def test_get_content_maintype_from_message_implicit(self
):
431 msg
= self
._msgobj
('msg_30.txt')
432 self
.assertEqual(msg
.get_payload(0).get_content_maintype(), 'message')
434 def test_get_content_maintype_from_message_explicit(self
):
435 msg
= self
._msgobj
('msg_28.txt')
436 self
.assertEqual(msg
.get_payload(0).get_content_maintype(), 'message')
438 def test_get_content_maintype_from_message_text_plain_implicit(self
):
439 msg
= self
._msgobj
('msg_03.txt')
440 self
.assertEqual(msg
.get_content_maintype(), 'text')
442 def test_get_content_maintype_from_message_text_plain_explicit(self
):
443 msg
= self
._msgobj
('msg_01.txt')
444 self
.assertEqual(msg
.get_content_maintype(), 'text')
446 def test_get_content_subtype_missing(self
):
448 self
.assertEqual(msg
.get_content_subtype(), 'plain')
450 def test_get_content_subtype_missing_with_default_type(self
):
452 msg
.set_default_type('message/rfc822')
453 self
.assertEqual(msg
.get_content_subtype(), 'rfc822')
455 def test_get_content_subtype_from_message_implicit(self
):
456 msg
= self
._msgobj
('msg_30.txt')
457 self
.assertEqual(msg
.get_payload(0).get_content_subtype(), 'rfc822')
459 def test_get_content_subtype_from_message_explicit(self
):
460 msg
= self
._msgobj
('msg_28.txt')
461 self
.assertEqual(msg
.get_payload(0).get_content_subtype(), 'rfc822')
463 def test_get_content_subtype_from_message_text_plain_implicit(self
):
464 msg
= self
._msgobj
('msg_03.txt')
465 self
.assertEqual(msg
.get_content_subtype(), 'plain')
467 def test_get_content_subtype_from_message_text_plain_explicit(self
):
468 msg
= self
._msgobj
('msg_01.txt')
469 self
.assertEqual(msg
.get_content_subtype(), 'plain')
471 def test_get_content_maintype_error(self
):
473 msg
['Content-Type'] = 'no-slash-in-this-string'
474 self
.assertEqual(msg
.get_content_maintype(), 'text')
476 def test_get_content_subtype_error(self
):
478 msg
['Content-Type'] = 'no-slash-in-this-string'
479 self
.assertEqual(msg
.get_content_subtype(), 'plain')
481 def test_replace_header(self
):
482 eq
= self
.assertEqual
484 msg
.add_header('First', 'One')
485 msg
.add_header('Second', 'Two')
486 msg
.add_header('Third', 'Three')
487 eq(msg
.keys(), ['First', 'Second', 'Third'])
488 eq(msg
.values(), ['One', 'Two', 'Three'])
489 msg
.replace_header('Second', 'Twenty')
490 eq(msg
.keys(), ['First', 'Second', 'Third'])
491 eq(msg
.values(), ['One', 'Twenty', 'Three'])
492 msg
.add_header('First', 'Eleven')
493 msg
.replace_header('First', 'One Hundred')
494 eq(msg
.keys(), ['First', 'Second', 'Third', 'First'])
495 eq(msg
.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
496 self
.assertRaises(KeyError, msg
.replace_header
, 'Fourth', 'Missing')
498 def test_broken_base64_payload(self
):
499 x
= 'AwDp0P7//y6LwKEAcPa/6Q=9'
501 msg
['content-type'] = 'audio/x-midi'
502 msg
['content-transfer-encoding'] = 'base64'
504 self
.assertEqual(msg
.get_payload(decode
=True), x
)
508 # Test the email.encoders module
509 class TestEncoders(unittest
.TestCase
):
510 def test_encode_empty_payload(self
):
511 eq
= self
.assertEqual
513 msg
.set_charset('us-ascii')
514 eq(msg
['content-transfer-encoding'], '7bit')
516 def test_default_cte(self
):
517 eq
= self
.assertEqual
518 msg
= MIMEText('hello world')
519 eq(msg
['content-transfer-encoding'], '7bit')
521 def test_default_cte(self
):
522 eq
= self
.assertEqual
523 # With no explicit _charset its us-ascii, and all are 7-bit
524 msg
= MIMEText('hello world')
525 eq(msg
['content-transfer-encoding'], '7bit')
526 # Similar, but with 8-bit data
527 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')
535 # Test long header wrapping
536 class TestLongHeaders(TestEmailBase
):
537 def test_split_long_continuation(self
):
538 eq
= self
.ndiffAssertEqual
539 msg
= email
.message_from_string("""\
540 Subject: bug demonstration
541 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
549 eq(sfp
.getvalue(), """\
550 Subject: bug demonstration
551 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
557 def test_another_long_almost_unsplittable_header(self
):
558 eq
= self
.ndiffAssertEqual
561 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
563 h
= Header(hstr
, continuation_ws
='\t')
566 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
571 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
574 def test_long_nonstring(self
):
575 eq
= self
.ndiffAssertEqual
576 g
= Charset("iso-8859-1")
577 cz
= Charset("iso-8859-2")
578 utf8
= Charset("utf-8")
579 g_head
= "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
580 cz_head
= "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
581 utf8_head
= u
"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
582 h
= Header(g_head
, g
, header_name
='Subject')
583 h
.append(cz_head
, cz
)
584 h
.append(utf8_head
, utf8
)
590 eq(sfp
.getvalue(), """\
591 Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
592 =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
593 =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
594 =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
595 =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
596 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
597 =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
598 =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
599 =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
600 =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
601 =?utf-8?b?44Gm44GE44G+44GZ44CC?=
605 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
606 =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
607 =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
608 =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
609 =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
610 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
611 =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
612 =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
613 =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
614 =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
615 =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
617 def test_long_header_encode(self
):
618 eq
= self
.ndiffAssertEqual
619 h
= Header('wasnipoop; giraffes="very-long-necked-animals"; '
620 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
621 header_name
='X-Foobar-Spoink-Defrobnit')
623 wasnipoop; giraffes="very-long-necked-animals";
624 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
626 def test_long_header_encode_with_tab_continuation(self
):
627 eq
= self
.ndiffAssertEqual
628 h
= Header('wasnipoop; giraffes="very-long-necked-animals"; '
629 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
630 header_name
='X-Foobar-Spoink-Defrobnit',
631 continuation_ws
='\t')
633 wasnipoop; giraffes="very-long-necked-animals";
634 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
636 def test_header_splitter(self
):
637 eq
= self
.ndiffAssertEqual
639 # It'd be great if we could use add_header() here, but that doesn't
640 # guarantee an order of the parameters.
641 msg
['X-Foobar-Spoink-Defrobnit'] = (
642 'wasnipoop; giraffes="very-long-necked-animals"; '
643 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
647 eq(sfp
.getvalue(), '''\
648 Content-Type: text/plain; charset="us-ascii"
650 Content-Transfer-Encoding: 7bit
651 X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
652 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
656 def test_no_semis_header_splitter(self
):
657 eq
= self
.ndiffAssertEqual
659 msg
['From'] = 'test@dom.ain'
660 msg
['References'] = SPACE
.join(['<%d@dom.ain>' % i
for i
in range(10)])
661 msg
.set_payload('Test')
665 eq(sfp
.getvalue(), """\
667 References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
668 <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
672 def test_no_split_long_header(self
):
673 eq
= self
.ndiffAssertEqual
674 hstr
= 'References: ' + 'x' * 80
675 h
= Header(hstr
, continuation_ws
='\t')
677 References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
679 def test_splitting_multiple_long_lines(self
):
680 eq
= self
.ndiffAssertEqual
682 from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
683 \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)
684 \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)
686 h
= Header(hstr
, continuation_ws
='\t')
688 from babylon.socal-raves.org (localhost [127.0.0.1]);
689 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
690 \tfor <mailman-admin@babylon.socal-raves.org>;
691 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
692 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
693 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
694 \tfor <mailman-admin@babylon.socal-raves.org>;
695 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
696 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
697 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
698 \tfor <mailman-admin@babylon.socal-raves.org>;
699 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
701 def test_splitting_first_line_only_is_long(self
):
702 eq
= self
.ndiffAssertEqual
704 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
705 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
706 \tid 17k4h5-00034i-00
707 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
708 h
= Header(hstr
, maxlinelen
=78, header_name
='Received',
709 continuation_ws
='\t')
711 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
712 \thelo=cthulhu.gerg.ca)
713 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
714 \tid 17k4h5-00034i-00
715 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
717 def test_long_8bit_header(self
):
718 eq
= self
.ndiffAssertEqual
720 h
= Header('Britische Regierung gibt', 'iso-8859-1',
721 header_name
='Subject')
722 h
.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
724 eq(msg
.as_string(), """\
725 Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
726 =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
730 def test_long_8bit_header_no_charset(self
):
731 eq
= self
.ndiffAssertEqual
733 msg
['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
734 eq(msg
.as_string(), """\
735 Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
739 def test_long_to_header(self
):
740 eq
= self
.ndiffAssertEqual
741 to
= '"Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,"Someone Test #B" <someone@umich.edu>, "Someone Test #C" <someone@eecs.umich.edu>, "Someone Test #D" <someone@eecs.umich.edu>'
744 eq(msg
.as_string(0), '''\
745 To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
746 "Someone Test #B" <someone@umich.edu>,
747 "Someone Test #C" <someone@eecs.umich.edu>,
748 "Someone Test #D" <someone@eecs.umich.edu>
752 def test_long_line_after_append(self
):
753 eq
= self
.ndiffAssertEqual
754 s
= 'This is an example of string which has almost the limit of header length.'
756 h
.append('Add another line.')
758 This is an example of string which has almost the limit of header length.
759 Add another line.""")
761 def test_shorter_line_with_append(self
):
762 eq
= self
.ndiffAssertEqual
763 s
= 'This is a shorter line.'
765 h
.append('Add another sentence. (Surprise?)')
767 'This is a shorter line. Add another sentence. (Surprise?)')
769 def test_long_field_name(self
):
770 eq
= self
.ndiffAssertEqual
771 fn
= 'X-Very-Very-Very-Long-Header-Name'
772 gs
= "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
773 h
= Header(gs
, 'iso-8859-1', header_name
=fn
)
774 # BAW: this seems broken because the first line is too long
776 =?iso-8859-1?q?Die_Mieter_treten_hier_?=
777 =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
778 =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
779 =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
781 def test_long_received_header(self
):
782 h
= 'from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; Wed, 05 Mar 2003 18:10:18 -0700'
784 msg
['Received-1'] = Header(h
, continuation_ws
='\t')
785 msg
['Received-2'] = h
786 self
.ndiffAssertEqual(msg
.as_string(), """\
787 Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
788 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
789 \tWed, 05 Mar 2003 18:10:18 -0700
790 Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
791 hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
792 Wed, 05 Mar 2003 18:10:18 -0700
796 def test_string_headerinst_eq(self
):
797 h
= '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
799 msg
['Received'] = Header(h
, header_name
='Received-1',
800 continuation_ws
='\t')
802 self
.ndiffAssertEqual(msg
.as_string(), """\
803 Received: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
804 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
805 Received: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
806 (David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
810 def test_long_unbreakable_lines_with_continuation(self
):
811 eq
= self
.ndiffAssertEqual
814 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
815 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
817 msg
['Face-2'] = Header(t
, header_name
='Face-2')
818 eq(msg
.as_string(), """\
819 Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
820 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
821 Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
822 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
826 def test_another_long_multiline_header(self
):
827 eq
= self
.ndiffAssertEqual
829 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
830 Wed, 16 Oct 2002 07:41:11 -0700'''
831 msg
= email
.message_from_string(m
)
832 eq(msg
.as_string(), '''\
833 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
834 Microsoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
838 def test_long_lines_with_different_header(self
):
839 eq
= self
.ndiffAssertEqual
841 List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
842 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
845 msg
['List'] = Header(h
, header_name
='List')
846 self
.ndiffAssertEqual(msg
.as_string(), """\
847 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
848 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
849 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
850 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
856 # Test mangling of "From " lines in the body of a message
857 class TestFromMangling(unittest
.TestCase
):
860 self
.msg
['From'] = 'aaa@bbb.org'
861 self
.msg
.set_payload("""\
862 From the desk of A.A.A.:
866 def test_mangled_from(self
):
868 g
= Generator(s
, mangle_from_
=True)
870 self
.assertEqual(s
.getvalue(), """\
873 >From the desk of A.A.A.:
877 def test_dont_mangle_from(self
):
879 g
= Generator(s
, mangle_from_
=False)
881 self
.assertEqual(s
.getvalue(), """\
884 From the desk of A.A.A.:
890 # Test the basic MIMEAudio class
891 class TestMIMEAudio(unittest
.TestCase
):
893 # Make sure we pick up the audiotest.au that lives in email/test/data.
894 # In Python, there's an audiotest.au living in Lib/test but that isn't
895 # included in some binary distros that don't include the test
896 # package. The trailing empty string on the .join() is significant
897 # since findfile() will do a dirname().
898 datadir
= os
.path
.join(os
.path
.dirname(landmark
), 'data', '')
899 fp
= open(findfile('audiotest.au', datadir
), 'rb')
901 self
._audiodata
= fp
.read()
904 self
._au
= MIMEAudio(self
._audiodata
)
906 def test_guess_minor_type(self
):
907 self
.assertEqual(self
._au
.get_content_type(), 'audio/basic')
909 def test_encoding(self
):
910 payload
= self
._au
.get_payload()
911 self
.assertEqual(base64
.decodestring(payload
), self
._audiodata
)
913 def test_checkSetMinor(self
):
914 au
= MIMEAudio(self
._audiodata
, 'fish')
915 self
.assertEqual(au
.get_content_type(), 'audio/fish')
917 def test_add_header(self
):
918 eq
= self
.assertEqual
919 unless
= self
.assertTrue
920 self
._au
.add_header('Content-Disposition', 'attachment',
921 filename
='audiotest.au')
922 eq(self
._au
['content-disposition'],
923 'attachment; filename="audiotest.au"')
924 eq(self
._au
.get_params(header
='content-disposition'),
925 [('attachment', ''), ('filename', 'audiotest.au')])
926 eq(self
._au
.get_param('filename', header
='content-disposition'),
929 eq(self
._au
.get_param('attachment', header
='content-disposition'), '')
930 unless(self
._au
.get_param('foo', failobj
=missing
,
931 header
='content-disposition') is missing
)
932 # Try some missing stuff
933 unless(self
._au
.get_param('foobar', missing
) is missing
)
934 unless(self
._au
.get_param('attachment', missing
,
935 header
='foobar') is missing
)
939 # Test the basic MIMEImage class
940 class TestMIMEImage(unittest
.TestCase
):
942 fp
= openfile('PyBanner048.gif')
944 self
._imgdata
= fp
.read()
947 self
._im
= MIMEImage(self
._imgdata
)
949 def test_guess_minor_type(self
):
950 self
.assertEqual(self
._im
.get_content_type(), 'image/gif')
952 def test_encoding(self
):
953 payload
= self
._im
.get_payload()
954 self
.assertEqual(base64
.decodestring(payload
), self
._imgdata
)
956 def test_checkSetMinor(self
):
957 im
= MIMEImage(self
._imgdata
, 'fish')
958 self
.assertEqual(im
.get_content_type(), 'image/fish')
960 def test_add_header(self
):
961 eq
= self
.assertEqual
962 unless
= self
.assertTrue
963 self
._im
.add_header('Content-Disposition', 'attachment',
964 filename
='dingusfish.gif')
965 eq(self
._im
['content-disposition'],
966 'attachment; filename="dingusfish.gif"')
967 eq(self
._im
.get_params(header
='content-disposition'),
968 [('attachment', ''), ('filename', 'dingusfish.gif')])
969 eq(self
._im
.get_param('filename', header
='content-disposition'),
972 eq(self
._im
.get_param('attachment', header
='content-disposition'), '')
973 unless(self
._im
.get_param('foo', failobj
=missing
,
974 header
='content-disposition') is missing
)
975 # Try some missing stuff
976 unless(self
._im
.get_param('foobar', missing
) is missing
)
977 unless(self
._im
.get_param('attachment', missing
,
978 header
='foobar') is missing
)
982 # Test the basic MIMEApplication class
983 class TestMIMEApplication(unittest
.TestCase
):
984 def test_headers(self
):
985 eq
= self
.assertEqual
986 msg
= MIMEApplication('\xfa\xfb\xfc\xfd\xfe\xff')
987 eq(msg
.get_content_type(), 'application/octet-stream')
988 eq(msg
['content-transfer-encoding'], 'base64')
991 eq
= self
.assertEqual
992 bytes
= '\xfa\xfb\xfc\xfd\xfe\xff'
993 msg
= MIMEApplication(bytes
)
994 eq(msg
.get_payload(), '+vv8/f7/')
995 eq(msg
.get_payload(decode
=True), bytes
)
999 # Test the basic MIMEText class
1000 class TestMIMEText(unittest
.TestCase
):
1002 self
._msg
= MIMEText('hello there')
1004 def test_types(self
):
1005 eq
= self
.assertEqual
1006 unless
= self
.assertTrue
1007 eq(self
._msg
.get_content_type(), 'text/plain')
1008 eq(self
._msg
.get_param('charset'), 'us-ascii')
1010 unless(self
._msg
.get_param('foobar', missing
) is missing
)
1011 unless(self
._msg
.get_param('charset', missing
, header
='foobar')
1014 def test_payload(self
):
1015 self
.assertEqual(self
._msg
.get_payload(), 'hello there')
1016 self
.assertTrue(not self
._msg
.is_multipart())
1018 def test_charset(self
):
1019 eq
= self
.assertEqual
1020 msg
= MIMEText('hello there', _charset
='us-ascii')
1021 eq(msg
.get_charset().input_charset
, 'us-ascii')
1022 eq(msg
['content-type'], 'text/plain; charset="us-ascii"')
1026 # Test complicated multipart/* messages
1027 class TestMultipart(TestEmailBase
):
1029 fp
= openfile('PyBanner048.gif')
1035 container
= MIMEBase('multipart', 'mixed', boundary
='BOUNDARY')
1036 image
= MIMEImage(data
, name
='dingusfish.gif')
1037 image
.add_header('content-disposition', 'attachment',
1038 filename
='dingusfish.gif')
1039 intro
= MIMEText('''\
1042 This is the dingus fish.
1044 container
.attach(intro
)
1045 container
.attach(image
)
1046 container
['From'] = 'Barry <barry@digicool.com>'
1047 container
['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1048 container
['Subject'] = 'Here is your dingus fish'
1050 now
= 987809702.54848599
1051 timetuple
= time
.localtime(now
)
1052 if timetuple
[-1] == 0:
1053 tzsecs
= time
.timezone
1055 tzsecs
= time
.altzone
1060 tzoffset
= ' %s%04d' % (sign
, tzsecs
// 36)
1061 container
['Date'] = time
.strftime(
1062 '%a, %d %b %Y %H:%M:%S',
1063 time
.localtime(now
)) + tzoffset
1064 self
._msg
= container
1068 def test_hierarchy(self
):
1070 eq
= self
.assertEqual
1071 unless
= self
.assertTrue
1072 raises
= self
.assertRaises
1075 unless(m
.is_multipart())
1076 eq(m
.get_content_type(), 'multipart/mixed')
1077 eq(len(m
.get_payload()), 2)
1078 raises(IndexError, m
.get_payload
, 2)
1079 m0
= m
.get_payload(0)
1080 m1
= m
.get_payload(1)
1081 unless(m0
is self
._txt
)
1082 unless(m1
is self
._im
)
1083 eq(m
.get_payload(), [m0
, m1
])
1084 unless(not m0
.is_multipart())
1085 unless(not m1
.is_multipart())
1087 def test_empty_multipart_idempotent(self
):
1089 Content-Type: multipart/mixed; boundary="BOUNDARY"
1093 From: bperson@dom.ain
1101 msg
= Parser().parsestr(text
)
1102 self
.ndiffAssertEqual(text
, msg
.as_string())
1104 def test_no_parts_in_a_multipart_with_none_epilogue(self
):
1105 outer
= MIMEBase('multipart', 'mixed')
1106 outer
['Subject'] = 'A subject'
1107 outer
['To'] = 'aperson@dom.ain'
1108 outer
['From'] = 'bperson@dom.ain'
1109 outer
.set_boundary('BOUNDARY')
1110 self
.ndiffAssertEqual(outer
.as_string(), '''\
1111 Content-Type: multipart/mixed; boundary="BOUNDARY"
1115 From: bperson@dom.ain
1121 def test_no_parts_in_a_multipart_with_empty_epilogue(self
):
1122 outer
= MIMEBase('multipart', 'mixed')
1123 outer
['Subject'] = 'A subject'
1124 outer
['To'] = 'aperson@dom.ain'
1125 outer
['From'] = 'bperson@dom.ain'
1128 outer
.set_boundary('BOUNDARY')
1129 self
.ndiffAssertEqual(outer
.as_string(), '''\
1130 Content-Type: multipart/mixed; boundary="BOUNDARY"
1134 From: bperson@dom.ain
1142 def test_one_part_in_a_multipart(self
):
1143 eq
= self
.ndiffAssertEqual
1144 outer
= MIMEBase('multipart', 'mixed')
1145 outer
['Subject'] = 'A subject'
1146 outer
['To'] = 'aperson@dom.ain'
1147 outer
['From'] = 'bperson@dom.ain'
1148 outer
.set_boundary('BOUNDARY')
1149 msg
= MIMEText('hello world')
1151 eq(outer
.as_string(), '''\
1152 Content-Type: multipart/mixed; boundary="BOUNDARY"
1156 From: bperson@dom.ain
1159 Content-Type: text/plain; charset="us-ascii"
1161 Content-Transfer-Encoding: 7bit
1166 def test_seq_parts_in_a_multipart_with_empty_preamble(self
):
1167 eq
= self
.ndiffAssertEqual
1168 outer
= MIMEBase('multipart', 'mixed')
1169 outer
['Subject'] = 'A subject'
1170 outer
['To'] = 'aperson@dom.ain'
1171 outer
['From'] = 'bperson@dom.ain'
1173 msg
= MIMEText('hello world')
1175 outer
.set_boundary('BOUNDARY')
1176 eq(outer
.as_string(), '''\
1177 Content-Type: multipart/mixed; boundary="BOUNDARY"
1181 From: bperson@dom.ain
1185 Content-Type: text/plain; charset="us-ascii"
1187 Content-Transfer-Encoding: 7bit
1193 def test_seq_parts_in_a_multipart_with_none_preamble(self
):
1194 eq
= self
.ndiffAssertEqual
1195 outer
= MIMEBase('multipart', 'mixed')
1196 outer
['Subject'] = 'A subject'
1197 outer
['To'] = 'aperson@dom.ain'
1198 outer
['From'] = 'bperson@dom.ain'
1199 outer
.preamble
= None
1200 msg
= MIMEText('hello world')
1202 outer
.set_boundary('BOUNDARY')
1203 eq(outer
.as_string(), '''\
1204 Content-Type: multipart/mixed; boundary="BOUNDARY"
1208 From: bperson@dom.ain
1211 Content-Type: text/plain; charset="us-ascii"
1213 Content-Transfer-Encoding: 7bit
1219 def test_seq_parts_in_a_multipart_with_none_epilogue(self
):
1220 eq
= self
.ndiffAssertEqual
1221 outer
= MIMEBase('multipart', 'mixed')
1222 outer
['Subject'] = 'A subject'
1223 outer
['To'] = 'aperson@dom.ain'
1224 outer
['From'] = 'bperson@dom.ain'
1225 outer
.epilogue
= None
1226 msg
= MIMEText('hello world')
1228 outer
.set_boundary('BOUNDARY')
1229 eq(outer
.as_string(), '''\
1230 Content-Type: multipart/mixed; boundary="BOUNDARY"
1234 From: bperson@dom.ain
1237 Content-Type: text/plain; charset="us-ascii"
1239 Content-Transfer-Encoding: 7bit
1245 def test_seq_parts_in_a_multipart_with_empty_epilogue(self
):
1246 eq
= self
.ndiffAssertEqual
1247 outer
= MIMEBase('multipart', 'mixed')
1248 outer
['Subject'] = 'A subject'
1249 outer
['To'] = 'aperson@dom.ain'
1250 outer
['From'] = 'bperson@dom.ain'
1252 msg
= MIMEText('hello world')
1254 outer
.set_boundary('BOUNDARY')
1255 eq(outer
.as_string(), '''\
1256 Content-Type: multipart/mixed; boundary="BOUNDARY"
1260 From: bperson@dom.ain
1263 Content-Type: text/plain; charset="us-ascii"
1265 Content-Transfer-Encoding: 7bit
1272 def test_seq_parts_in_a_multipart_with_nl_epilogue(self
):
1273 eq
= self
.ndiffAssertEqual
1274 outer
= MIMEBase('multipart', 'mixed')
1275 outer
['Subject'] = 'A subject'
1276 outer
['To'] = 'aperson@dom.ain'
1277 outer
['From'] = 'bperson@dom.ain'
1278 outer
.epilogue
= '\n'
1279 msg
= MIMEText('hello world')
1281 outer
.set_boundary('BOUNDARY')
1282 eq(outer
.as_string(), '''\
1283 Content-Type: multipart/mixed; boundary="BOUNDARY"
1287 From: bperson@dom.ain
1290 Content-Type: text/plain; charset="us-ascii"
1292 Content-Transfer-Encoding: 7bit
1299 def test_message_external_body(self
):
1300 eq
= self
.assertEqual
1301 msg
= self
._msgobj
('msg_36.txt')
1302 eq(len(msg
.get_payload()), 2)
1303 msg1
= msg
.get_payload(1)
1304 eq(msg1
.get_content_type(), 'multipart/alternative')
1305 eq(len(msg1
.get_payload()), 2)
1306 for subpart
in msg1
.get_payload():
1307 eq(subpart
.get_content_type(), 'message/external-body')
1308 eq(len(subpart
.get_payload()), 1)
1309 subsubpart
= subpart
.get_payload(0)
1310 eq(subsubpart
.get_content_type(), 'text/plain')
1312 def test_double_boundary(self
):
1313 # msg_37.txt is a multipart that contains two dash-boundary's in a
1314 # row. Our interpretation of RFC 2046 calls for ignoring the second
1315 # and subsequent boundaries.
1316 msg
= self
._msgobj
('msg_37.txt')
1317 self
.assertEqual(len(msg
.get_payload()), 3)
1319 def test_nested_inner_contains_outer_boundary(self
):
1320 eq
= self
.ndiffAssertEqual
1321 # msg_38.txt has an inner part that contains outer boundaries. My
1322 # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
1323 # these are illegal and should be interpreted as unterminated inner
1325 msg
= self
._msgobj
('msg_38.txt')
1327 iterators
._structure
(msg
, sfp
)
1328 eq(sfp
.getvalue(), """\
1331 multipart/alternative
1338 def test_nested_with_same_boundary(self
):
1339 eq
= self
.ndiffAssertEqual
1340 # msg 39.txt is similarly evil in that it's got inner parts that use
1341 # the same boundary as outer parts. Again, I believe the way this is
1342 # parsed is closest to the spirit of RFC 2046
1343 msg
= self
._msgobj
('msg_39.txt')
1345 iterators
._structure
(msg
, sfp
)
1346 eq(sfp
.getvalue(), """\
1349 multipart/alternative
1350 application/octet-stream
1351 application/octet-stream
1355 def test_boundary_in_non_multipart(self
):
1356 msg
= self
._msgobj
('msg_40.txt')
1357 self
.assertEqual(msg
.as_string(), '''\
1359 Content-Type: text/html; boundary="--961284236552522269"
1361 ----961284236552522269
1362 Content-Type: text/html;
1363 Content-Transfer-Encoding: 7Bit
1367 ----961284236552522269--
1370 def test_boundary_with_leading_space(self
):
1371 eq
= self
.assertEqual
1372 msg
= email
.message_from_string('''\
1374 Content-Type: multipart/mixed; boundary=" XXXX"
1377 Content-Type: text/plain
1381 Content-Type: text/plain
1385 self
.assertTrue(msg
.is_multipart())
1386 eq(msg
.get_boundary(), ' XXXX')
1387 eq(len(msg
.get_payload()), 2)
1389 def test_boundary_without_trailing_newline(self
):
1390 m
= Parser().parsestr("""\
1391 Content-Type: multipart/mixed; boundary="===============0012394164=="
1394 --===============0012394164==
1395 Content-Type: image/file1.jpg
1397 Content-Transfer-Encoding: base64
1400 --===============0012394164==--""")
1401 self
.assertEqual(m
.get_payload(0).get_payload(), 'YXNkZg==')
1405 # Test some badly formatted messages
1406 class TestNonConformant(TestEmailBase
):
1407 def test_parse_missing_minor_type(self
):
1408 eq
= self
.assertEqual
1409 msg
= self
._msgobj
('msg_14.txt')
1410 eq(msg
.get_content_type(), 'text/plain')
1411 eq(msg
.get_content_maintype(), 'text')
1412 eq(msg
.get_content_subtype(), 'plain')
1414 def test_same_boundary_inner_outer(self
):
1415 unless
= self
.assertTrue
1416 msg
= self
._msgobj
('msg_15.txt')
1417 # XXX We can probably eventually do better
1418 inner
= msg
.get_payload(0)
1419 unless(hasattr(inner
, 'defects'))
1420 self
.assertEqual(len(inner
.defects
), 1)
1421 unless(isinstance(inner
.defects
[0],
1422 errors
.StartBoundaryNotFoundDefect
))
1424 def test_multipart_no_boundary(self
):
1425 unless
= self
.assertTrue
1426 msg
= self
._msgobj
('msg_25.txt')
1427 unless(isinstance(msg
.get_payload(), str))
1428 self
.assertEqual(len(msg
.defects
), 2)
1429 unless(isinstance(msg
.defects
[0], errors
.NoBoundaryInMultipartDefect
))
1430 unless(isinstance(msg
.defects
[1],
1431 errors
.MultipartInvariantViolationDefect
))
1433 def test_invalid_content_type(self
):
1434 eq
= self
.assertEqual
1435 neq
= self
.ndiffAssertEqual
1437 # RFC 2045, $5.2 says invalid yields text/plain
1438 msg
['Content-Type'] = 'text'
1439 eq(msg
.get_content_maintype(), 'text')
1440 eq(msg
.get_content_subtype(), 'plain')
1441 eq(msg
.get_content_type(), 'text/plain')
1442 # Clear the old value and try something /really/ invalid
1443 del msg
['content-type']
1444 msg
['Content-Type'] = 'foo'
1445 eq(msg
.get_content_maintype(), 'text')
1446 eq(msg
.get_content_subtype(), 'plain')
1447 eq(msg
.get_content_type(), 'text/plain')
1448 # Still, make sure that the message is idempotently generated
1452 neq(s
.getvalue(), 'Content-Type: foo\n\n')
1454 def test_no_start_boundary(self
):
1455 eq
= self
.ndiffAssertEqual
1456 msg
= self
._msgobj
('msg_31.txt')
1457 eq(msg
.get_payload(), """\
1459 Content-Type: text/plain
1464 Content-Type: text/plain
1471 def test_no_separating_blank_line(self
):
1472 eq
= self
.ndiffAssertEqual
1473 msg
= self
._msgobj
('msg_35.txt')
1474 eq(msg
.as_string(), """\
1475 From: aperson@dom.ain
1477 Subject: here's something interesting
1479 counter to RFC 2822, there's no separating newline here
1482 def test_lying_multipart(self
):
1483 unless
= self
.assertTrue
1484 msg
= self
._msgobj
('msg_41.txt')
1485 unless(hasattr(msg
, 'defects'))
1486 self
.assertEqual(len(msg
.defects
), 2)
1487 unless(isinstance(msg
.defects
[0], errors
.NoBoundaryInMultipartDefect
))
1488 unless(isinstance(msg
.defects
[1],
1489 errors
.MultipartInvariantViolationDefect
))
1491 def test_missing_start_boundary(self
):
1492 outer
= self
._msgobj
('msg_42.txt')
1493 # The message structure is:
1498 # multipart/mixed [*]
1500 # [*] This message is missing its start boundary
1501 bad
= outer
.get_payload(1).get_payload(0)
1502 self
.assertEqual(len(bad
.defects
), 1)
1503 self
.assertTrue(isinstance(bad
.defects
[0],
1504 errors
.StartBoundaryNotFoundDefect
))
1506 def test_first_line_is_continuation_header(self
):
1507 eq
= self
.assertEqual
1508 m
= ' Line 1\nLine 2\nLine 3'
1509 msg
= email
.message_from_string(m
)
1511 eq(msg
.get_payload(), 'Line 2\nLine 3')
1512 eq(len(msg
.defects
), 1)
1513 self
.assertTrue(isinstance(msg
.defects
[0],
1514 errors
.FirstHeaderLineIsContinuationDefect
))
1515 eq(msg
.defects
[0].line
, ' Line 1\n')
1519 # Test RFC 2047 header encoding and decoding
1520 class TestRFC2047(unittest
.TestCase
):
1521 def test_rfc2047_multiline(self
):
1522 eq
= self
.assertEqual
1523 s
= """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1524 foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1525 dh
= decode_header(s
)
1528 ('r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1529 ('baz foo bar', None),
1530 ('r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1531 eq(str(make_header(dh
)),
1532 """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar
1533 =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""")
1535 def test_whitespace_eater_unicode(self
):
1536 eq
= self
.assertEqual
1537 s
= '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1538 dh
= decode_header(s
)
1539 eq(dh
, [('Andr\xe9', 'iso-8859-1'), ('Pirard <pirard@dom.ain>', None)])
1540 hu
= unicode(make_header(dh
)).encode('latin-1')
1541 eq(hu
, 'Andr\xe9 Pirard <pirard@dom.ain>')
1543 def test_whitespace_eater_unicode_2(self
):
1544 eq
= self
.assertEqual
1545 s
= 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1546 dh
= decode_header(s
)
1547 eq(dh
, [('The', None), ('quick brown fox', 'iso-8859-1'),
1548 ('jumped over the', None), ('lazy dog', 'iso-8859-1')])
1549 hu
= make_header(dh
).__unicode
__()
1550 eq(hu
, u
'The quick brown fox jumped over the lazy dog')
1552 def test_rfc2047_missing_whitespace(self
):
1553 s
= 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
1554 dh
= decode_header(s
)
1555 self
.assertEqual(dh
, [(s
, None)])
1557 def test_rfc2047_with_whitespace(self
):
1558 s
= 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
1559 dh
= decode_header(s
)
1560 self
.assertEqual(dh
, [('Sm', None), ('\xf6', 'iso-8859-1'),
1561 ('rg', None), ('\xe5', 'iso-8859-1'),
1566 # Test the MIMEMessage class
1567 class TestMIMEMessage(TestEmailBase
):
1569 fp
= openfile('msg_11.txt')
1571 self
._text
= fp
.read()
1575 def test_type_error(self
):
1576 self
.assertRaises(TypeError, MIMEMessage
, 'a plain string')
1578 def test_valid_argument(self
):
1579 eq
= self
.assertEqual
1580 unless
= self
.assertTrue
1581 subject
= 'A sub-message'
1583 m
['Subject'] = subject
1585 eq(r
.get_content_type(), 'message/rfc822')
1586 payload
= r
.get_payload()
1587 unless(isinstance(payload
, list))
1589 subpart
= payload
[0]
1590 unless(subpart
is m
)
1591 eq(subpart
['subject'], subject
)
1593 def test_bad_multipart(self
):
1594 eq
= self
.assertEqual
1596 msg1
['Subject'] = 'subpart 1'
1598 msg2
['Subject'] = 'subpart 2'
1599 r
= MIMEMessage(msg1
)
1600 self
.assertRaises(errors
.MultipartConversionError
, r
.attach
, msg2
)
1602 def test_generate(self
):
1603 # First craft the message to be encapsulated
1605 m
['Subject'] = 'An enclosed message'
1606 m
.set_payload('Here is the body of the message.\n')
1608 r
['Subject'] = 'The enclosing message'
1612 self
.assertEqual(s
.getvalue(), """\
1613 Content-Type: message/rfc822
1615 Subject: The enclosing message
1617 Subject: An enclosed message
1619 Here is the body of the message.
1622 def test_parse_message_rfc822(self
):
1623 eq
= self
.assertEqual
1624 unless
= self
.assertTrue
1625 msg
= self
._msgobj
('msg_11.txt')
1626 eq(msg
.get_content_type(), 'message/rfc822')
1627 payload
= msg
.get_payload()
1628 unless(isinstance(payload
, list))
1631 self
.assertTrue(isinstance(submsg
, Message
))
1632 eq(submsg
['subject'], 'An enclosed message')
1633 eq(submsg
.get_payload(), 'Here is the body of the message.\n')
1636 eq
= self
.assertEqual
1637 unless
= self
.assertTrue
1638 # msg 16 is a Delivery Status Notification, see RFC 1894
1639 msg
= self
._msgobj
('msg_16.txt')
1640 eq(msg
.get_content_type(), 'multipart/report')
1641 unless(msg
.is_multipart())
1642 eq(len(msg
.get_payload()), 3)
1643 # Subpart 1 is a text/plain, human readable section
1644 subpart
= msg
.get_payload(0)
1645 eq(subpart
.get_content_type(), 'text/plain')
1646 eq(subpart
.get_payload(), """\
1647 This report relates to a message you sent with the following header fields:
1649 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1650 Date: Sun, 23 Sep 2001 20:10:55 -0700
1651 From: "Ian T. Henry" <henryi@oxy.edu>
1652 To: SoCal Raves <scr@socal-raves.org>
1653 Subject: [scr] yeah for Ians!!
1655 Your message cannot be delivered to the following recipients:
1657 Recipient address: jangel1@cougar.noc.ucla.edu
1658 Reason: recipient reached disk quota
1661 # Subpart 2 contains the machine parsable DSN information. It
1662 # consists of two blocks of headers, represented by two nested Message
1664 subpart
= msg
.get_payload(1)
1665 eq(subpart
.get_content_type(), 'message/delivery-status')
1666 eq(len(subpart
.get_payload()), 2)
1667 # message/delivery-status should treat each block as a bunch of
1668 # headers, i.e. a bunch of Message objects.
1669 dsn1
= subpart
.get_payload(0)
1670 unless(isinstance(dsn1
, Message
))
1671 eq(dsn1
['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1672 eq(dsn1
.get_param('dns', header
='reporting-mta'), '')
1673 # Try a missing one <wink>
1674 eq(dsn1
.get_param('nsd', header
='reporting-mta'), None)
1675 dsn2
= subpart
.get_payload(1)
1676 unless(isinstance(dsn2
, Message
))
1677 eq(dsn2
['action'], 'failed')
1678 eq(dsn2
.get_params(header
='original-recipient'),
1679 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1680 eq(dsn2
.get_param('rfc822', header
='final-recipient'), '')
1681 # Subpart 3 is the original message
1682 subpart
= msg
.get_payload(2)
1683 eq(subpart
.get_content_type(), 'message/rfc822')
1684 payload
= subpart
.get_payload()
1685 unless(isinstance(payload
, list))
1687 subsubpart
= payload
[0]
1688 unless(isinstance(subsubpart
, Message
))
1689 eq(subsubpart
.get_content_type(), 'text/plain')
1690 eq(subsubpart
['message-id'],
1691 '<002001c144a6$8752e060$56104586@oxy.edu>')
1693 def test_epilogue(self
):
1694 eq
= self
.ndiffAssertEqual
1695 fp
= openfile('msg_21.txt')
1701 msg
['From'] = 'aperson@dom.ain'
1702 msg
['To'] = 'bperson@dom.ain'
1703 msg
['Subject'] = 'Test'
1704 msg
.preamble
= 'MIME message'
1705 msg
.epilogue
= 'End of MIME message\n'
1706 msg1
= MIMEText('One')
1707 msg2
= MIMEText('Two')
1708 msg
.add_header('Content-Type', 'multipart/mixed', boundary
='BOUNDARY')
1714 eq(sfp
.getvalue(), text
)
1716 def test_no_nl_preamble(self
):
1717 eq
= self
.ndiffAssertEqual
1719 msg
['From'] = 'aperson@dom.ain'
1720 msg
['To'] = 'bperson@dom.ain'
1721 msg
['Subject'] = 'Test'
1722 msg
.preamble
= 'MIME message'
1724 msg1
= MIMEText('One')
1725 msg2
= MIMEText('Two')
1726 msg
.add_header('Content-Type', 'multipart/mixed', boundary
='BOUNDARY')
1729 eq(msg
.as_string(), """\
1730 From: aperson@dom.ain
1733 Content-Type: multipart/mixed; boundary="BOUNDARY"
1737 Content-Type: text/plain; charset="us-ascii"
1739 Content-Transfer-Encoding: 7bit
1743 Content-Type: text/plain; charset="us-ascii"
1745 Content-Transfer-Encoding: 7bit
1751 def test_default_type(self
):
1752 eq
= self
.assertEqual
1753 fp
= openfile('msg_30.txt')
1755 msg
= email
.message_from_file(fp
)
1758 container1
= msg
.get_payload(0)
1759 eq(container1
.get_default_type(), 'message/rfc822')
1760 eq(container1
.get_content_type(), 'message/rfc822')
1761 container2
= msg
.get_payload(1)
1762 eq(container2
.get_default_type(), 'message/rfc822')
1763 eq(container2
.get_content_type(), 'message/rfc822')
1764 container1a
= container1
.get_payload(0)
1765 eq(container1a
.get_default_type(), 'text/plain')
1766 eq(container1a
.get_content_type(), 'text/plain')
1767 container2a
= container2
.get_payload(0)
1768 eq(container2a
.get_default_type(), 'text/plain')
1769 eq(container2a
.get_content_type(), 'text/plain')
1771 def test_default_type_with_explicit_container_type(self
):
1772 eq
= self
.assertEqual
1773 fp
= openfile('msg_28.txt')
1775 msg
= email
.message_from_file(fp
)
1778 container1
= msg
.get_payload(0)
1779 eq(container1
.get_default_type(), 'message/rfc822')
1780 eq(container1
.get_content_type(), 'message/rfc822')
1781 container2
= msg
.get_payload(1)
1782 eq(container2
.get_default_type(), 'message/rfc822')
1783 eq(container2
.get_content_type(), 'message/rfc822')
1784 container1a
= container1
.get_payload(0)
1785 eq(container1a
.get_default_type(), 'text/plain')
1786 eq(container1a
.get_content_type(), 'text/plain')
1787 container2a
= container2
.get_payload(0)
1788 eq(container2a
.get_default_type(), 'text/plain')
1789 eq(container2a
.get_content_type(), 'text/plain')
1791 def test_default_type_non_parsed(self
):
1792 eq
= self
.assertEqual
1793 neq
= self
.ndiffAssertEqual
1795 container
= MIMEMultipart('digest', 'BOUNDARY')
1796 container
.epilogue
= ''
1798 subpart1a
= MIMEText('message 1\n')
1799 subpart2a
= MIMEText('message 2\n')
1800 subpart1
= MIMEMessage(subpart1a
)
1801 subpart2
= MIMEMessage(subpart2a
)
1802 container
.attach(subpart1
)
1803 container
.attach(subpart2
)
1804 eq(subpart1
.get_content_type(), 'message/rfc822')
1805 eq(subpart1
.get_default_type(), 'message/rfc822')
1806 eq(subpart2
.get_content_type(), 'message/rfc822')
1807 eq(subpart2
.get_default_type(), 'message/rfc822')
1808 neq(container
.as_string(0), '''\
1809 Content-Type: multipart/digest; boundary="BOUNDARY"
1813 Content-Type: message/rfc822
1816 Content-Type: text/plain; charset="us-ascii"
1818 Content-Transfer-Encoding: 7bit
1823 Content-Type: message/rfc822
1826 Content-Type: text/plain; charset="us-ascii"
1828 Content-Transfer-Encoding: 7bit
1834 del subpart1
['content-type']
1835 del subpart1
['mime-version']
1836 del subpart2
['content-type']
1837 del subpart2
['mime-version']
1838 eq(subpart1
.get_content_type(), 'message/rfc822')
1839 eq(subpart1
.get_default_type(), 'message/rfc822')
1840 eq(subpart2
.get_content_type(), 'message/rfc822')
1841 eq(subpart2
.get_default_type(), 'message/rfc822')
1842 neq(container
.as_string(0), '''\
1843 Content-Type: multipart/digest; boundary="BOUNDARY"
1848 Content-Type: text/plain; charset="us-ascii"
1850 Content-Transfer-Encoding: 7bit
1856 Content-Type: text/plain; charset="us-ascii"
1858 Content-Transfer-Encoding: 7bit
1865 def test_mime_attachments_in_constructor(self
):
1866 eq
= self
.assertEqual
1867 text1
= MIMEText('')
1868 text2
= MIMEText('')
1869 msg
= MIMEMultipart(_subparts
=(text1
, text2
))
1870 eq(len(msg
.get_payload()), 2)
1871 eq(msg
.get_payload(0), text1
)
1872 eq(msg
.get_payload(1), text2
)
1876 # A general test of parser->model->generator idempotency. IOW, read a message
1877 # in, parse it into a message object tree, then without touching the tree,
1878 # regenerate the plain text. The original text and the transformed text
1879 # should be identical. Note: that we ignore the Unix-From since that may
1880 # contain a changed date.
1881 class TestIdempotent(TestEmailBase
):
1882 def _msgobj(self
, filename
):
1883 fp
= openfile(filename
)
1888 msg
= email
.message_from_string(data
)
1891 def _idempotent(self
, msg
, text
):
1892 eq
= self
.ndiffAssertEqual
1894 g
= Generator(s
, maxheaderlen
=0)
1896 eq(text
, s
.getvalue())
1898 def test_parse_text_message(self
):
1899 eq
= self
.assertEqual
1900 msg
, text
= self
._msgobj
('msg_01.txt')
1901 eq(msg
.get_content_type(), 'text/plain')
1902 eq(msg
.get_content_maintype(), 'text')
1903 eq(msg
.get_content_subtype(), 'plain')
1904 eq(msg
.get_params()[1], ('charset', 'us-ascii'))
1905 eq(msg
.get_param('charset'), 'us-ascii')
1906 eq(msg
.preamble
, None)
1907 eq(msg
.epilogue
, None)
1908 self
._idempotent
(msg
, text
)
1910 def test_parse_untyped_message(self
):
1911 eq
= self
.assertEqual
1912 msg
, text
= self
._msgobj
('msg_03.txt')
1913 eq(msg
.get_content_type(), 'text/plain')
1914 eq(msg
.get_params(), None)
1915 eq(msg
.get_param('charset'), None)
1916 self
._idempotent
(msg
, text
)
1918 def test_simple_multipart(self
):
1919 msg
, text
= self
._msgobj
('msg_04.txt')
1920 self
._idempotent
(msg
, text
)
1922 def test_MIME_digest(self
):
1923 msg
, text
= self
._msgobj
('msg_02.txt')
1924 self
._idempotent
(msg
, text
)
1926 def test_long_header(self
):
1927 msg
, text
= self
._msgobj
('msg_27.txt')
1928 self
._idempotent
(msg
, text
)
1930 def test_MIME_digest_with_part_headers(self
):
1931 msg
, text
= self
._msgobj
('msg_28.txt')
1932 self
._idempotent
(msg
, text
)
1934 def test_mixed_with_image(self
):
1935 msg
, text
= self
._msgobj
('msg_06.txt')
1936 self
._idempotent
(msg
, text
)
1938 def test_multipart_report(self
):
1939 msg
, text
= self
._msgobj
('msg_05.txt')
1940 self
._idempotent
(msg
, text
)
1943 msg
, text
= self
._msgobj
('msg_16.txt')
1944 self
._idempotent
(msg
, text
)
1946 def test_preamble_epilogue(self
):
1947 msg
, text
= self
._msgobj
('msg_21.txt')
1948 self
._idempotent
(msg
, text
)
1950 def test_multipart_one_part(self
):
1951 msg
, text
= self
._msgobj
('msg_23.txt')
1952 self
._idempotent
(msg
, text
)
1954 def test_multipart_no_parts(self
):
1955 msg
, text
= self
._msgobj
('msg_24.txt')
1956 self
._idempotent
(msg
, text
)
1958 def test_no_start_boundary(self
):
1959 msg
, text
= self
._msgobj
('msg_31.txt')
1960 self
._idempotent
(msg
, text
)
1962 def test_rfc2231_charset(self
):
1963 msg
, text
= self
._msgobj
('msg_32.txt')
1964 self
._idempotent
(msg
, text
)
1966 def test_more_rfc2231_parameters(self
):
1967 msg
, text
= self
._msgobj
('msg_33.txt')
1968 self
._idempotent
(msg
, text
)
1970 def test_text_plain_in_a_multipart_digest(self
):
1971 msg
, text
= self
._msgobj
('msg_34.txt')
1972 self
._idempotent
(msg
, text
)
1974 def test_nested_multipart_mixeds(self
):
1975 msg
, text
= self
._msgobj
('msg_12a.txt')
1976 self
._idempotent
(msg
, text
)
1978 def test_message_external_body_idempotent(self
):
1979 msg
, text
= self
._msgobj
('msg_36.txt')
1980 self
._idempotent
(msg
, text
)
1982 def test_content_type(self
):
1983 eq
= self
.assertEqual
1984 unless
= self
.assertTrue
1985 # Get a message object and reset the seek pointer for other tests
1986 msg
, text
= self
._msgobj
('msg_05.txt')
1987 eq(msg
.get_content_type(), 'multipart/report')
1988 # Test the Content-Type: parameters
1990 for pk
, pv
in msg
.get_params():
1992 eq(params
['report-type'], 'delivery-status')
1993 eq(params
['boundary'], 'D1690A7AC1.996856090/mail.example.com')
1994 eq(msg
.preamble
, 'This is a MIME-encapsulated message.\n')
1995 eq(msg
.epilogue
, '\n')
1996 eq(len(msg
.get_payload()), 3)
1997 # Make sure the subparts are what we expect
1998 msg1
= msg
.get_payload(0)
1999 eq(msg1
.get_content_type(), 'text/plain')
2000 eq(msg1
.get_payload(), 'Yadda yadda yadda\n')
2001 msg2
= msg
.get_payload(1)
2002 eq(msg2
.get_content_type(), 'text/plain')
2003 eq(msg2
.get_payload(), 'Yadda yadda yadda\n')
2004 msg3
= msg
.get_payload(2)
2005 eq(msg3
.get_content_type(), 'message/rfc822')
2006 self
.assertTrue(isinstance(msg3
, Message
))
2007 payload
= msg3
.get_payload()
2008 unless(isinstance(payload
, list))
2011 unless(isinstance(msg4
, Message
))
2012 eq(msg4
.get_payload(), 'Yadda yadda yadda\n')
2014 def test_parser(self
):
2015 eq
= self
.assertEqual
2016 unless
= self
.assertTrue
2017 msg
, text
= self
._msgobj
('msg_06.txt')
2018 # Check some of the outer headers
2019 eq(msg
.get_content_type(), 'message/rfc822')
2020 # Make sure the payload is a list of exactly one sub-Message, and that
2021 # that submessage has a type of text/plain
2022 payload
= msg
.get_payload()
2023 unless(isinstance(payload
, list))
2026 self
.assertTrue(isinstance(msg1
, Message
))
2027 eq(msg1
.get_content_type(), 'text/plain')
2028 self
.assertTrue(isinstance(msg1
.get_payload(), str))
2029 eq(msg1
.get_payload(), '\n')
2033 # Test various other bits of the package's functionality
2034 class TestMiscellaneous(TestEmailBase
):
2035 def test_message_from_string(self
):
2036 fp
= openfile('msg_01.txt')
2041 msg
= email
.message_from_string(text
)
2043 # Don't wrap/continue long headers since we're trying to test
2045 g
= Generator(s
, maxheaderlen
=0)
2047 self
.assertEqual(text
, s
.getvalue())
2049 def test_message_from_file(self
):
2050 fp
= openfile('msg_01.txt')
2054 msg
= email
.message_from_file(fp
)
2056 # Don't wrap/continue long headers since we're trying to test
2058 g
= Generator(s
, maxheaderlen
=0)
2060 self
.assertEqual(text
, s
.getvalue())
2064 def test_message_from_string_with_class(self
):
2065 unless
= self
.assertTrue
2066 fp
= openfile('msg_01.txt')
2072 class MyMessage(Message
):
2075 msg
= email
.message_from_string(text
, MyMessage
)
2076 unless(isinstance(msg
, MyMessage
))
2077 # Try something more complicated
2078 fp
= openfile('msg_02.txt')
2083 msg
= email
.message_from_string(text
, MyMessage
)
2084 for subpart
in msg
.walk():
2085 unless(isinstance(subpart
, MyMessage
))
2087 def test_message_from_file_with_class(self
):
2088 unless
= self
.assertTrue
2090 class MyMessage(Message
):
2093 fp
= openfile('msg_01.txt')
2095 msg
= email
.message_from_file(fp
, MyMessage
)
2098 unless(isinstance(msg
, MyMessage
))
2099 # Try something more complicated
2100 fp
= openfile('msg_02.txt')
2102 msg
= email
.message_from_file(fp
, MyMessage
)
2105 for subpart
in msg
.walk():
2106 unless(isinstance(subpart
, MyMessage
))
2108 def test__all__(self
):
2109 module
= __import__('email')
2110 # Can't use sorted() here due to Python 2.3 compatibility
2111 all
= module
.__all
__[:]
2113 self
.assertEqual(all
, [
2115 'Charset', 'Encoders', 'Errors', 'Generator',
2116 'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
2117 'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
2118 'MIMENonMultipart', 'MIMEText', 'Message',
2119 'Parser', 'Utils', 'base64MIME',
2121 'base64mime', 'charset', 'encoders', 'errors', 'generator',
2122 'header', 'iterators', 'message', 'message_from_file',
2123 'message_from_string', 'mime', 'parser',
2124 'quopriMIME', 'quoprimime', 'utils',
2127 def test_formatdate(self
):
2129 self
.assertEqual(utils
.parsedate(utils
.formatdate(now
))[:6],
2130 time
.gmtime(now
)[:6])
2132 def test_formatdate_localtime(self
):
2135 utils
.parsedate(utils
.formatdate(now
, localtime
=True))[:6],
2136 time
.localtime(now
)[:6])
2138 def test_formatdate_usegmt(self
):
2141 utils
.formatdate(now
, localtime
=False),
2142 time
.strftime('%a, %d %b %Y %H:%M:%S -0000', time
.gmtime(now
)))
2144 utils
.formatdate(now
, localtime
=False, usegmt
=True),
2145 time
.strftime('%a, %d %b %Y %H:%M:%S GMT', time
.gmtime(now
)))
2147 def test_parsedate_none(self
):
2148 self
.assertEqual(utils
.parsedate(''), None)
2150 def test_parsedate_compact(self
):
2151 # The FWS after the comma is optional
2152 self
.assertEqual(utils
.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2153 utils
.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2155 def test_parsedate_no_dayofweek(self
):
2156 eq
= self
.assertEqual
2157 eq(utils
.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2158 (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
2160 def test_parsedate_compact_no_dayofweek(self
):
2161 eq
= self
.assertEqual
2162 eq(utils
.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2163 (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
2165 def test_parsedate_acceptable_to_time_functions(self
):
2166 eq
= self
.assertEqual
2167 timetup
= utils
.parsedate('5 Feb 2003 13:47:26 -0800')
2168 t
= int(time
.mktime(timetup
))
2169 eq(time
.localtime(t
)[:6], timetup
[:6])
2170 eq(int(time
.strftime('%Y', timetup
)), 2003)
2171 timetup
= utils
.parsedate_tz('5 Feb 2003 13:47:26 -0800')
2172 t
= int(time
.mktime(timetup
[:9]))
2173 eq(time
.localtime(t
)[:6], timetup
[:6])
2174 eq(int(time
.strftime('%Y', timetup
[:9])), 2003)
2176 def test_parseaddr_empty(self
):
2177 self
.assertEqual(utils
.parseaddr('<>'), ('', ''))
2178 self
.assertEqual(utils
.formataddr(utils
.parseaddr('<>')), '')
2180 def test_noquote_dump(self
):
2182 utils
.formataddr(('A Silly Person', 'person@dom.ain')),
2183 'A Silly Person <person@dom.ain>')
2185 def test_escape_dump(self
):
2187 utils
.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
2188 r
'"A \(Very\) Silly Person" <person@dom.ain>')
2189 a
= r
'A \(Special\) Person'
2190 b
= 'person@dom.ain'
2191 self
.assertEqual(utils
.parseaddr(utils
.formataddr((a
, b
))), (a
, b
))
2193 def test_escape_backslashes(self
):
2195 utils
.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
2196 r
'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
2197 a
= r
'Arthur \Backslash\ Foobar'
2198 b
= 'person@dom.ain'
2199 self
.assertEqual(utils
.parseaddr(utils
.formataddr((a
, b
))), (a
, b
))
2201 def test_name_with_dot(self
):
2202 x
= 'John X. Doe <jxd@example.com>'
2203 y
= '"John X. Doe" <jxd@example.com>'
2204 a
, b
= ('John X. Doe', 'jxd@example.com')
2205 self
.assertEqual(utils
.parseaddr(x
), (a
, b
))
2206 self
.assertEqual(utils
.parseaddr(y
), (a
, b
))
2207 # formataddr() quotes the name if there's a dot in it
2208 self
.assertEqual(utils
.formataddr((a
, b
)), y
)
2210 def test_multiline_from_comment(self
):
2213 \tBar <foo@example.com>"""
2214 self
.assertEqual(utils
.parseaddr(x
), ('Foo Bar', 'foo@example.com'))
2216 def test_quote_dump(self
):
2218 utils
.formataddr(('A Silly; Person', 'person@dom.ain')),
2219 r
'"A Silly; Person" <person@dom.ain>')
2221 def test_fix_eols(self
):
2222 eq
= self
.assertEqual
2223 eq(utils
.fix_eols('hello'), 'hello')
2224 eq(utils
.fix_eols('hello\n'), 'hello\r\n')
2225 eq(utils
.fix_eols('hello\r'), 'hello\r\n')
2226 eq(utils
.fix_eols('hello\r\n'), 'hello\r\n')
2227 eq(utils
.fix_eols('hello\n\r'), 'hello\r\n\r\n')
2229 def test_charset_richcomparisons(self
):
2230 eq
= self
.assertEqual
2231 ne
= self
.assertNotEqual
2234 eq(cset1
, 'us-ascii')
2235 eq(cset1
, 'US-ASCII')
2236 eq(cset1
, 'Us-AsCiI')
2237 eq('us-ascii', cset1
)
2238 eq('US-ASCII', cset1
)
2239 eq('Us-AsCiI', cset1
)
2240 ne(cset1
, 'usascii')
2241 ne(cset1
, 'USASCII')
2242 ne(cset1
, 'UsAsCiI')
2243 ne('usascii', cset1
)
2244 ne('USASCII', cset1
)
2245 ne('UsAsCiI', cset1
)
2249 def test_getaddresses(self
):
2250 eq
= self
.assertEqual
2251 eq(utils
.getaddresses(['aperson@dom.ain (Al Person)',
2252 'Bud Person <bperson@dom.ain>']),
2253 [('Al Person', 'aperson@dom.ain'),
2254 ('Bud Person', 'bperson@dom.ain')])
2256 def test_getaddresses_nasty(self
):
2257 eq
= self
.assertEqual
2258 eq(utils
.getaddresses(['foo: ;']), [('', '')])
2259 eq(utils
.getaddresses(
2261 [('', ''), ('', ''), ('', '*--')])
2262 eq(utils
.getaddresses(
2263 ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
2264 [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
2266 def test_getaddresses_embedded_comment(self
):
2267 """Test proper handling of a nested comment"""
2268 eq
= self
.assertEqual
2269 addrs
= utils
.getaddresses(['User ((nested comment)) <foo@bar.com>'])
2270 eq(addrs
[0][1], 'foo@bar.com')
2272 def test_utils_quote_unquote(self
):
2273 eq
= self
.assertEqual
2275 msg
.add_header('content-disposition', 'attachment',
2276 filename
='foo\\wacky"name')
2277 eq(msg
.get_filename(), 'foo\\wacky"name')
2279 def test_get_body_encoding_with_bogus_charset(self
):
2280 charset
= Charset('not a charset')
2281 self
.assertEqual(charset
.get_body_encoding(), 'base64')
2283 def test_get_body_encoding_with_uppercase_charset(self
):
2284 eq
= self
.assertEqual
2286 msg
['Content-Type'] = 'text/plain; charset=UTF-8'
2287 eq(msg
['content-type'], 'text/plain; charset=UTF-8')
2288 charsets
= msg
.get_charsets()
2289 eq(len(charsets
), 1)
2290 eq(charsets
[0], 'utf-8')
2291 charset
= Charset(charsets
[0])
2292 eq(charset
.get_body_encoding(), 'base64')
2293 msg
.set_payload('hello world', charset
=charset
)
2294 eq(msg
.get_payload(), 'aGVsbG8gd29ybGQ=\n')
2295 eq(msg
.get_payload(decode
=True), 'hello world')
2296 eq(msg
['content-transfer-encoding'], 'base64')
2299 msg
['Content-Type'] = 'text/plain; charset="US-ASCII"'
2300 charsets
= msg
.get_charsets()
2301 eq(len(charsets
), 1)
2302 eq(charsets
[0], 'us-ascii')
2303 charset
= Charset(charsets
[0])
2304 eq(charset
.get_body_encoding(), encoders
.encode_7or8bit
)
2305 msg
.set_payload('hello world', charset
=charset
)
2306 eq(msg
.get_payload(), 'hello world')
2307 eq(msg
['content-transfer-encoding'], '7bit')
2309 def test_charsets_case_insensitive(self
):
2310 lc
= Charset('us-ascii')
2311 uc
= Charset('US-ASCII')
2312 self
.assertEqual(lc
.get_body_encoding(), uc
.get_body_encoding())
2314 def test_partial_falls_inside_message_delivery_status(self
):
2315 eq
= self
.ndiffAssertEqual
2316 # The Parser interface provides chunks of data to FeedParser in 8192
2317 # byte gulps. SF bug #1076485 found one of those chunks inside
2318 # message/delivery-status header block, which triggered an
2319 # unreadline() of NeedMoreData.
2320 msg
= self
._msgobj
('msg_43.txt')
2322 iterators
._structure
(msg
, sfp
)
2323 eq(sfp
.getvalue(), """\
2326 message/delivery-status
2358 # Test the iterator/generators
2359 class TestIterators(TestEmailBase
):
2360 def test_body_line_iterator(self
):
2361 eq
= self
.assertEqual
2362 neq
= self
.ndiffAssertEqual
2363 # First a simple non-multipart message
2364 msg
= self
._msgobj
('msg_01.txt')
2365 it
= iterators
.body_line_iterator(msg
)
2368 neq(EMPTYSTRING
.join(lines
), msg
.get_payload())
2369 # Now a more complicated multipart
2370 msg
= self
._msgobj
('msg_02.txt')
2371 it
= iterators
.body_line_iterator(msg
)
2374 fp
= openfile('msg_19.txt')
2376 neq(EMPTYSTRING
.join(lines
), fp
.read())
2380 def test_typed_subpart_iterator(self
):
2381 eq
= self
.assertEqual
2382 msg
= self
._msgobj
('msg_04.txt')
2383 it
= iterators
.typed_subpart_iterator(msg
, 'text')
2388 lines
.append(subpart
.get_payload())
2390 eq(EMPTYSTRING
.join(lines
), """\
2391 a simple kind of mirror
2392 to reflect upon our own
2393 a simple kind of mirror
2394 to reflect upon our own
2397 def test_typed_subpart_iterator_default_type(self
):
2398 eq
= self
.assertEqual
2399 msg
= self
._msgobj
('msg_03.txt')
2400 it
= iterators
.typed_subpart_iterator(msg
, 'text', 'plain')
2405 lines
.append(subpart
.get_payload())
2407 eq(EMPTYSTRING
.join(lines
), """\
2411 Do you like this message?
2418 class TestParsers(TestEmailBase
):
2419 def test_header_parser(self
):
2420 eq
= self
.assertEqual
2421 # Parse only the headers of a complex multipart MIME document
2422 fp
= openfile('msg_02.txt')
2424 msg
= HeaderParser().parse(fp
)
2427 eq(msg
['from'], 'ppp-request@zzz.org')
2428 eq(msg
['to'], 'ppp@zzz.org')
2429 eq(msg
.get_content_type(), 'multipart/mixed')
2430 self
.assertFalse(msg
.is_multipart())
2431 self
.assertTrue(isinstance(msg
.get_payload(), str))
2433 def test_whitespace_continuation(self
):
2434 eq
= self
.assertEqual
2435 # This message contains a line after the Subject: header that has only
2436 # whitespace, but it is not empty!
2437 msg
= email
.message_from_string("""\
2438 From: aperson@dom.ain
2440 Subject: the next line has a space on it
2442 Date: Mon, 8 Apr 2002 15:09:19 -0400
2445 Here's the message body
2447 eq(msg
['subject'], 'the next line has a space on it\n ')
2448 eq(msg
['message-id'], 'spam')
2449 eq(msg
.get_payload(), "Here's the message body\n")
2451 def test_whitespace_continuation_last_header(self
):
2452 eq
= self
.assertEqual
2453 # Like the previous test, but the subject line is the last
2455 msg
= email
.message_from_string("""\
2456 From: aperson@dom.ain
2458 Date: Mon, 8 Apr 2002 15:09:19 -0400
2460 Subject: the next line has a space on it
2463 Here's the message body
2465 eq(msg
['subject'], 'the next line has a space on it\n ')
2466 eq(msg
['message-id'], 'spam')
2467 eq(msg
.get_payload(), "Here's the message body\n")
2469 def test_crlf_separation(self
):
2470 eq
= self
.assertEqual
2471 fp
= openfile('msg_26.txt', mode
='rb')
2473 msg
= Parser().parse(fp
)
2476 eq(len(msg
.get_payload()), 2)
2477 part1
= msg
.get_payload(0)
2478 eq(part1
.get_content_type(), 'text/plain')
2479 eq(part1
.get_payload(), 'Simple email with attachment.\r\n\r\n')
2480 part2
= msg
.get_payload(1)
2481 eq(part2
.get_content_type(), 'application/riscos')
2483 def test_multipart_digest_with_extra_mime_headers(self
):
2484 eq
= self
.assertEqual
2485 neq
= self
.ndiffAssertEqual
2486 fp
= openfile('msg_28.txt')
2488 msg
= email
.message_from_file(fp
)
2497 eq(msg
.is_multipart(), 1)
2498 eq(len(msg
.get_payload()), 2)
2499 part1
= msg
.get_payload(0)
2500 eq(part1
.get_content_type(), 'message/rfc822')
2501 eq(part1
.is_multipart(), 1)
2502 eq(len(part1
.get_payload()), 1)
2503 part1a
= part1
.get_payload(0)
2504 eq(part1a
.is_multipart(), 0)
2505 eq(part1a
.get_content_type(), 'text/plain')
2506 neq(part1a
.get_payload(), 'message 1\n')
2507 # next message/rfc822
2508 part2
= msg
.get_payload(1)
2509 eq(part2
.get_content_type(), 'message/rfc822')
2510 eq(part2
.is_multipart(), 1)
2511 eq(len(part2
.get_payload()), 1)
2512 part2a
= part2
.get_payload(0)
2513 eq(part2a
.is_multipart(), 0)
2514 eq(part2a
.get_content_type(), 'text/plain')
2515 neq(part2a
.get_payload(), 'message 2\n')
2517 def test_three_lines(self
):
2518 # A bug report by Andrew McNamara
2519 lines
= ['From: Andrew Person <aperson@dom.ain',
2521 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2522 msg
= email
.message_from_string(NL
.join(lines
))
2523 self
.assertEqual(msg
['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2525 def test_strip_line_feed_and_carriage_return_in_headers(self
):
2526 eq
= self
.assertEqual
2527 # For [ 1002475 ] email message parser doesn't handle \r\n correctly
2529 value2
= 'more text'
2530 m
= 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
2532 msg
= email
.message_from_string(m
)
2533 eq(msg
.get('Header'), value1
)
2534 eq(msg
.get('Next-Header'), value2
)
2536 def test_rfc2822_header_syntax(self
):
2537 eq
= self
.assertEqual
2538 m
= '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2539 msg
= email
.message_from_string(m
)
2540 eq(len(msg
.keys()), 3)
2543 eq(keys
, ['!"#QUX;~', '>From', 'From'])
2544 eq(msg
.get_payload(), 'body')
2546 def test_rfc2822_space_not_allowed_in_header(self
):
2547 eq
= self
.assertEqual
2548 m
= '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2549 msg
= email
.message_from_string(m
)
2550 eq(len(msg
.keys()), 0)
2552 def test_rfc2822_one_character_header(self
):
2553 eq
= self
.assertEqual
2554 m
= 'A: first header\nB: second header\nCC: third header\n\nbody'
2555 msg
= email
.message_from_string(m
)
2556 headers
= msg
.keys()
2558 eq(headers
, ['A', 'B', 'CC'])
2559 eq(msg
.get_payload(), 'body')
2563 class TestBase64(unittest
.TestCase
):
2565 eq
= self
.assertEqual
2566 eq(base64mime
.base64_len('hello'),
2567 len(base64mime
.encode('hello', eol
='')))
2568 for size
in range(15):
2569 if size
== 0 : bsize
= 0
2570 elif size
<= 3 : bsize
= 4
2571 elif size
<= 6 : bsize
= 8
2572 elif size
<= 9 : bsize
= 12
2573 elif size
<= 12: bsize
= 16
2575 eq(base64mime
.base64_len('x'*size
), bsize
)
2577 def test_decode(self
):
2578 eq
= self
.assertEqual
2579 eq(base64mime
.decode(''), '')
2580 eq(base64mime
.decode('aGVsbG8='), 'hello')
2581 eq(base64mime
.decode('aGVsbG8=', 'X'), 'hello')
2582 eq(base64mime
.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
2584 def test_encode(self
):
2585 eq
= self
.assertEqual
2586 eq(base64mime
.encode(''), '')
2587 eq(base64mime
.encode('hello'), 'aGVsbG8=\n')
2588 # Test the binary flag
2589 eq(base64mime
.encode('hello\n'), 'aGVsbG8K\n')
2590 eq(base64mime
.encode('hello\n', 0), 'aGVsbG8NCg==\n')
2591 # Test the maxlinelen arg
2592 eq(base64mime
.encode('xxxx ' * 20, maxlinelen
=40), """\
2593 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2594 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2595 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2598 # Test the eol argument
2599 eq(base64mime
.encode('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2600 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2601 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2602 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2606 def test_header_encode(self
):
2607 eq
= self
.assertEqual
2608 he
= base64mime
.header_encode
2609 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
2610 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
2611 # Test the charset option
2612 eq(he('hello', charset
='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
2613 # Test the keep_eols flag
2614 eq(he('hello\nworld', keep_eols
=True),
2615 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
2616 # Test the maxlinelen argument
2617 eq(he('xxxx ' * 20, maxlinelen
=40), """\
2618 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
2619 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
2620 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
2621 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
2622 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
2623 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2624 # Test the eol argument
2625 eq(he('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2626 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
2627 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
2628 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
2629 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
2630 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
2631 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2635 class TestQuopri(unittest
.TestCase
):
2637 self
.hlit
= [chr(x
) for x
in range(ord('a'), ord('z')+1)] + \
2638 [chr(x
) for x
in range(ord('A'), ord('Z')+1)] + \
2639 [chr(x
) for x
in range(ord('0'), ord('9')+1)] + \
2640 ['!', '*', '+', '-', '/', ' ']
2641 self
.hnon
= [chr(x
) for x
in range(256) if chr(x
) not in self
.hlit
]
2642 assert len(self
.hlit
) + len(self
.hnon
) == 256
2643 self
.blit
= [chr(x
) for x
in range(ord(' '), ord('~')+1)] + ['\t']
2644 self
.blit
.remove('=')
2645 self
.bnon
= [chr(x
) for x
in range(256) if chr(x
) not in self
.blit
]
2646 assert len(self
.blit
) + len(self
.bnon
) == 256
2648 def test_header_quopri_check(self
):
2650 self
.assertFalse(quoprimime
.header_quopri_check(c
))
2652 self
.assertTrue(quoprimime
.header_quopri_check(c
))
2654 def test_body_quopri_check(self
):
2656 self
.assertFalse(quoprimime
.body_quopri_check(c
))
2658 self
.assertTrue(quoprimime
.body_quopri_check(c
))
2660 def test_header_quopri_len(self
):
2661 eq
= self
.assertEqual
2662 hql
= quoprimime
.header_quopri_len
2663 enc
= quoprimime
.header_encode
2664 for s
in ('hello', 'h@e@l@l@o@'):
2665 # Empty charset and no line-endings. 7 == RFC chrome
2666 eq(hql(s
), len(enc(s
, charset
='', eol
=''))-7)
2672 def test_body_quopri_len(self
):
2673 eq
= self
.assertEqual
2674 bql
= quoprimime
.body_quopri_len
2680 def test_quote_unquote_idempotent(self
):
2681 for x
in range(256):
2683 self
.assertEqual(quoprimime
.unquote(quoprimime
.quote(c
)), c
)
2685 def test_header_encode(self
):
2686 eq
= self
.assertEqual
2687 he
= quoprimime
.header_encode
2688 eq(he('hello'), '=?iso-8859-1?q?hello?=')
2689 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
2690 # Test the charset option
2691 eq(he('hello', charset
='iso-8859-2'), '=?iso-8859-2?q?hello?=')
2692 # Test the keep_eols flag
2693 eq(he('hello\nworld', keep_eols
=True), '=?iso-8859-1?q?hello=0Aworld?=')
2694 # Test a non-ASCII character
2695 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
2696 # Test the maxlinelen argument
2697 eq(he('xxxx ' * 20, maxlinelen
=40), """\
2698 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
2699 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
2700 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
2701 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
2702 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2703 # Test the eol argument
2704 eq(he('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2705 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
2706 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
2707 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
2708 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
2709 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2711 def test_decode(self
):
2712 eq
= self
.assertEqual
2713 eq(quoprimime
.decode(''), '')
2714 eq(quoprimime
.decode('hello'), 'hello')
2715 eq(quoprimime
.decode('hello', 'X'), 'hello')
2716 eq(quoprimime
.decode('hello\nworld', 'X'), 'helloXworld')
2718 def test_encode(self
):
2719 eq
= self
.assertEqual
2720 eq(quoprimime
.encode(''), '')
2721 eq(quoprimime
.encode('hello'), 'hello')
2722 # Test the binary flag
2723 eq(quoprimime
.encode('hello\r\nworld'), 'hello\nworld')
2724 eq(quoprimime
.encode('hello\r\nworld', 0), 'hello\nworld')
2725 # Test the maxlinelen arg
2726 eq(quoprimime
.encode('xxxx ' * 20, maxlinelen
=40), """\
2727 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
2728 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
2729 x xxxx xxxx xxxx xxxx=20""")
2730 # Test the eol argument
2731 eq(quoprimime
.encode('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2732 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
2733 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
2734 x xxxx xxxx xxxx xxxx=20""")
2735 eq(quoprimime
.encode("""\
2745 # Test the Charset class
2746 class TestCharset(unittest
.TestCase
):
2748 from email
import charset
as CharsetModule
2750 del CharsetModule
.CHARSETS
['fake']
2754 def test_idempotent(self
):
2755 eq
= self
.assertEqual
2756 # Make sure us-ascii = no Unicode conversion
2757 c
= Charset('us-ascii')
2759 sp
= c
.to_splittable(s
)
2760 eq(s
, c
.from_splittable(sp
))
2761 # test 8-bit idempotency with us-ascii
2762 s
= '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
2763 sp
= c
.to_splittable(s
)
2764 eq(s
, c
.from_splittable(sp
))
2766 def test_body_encode(self
):
2767 eq
= self
.assertEqual
2768 # Try a charset with QP body encoding
2769 c
= Charset('iso-8859-1')
2770 eq('hello w=F6rld', c
.body_encode('hello w\xf6rld'))
2771 # Try a charset with Base64 body encoding
2772 c
= Charset('utf-8')
2773 eq('aGVsbG8gd29ybGQ=\n', c
.body_encode('hello world'))
2774 # Try a charset with None body encoding
2775 c
= Charset('us-ascii')
2776 eq('hello world', c
.body_encode('hello world'))
2777 # Try the convert argument, where input codec != output codec
2778 c
= Charset('euc-jp')
2779 # With apologies to Tokio Kikuchi ;)
2781 eq('\x1b$B5FCO;~IW\x1b(B',
2782 c
.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
2783 eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
2784 c
.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
2786 # We probably don't have the Japanese codecs installed
2788 # Testing SF bug #625509, which we have to fake, since there are no
2789 # built-in encodings where the header encoding is QP but the body
2791 from email
import charset
as CharsetModule
2792 CharsetModule
.add_charset('fake', CharsetModule
.QP
, None)
2794 eq('hello w\xf6rld', c
.body_encode('hello w\xf6rld'))
2796 def test_unicode_charset_name(self
):
2797 charset
= Charset(u
'us-ascii')
2798 self
.assertEqual(str(charset
), 'us-ascii')
2799 self
.assertRaises(errors
.CharsetError
, Charset
, 'asc\xffii')
2803 # Test multilingual MIME headers.
2804 class TestHeader(TestEmailBase
):
2805 def test_simple(self
):
2806 eq
= self
.ndiffAssertEqual
2807 h
= Header('Hello World!')
2808 eq(h
.encode(), 'Hello World!')
2809 h
.append(' Goodbye World!')
2810 eq(h
.encode(), 'Hello World! Goodbye World!')
2812 def test_simple_surprise(self
):
2813 eq
= self
.ndiffAssertEqual
2814 h
= Header('Hello World!')
2815 eq(h
.encode(), 'Hello World!')
2816 h
.append('Goodbye World!')
2817 eq(h
.encode(), 'Hello World! Goodbye World!')
2819 def test_header_needs_no_decoding(self
):
2820 h
= 'no decoding needed'
2821 self
.assertEqual(decode_header(h
), [(h
, None)])
2823 def test_long(self
):
2824 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.",
2826 for l
in h
.encode(splitchars
=' ').split('\n '):
2827 self
.assertTrue(len(l
) <= 76)
2829 def test_multilingual(self
):
2830 eq
= self
.ndiffAssertEqual
2831 g
= Charset("iso-8859-1")
2832 cz
= Charset("iso-8859-2")
2833 utf8
= Charset("utf-8")
2834 g_head
= "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
2835 cz_head
= "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
2836 utf8_head
= u
"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
2837 h
= Header(g_head
, g
)
2838 h
.append(cz_head
, cz
)
2839 h
.append(utf8_head
, utf8
)
2842 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_ko?=
2843 =?iso-8859-1?q?mfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wan?=
2844 =?iso-8859-1?q?dgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6?=
2845 =?iso-8859-1?q?rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
2846 =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
2847 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
2848 =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
2849 =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
2850 =?utf-8?q?_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das_Oder_die_Fl?=
2851 =?utf-8?b?aXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBo+OBpuOBhOOBvuOBmQ==?=
2852 =?utf-8?b?44CC?=""")
2853 eq(decode_header(enc
),
2854 [(g_head
, "iso-8859-1"), (cz_head
, "iso-8859-2"),
2855 (utf8_head
, "utf-8")])
2857 eq(ustr
.encode('utf-8'),
2858 'Die Mieter treten hier ein werden mit einem Foerderband '
2859 'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
2860 'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
2861 'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
2862 'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
2863 '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
2864 '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
2865 '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
2866 '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
2867 '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
2868 '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
2869 '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
2870 '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
2871 'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
2872 'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
2873 '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
2874 # Test make_header()
2875 newh
= make_header(decode_header(enc
))
2878 def test_header_ctor_default_args(self
):
2879 eq
= self
.ndiffAssertEqual
2882 h
.append('foo', Charset('iso-8859-1'))
2883 eq(h
, '=?iso-8859-1?q?foo?=')
2885 def test_explicit_maxlinelen(self
):
2886 eq
= self
.ndiffAssertEqual
2887 hstr
= 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
2890 A very long line that must get split to something other than at the 76th
2891 character boundary to test the non-default behavior''')
2892 h
= Header(hstr
, header_name
='Subject')
2894 A very long line that must get split to something other than at the
2895 76th character boundary to test the non-default behavior''')
2896 h
= Header(hstr
, maxlinelen
=1024, header_name
='Subject')
2897 eq(h
.encode(), hstr
)
2899 def test_us_ascii_header(self
):
2900 eq
= self
.assertEqual
2902 x
= decode_header(s
)
2903 eq(x
, [('hello', None)])
2907 def test_string_charset(self
):
2908 eq
= self
.assertEqual
2910 h
.append('hello', 'iso-8859-1')
2911 eq(h
, '=?iso-8859-1?q?hello?=')
2913 ## def test_unicode_error(self):
2914 ## raises = self.assertRaises
2915 ## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
2916 ## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
2918 ## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
2919 ## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
2920 ## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
2922 def test_utf8_shortest(self
):
2923 eq
= self
.assertEqual
2924 h
= Header(u
'p\xf6stal', 'utf-8')
2925 eq(h
.encode(), '=?utf-8?q?p=C3=B6stal?=')
2926 h
= Header(u
'\u83ca\u5730\u6642\u592b', 'utf-8')
2927 eq(h
.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
2929 def test_bad_8bit_header(self
):
2930 raises
= self
.assertRaises
2931 eq
= self
.assertEqual
2932 x
= 'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
2933 raises(UnicodeError, Header
, x
)
2935 raises(UnicodeError, h
.append
, x
)
2936 eq(str(Header(x
, errors
='replace')), x
)
2937 h
.append(x
, errors
='replace')
2940 def test_encoded_adjacent_nonencoded(self
):
2941 eq
= self
.assertEqual
2943 h
.append('hello', 'iso-8859-1')
2946 eq(s
, '=?iso-8859-1?q?hello?= world')
2947 h
= make_header(decode_header(s
))
2950 def test_whitespace_eater(self
):
2951 eq
= self
.assertEqual
2952 s
= 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
2953 parts
= decode_header(s
)
2954 eq(parts
, [('Subject:', None), ('\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), ('zz.', None)])
2955 hdr
= make_header(parts
)
2957 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
2959 def test_broken_base64_header(self
):
2960 raises
= self
.assertRaises
2961 s
= 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3I ?='
2962 raises(errors
.HeaderParseError
, decode_header
, s
)
2966 # Test RFC 2231 header parameters (en/de)coding
2967 class TestRFC2231(TestEmailBase
):
2968 def test_get_param(self
):
2969 eq
= self
.assertEqual
2970 msg
= self
._msgobj
('msg_29.txt')
2971 eq(msg
.get_param('title'),
2972 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2973 eq(msg
.get_param('title', unquote
=False),
2974 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
2976 def test_set_param(self
):
2977 eq
= self
.assertEqual
2979 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
2981 eq(msg
.get_param('title'),
2982 ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
2983 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
2984 charset
='us-ascii', language
='en')
2985 eq(msg
.get_param('title'),
2986 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2987 msg
= self
._msgobj
('msg_01.txt')
2988 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
2989 charset
='us-ascii', language
='en')
2990 self
.ndiffAssertEqual(msg
.as_string(), """\
2991 Return-Path: <bbb@zzz.org>
2992 Delivered-To: bbb@zzz.org
2993 Received: by mail.zzz.org (Postfix, from userid 889)
2994 id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
2996 Content-Transfer-Encoding: 7bit
2997 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2998 From: bbb@ddd.com (John X. Doe)
3000 Subject: This is a test message
3001 Date: Fri, 4 May 2001 14:05:44 -0400
3002 Content-Type: text/plain; charset=us-ascii;
3003 title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3008 Do you like this message?
3013 def test_del_param(self
):
3014 eq
= self
.ndiffAssertEqual
3015 msg
= self
._msgobj
('msg_01.txt')
3016 msg
.set_param('foo', 'bar', charset
='us-ascii', language
='en')
3017 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
3018 charset
='us-ascii', language
='en')
3019 msg
.del_param('foo', header
='Content-Type')
3020 eq(msg
.as_string(), """\
3021 Return-Path: <bbb@zzz.org>
3022 Delivered-To: bbb@zzz.org
3023 Received: by mail.zzz.org (Postfix, from userid 889)
3024 id 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3026 Content-Transfer-Encoding: 7bit
3027 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
3028 From: bbb@ddd.com (John X. Doe)
3030 Subject: This is a test message
3031 Date: Fri, 4 May 2001 14:05:44 -0400
3032 Content-Type: text/plain; charset="us-ascii";
3033 title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3038 Do you like this message?
3043 def test_rfc2231_get_content_charset(self
):
3044 eq
= self
.assertEqual
3045 msg
= self
._msgobj
('msg_32.txt')
3046 eq(msg
.get_content_charset(), 'us-ascii')
3048 def test_rfc2231_no_language_or_charset(self
):
3050 Content-Transfer-Encoding: 8bit
3051 Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
3052 Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
3055 msg
= email
.message_from_string(m
)
3056 param
= msg
.get_param('NAME')
3057 self
.assertFalse(isinstance(param
, tuple))
3060 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
3062 def test_rfc2231_no_language_or_charset_in_filename(self
):
3064 Content-Disposition: inline;
3065 \tfilename*0*="''This%20is%20even%20more%20";
3066 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3067 \tfilename*2="is it not.pdf"
3070 msg
= email
.message_from_string(m
)
3071 self
.assertEqual(msg
.get_filename(),
3072 'This is even more ***fun*** is it not.pdf')
3074 def test_rfc2231_no_language_or_charset_in_filename_encoded(self
):
3076 Content-Disposition: inline;
3077 \tfilename*0*="''This%20is%20even%20more%20";
3078 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3079 \tfilename*2="is it not.pdf"
3082 msg
= email
.message_from_string(m
)
3083 self
.assertEqual(msg
.get_filename(),
3084 'This is even more ***fun*** is it not.pdf')
3086 def test_rfc2231_partly_encoded(self
):
3088 Content-Disposition: inline;
3089 \tfilename*0="''This%20is%20even%20more%20";
3090 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3091 \tfilename*2="is it not.pdf"
3094 msg
= email
.message_from_string(m
)
3097 'This%20is%20even%20more%20***fun*** is it not.pdf')
3099 def test_rfc2231_partly_nonencoded(self
):
3101 Content-Disposition: inline;
3102 \tfilename*0="This%20is%20even%20more%20";
3103 \tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
3104 \tfilename*2="is it not.pdf"
3107 msg
= email
.message_from_string(m
)
3110 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
3112 def test_rfc2231_no_language_or_charset_in_boundary(self
):
3114 Content-Type: multipart/alternative;
3115 \tboundary*0*="''This%20is%20even%20more%20";
3116 \tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
3117 \tboundary*2="is it not.pdf"
3120 msg
= email
.message_from_string(m
)
3121 self
.assertEqual(msg
.get_boundary(),
3122 'This is even more ***fun*** is it not.pdf')
3124 def test_rfc2231_no_language_or_charset_in_charset(self
):
3125 # This is a nonsensical charset value, but tests the code anyway
3127 Content-Type: text/plain;
3128 \tcharset*0*="This%20is%20even%20more%20";
3129 \tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
3130 \tcharset*2="is it not.pdf"
3133 msg
= email
.message_from_string(m
)
3134 self
.assertEqual(msg
.get_content_charset(),
3135 'this is even more ***fun*** is it not.pdf')
3137 def test_rfc2231_bad_encoding_in_filename(self
):
3139 Content-Disposition: inline;
3140 \tfilename*0*="bogus'xx'This%20is%20even%20more%20";
3141 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3142 \tfilename*2="is it not.pdf"
3145 msg
= email
.message_from_string(m
)
3146 self
.assertEqual(msg
.get_filename(),
3147 'This is even more ***fun*** is it not.pdf')
3149 def test_rfc2231_bad_encoding_in_charset(self
):
3151 Content-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
3154 msg
= email
.message_from_string(m
)
3155 # This should return None because non-ascii characters in the charset
3157 self
.assertEqual(msg
.get_content_charset(), None)
3159 def test_rfc2231_bad_character_in_charset(self
):
3161 Content-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
3164 msg
= email
.message_from_string(m
)
3165 # This should return None because non-ascii characters in the charset
3167 self
.assertEqual(msg
.get_content_charset(), None)
3169 def test_rfc2231_bad_character_in_filename(self
):
3171 Content-Disposition: inline;
3172 \tfilename*0*="ascii'xx'This%20is%20even%20more%20";
3173 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3174 \tfilename*2*="is it not.pdf%E2"
3177 msg
= email
.message_from_string(m
)
3178 self
.assertEqual(msg
.get_filename(),
3179 u
'This is even more ***fun*** is it not.pdf\ufffd')
3181 def test_rfc2231_unknown_encoding(self
):
3183 Content-Transfer-Encoding: 8bit
3184 Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
3187 msg
= email
.message_from_string(m
)
3188 self
.assertEqual(msg
.get_filename(), 'myfile.txt')
3190 def test_rfc2231_single_tick_in_filename_extended(self
):
3191 eq
= self
.assertEqual
3193 Content-Type: application/x-foo;
3194 \tname*0*=\"Frank's\"; name*1*=\" Document\"
3197 msg
= email
.message_from_string(m
)
3198 charset
, language
, s
= msg
.get_param('name')
3201 eq(s
, "Frank's Document")
3203 def test_rfc2231_single_tick_in_filename(self
):
3205 Content-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
3208 msg
= email
.message_from_string(m
)
3209 param
= msg
.get_param('name')
3210 self
.assertFalse(isinstance(param
, tuple))
3211 self
.assertEqual(param
, "Frank's Document")
3213 def test_rfc2231_tick_attack_extended(self
):
3214 eq
= self
.assertEqual
3216 Content-Type: application/x-foo;
3217 \tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
3220 msg
= email
.message_from_string(m
)
3221 charset
, language
, s
= msg
.get_param('name')
3222 eq(charset
, 'us-ascii')
3223 eq(language
, 'en-us')
3224 eq(s
, "Frank's Document")
3226 def test_rfc2231_tick_attack(self
):
3228 Content-Type: application/x-foo;
3229 \tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
3232 msg
= email
.message_from_string(m
)
3233 param
= msg
.get_param('name')
3234 self
.assertFalse(isinstance(param
, tuple))
3235 self
.assertEqual(param
, "us-ascii'en-us'Frank's Document")
3237 def test_rfc2231_no_extended_values(self
):
3238 eq
= self
.assertEqual
3240 Content-Type: application/x-foo; name=\"Frank's Document\"
3243 msg
= email
.message_from_string(m
)
3244 eq(msg
.get_param('name'), "Frank's Document")
3246 def test_rfc2231_encoded_then_unencoded_segments(self
):
3247 eq
= self
.assertEqual
3249 Content-Type: application/x-foo;
3250 \tname*0*=\"us-ascii'en-us'My\";
3251 \tname*1=\" Document\";
3252 \tname*2*=\" For You\"
3255 msg
= email
.message_from_string(m
)
3256 charset
, language
, s
= msg
.get_param('name')
3257 eq(charset
, 'us-ascii')
3258 eq(language
, 'en-us')
3259 eq(s
, 'My Document For You')
3261 def test_rfc2231_unencoded_then_encoded_segments(self
):
3262 eq
= self
.assertEqual
3264 Content-Type: application/x-foo;
3265 \tname*0=\"us-ascii'en-us'My\";
3266 \tname*1*=\" Document\";
3267 \tname*2*=\" For You\"
3270 msg
= email
.message_from_string(m
)
3271 charset
, language
, s
= msg
.get_param('name')
3272 eq(charset
, 'us-ascii')
3273 eq(language
, 'en-us')
3274 eq(s
, 'My Document For You')
3279 mod
= sys
.modules
[__name__
]
3280 return [getattr(mod
, name
) for name
in dir(mod
) if name
.startswith('Test')]
3284 suite
= unittest
.TestSuite()
3285 for testclass
in _testclasses():
3286 suite
.addTest(unittest
.makeSuite(testclass
))
3291 for testclass
in _testclasses():
3292 run_unittest(testclass
)
3296 if __name__
== '__main__':
3297 unittest
.main(defaultTest
='suite')