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
.MIMEAudio
import MIMEAudio
22 from email
.MIMEText
import MIMEText
23 from email
.MIMEImage
import MIMEImage
24 from email
.MIMEBase
import MIMEBase
25 from email
.MIMEMessage
import MIMEMessage
26 from email
.MIMEMultipart
import MIMEMultipart
27 from email
import Utils
28 from email
import Errors
29 from email
import Encoders
30 from email
import Iterators
31 from email
import base64MIME
32 from email
import quopriMIME
34 from test
.test_support
import findfile
, run_unittest
35 from email
.test
import __file__
as landmark
44 def openfile(filename
, mode
='r'):
45 path
= os
.path
.join(os
.path
.dirname(landmark
), 'data', filename
)
46 return open(path
, mode
)
51 class TestEmailBase(unittest
.TestCase
):
52 def ndiffAssertEqual(self
, first
, second
):
53 """Like failUnlessEqual except use ndiff for readable output."""
57 diff
= difflib
.ndiff(sfirst
.splitlines(), ssecond
.splitlines())
59 print >> fp
, NL
, NL
.join(diff
)
60 raise self
.failureException
, fp
.getvalue()
62 def _msgobj(self
, filename
):
63 fp
= openfile(findfile(filename
))
65 msg
= email
.message_from_file(fp
)
72 # Test various aspects of the Message class's API
73 class TestMessageAPI(TestEmailBase
):
74 def test_get_all(self
):
76 msg
= self
._msgobj
('msg_20.txt')
77 eq(msg
.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
78 eq(msg
.get_all('xx', 'n/a'), 'n/a')
80 def test_getset_charset(self
):
83 eq(msg
.get_charset(), None)
84 charset
= Charset('iso-8859-1')
85 msg
.set_charset(charset
)
86 eq(msg
['mime-version'], '1.0')
87 eq(msg
.get_content_type(), 'text/plain')
88 eq(msg
['content-type'], 'text/plain; charset="iso-8859-1"')
89 eq(msg
.get_param('charset'), 'iso-8859-1')
90 eq(msg
['content-transfer-encoding'], 'quoted-printable')
91 eq(msg
.get_charset().input_charset
, 'iso-8859-1')
94 eq(msg
.get_charset(), None)
95 eq(msg
['content-type'], 'text/plain')
96 # Try adding a charset when there's already MIME headers present
98 msg
['MIME-Version'] = '2.0'
99 msg
['Content-Type'] = 'text/x-weird'
100 msg
['Content-Transfer-Encoding'] = 'quinted-puntable'
101 msg
.set_charset(charset
)
102 eq(msg
['mime-version'], '2.0')
103 eq(msg
['content-type'], 'text/x-weird; charset="iso-8859-1"')
104 eq(msg
['content-transfer-encoding'], 'quinted-puntable')
106 def test_set_charset_from_string(self
):
107 eq
= self
.assertEqual
109 msg
.set_charset('us-ascii')
110 eq(msg
.get_charset().input_charset
, 'us-ascii')
111 eq(msg
['content-type'], 'text/plain; charset="us-ascii"')
113 def test_set_payload_with_charset(self
):
115 charset
= Charset('iso-8859-1')
116 msg
.set_payload('This is a string payload', charset
)
117 self
.assertEqual(msg
.get_charset().input_charset
, 'iso-8859-1')
119 def test_get_charsets(self
):
120 eq
= self
.assertEqual
122 msg
= self
._msgobj
('msg_08.txt')
123 charsets
= msg
.get_charsets()
124 eq(charsets
, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
126 msg
= self
._msgobj
('msg_09.txt')
127 charsets
= msg
.get_charsets('dingbat')
128 eq(charsets
, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
131 msg
= self
._msgobj
('msg_12.txt')
132 charsets
= msg
.get_charsets()
133 eq(charsets
, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
134 'iso-8859-3', 'us-ascii', 'koi8-r'])
136 def test_get_filename(self
):
137 eq
= self
.assertEqual
139 msg
= self
._msgobj
('msg_04.txt')
140 filenames
= [p
.get_filename() for p
in msg
.get_payload()]
141 eq(filenames
, ['msg.txt', 'msg.txt'])
143 msg
= self
._msgobj
('msg_07.txt')
144 subpart
= msg
.get_payload(1)
145 eq(subpart
.get_filename(), 'dingusfish.gif')
147 def test_get_filename_with_name_parameter(self
):
148 eq
= self
.assertEqual
150 msg
= self
._msgobj
('msg_44.txt')
151 filenames
= [p
.get_filename() for p
in msg
.get_payload()]
152 eq(filenames
, ['msg.txt', 'msg.txt'])
154 def test_get_boundary(self
):
155 eq
= self
.assertEqual
156 msg
= self
._msgobj
('msg_07.txt')
158 eq(msg
.get_boundary(), 'BOUNDARY')
160 def test_set_boundary(self
):
161 eq
= self
.assertEqual
162 # This one has no existing boundary parameter, but the Content-Type:
163 # header appears fifth.
164 msg
= self
._msgobj
('msg_01.txt')
165 msg
.set_boundary('BOUNDARY')
166 header
, value
= msg
.items()[4]
167 eq(header
.lower(), 'content-type')
168 eq(value
, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
169 # This one has a Content-Type: header, with a boundary, stuck in the
170 # middle of its headers. Make sure the order is preserved; it should
172 msg
= self
._msgobj
('msg_04.txt')
173 msg
.set_boundary('BOUNDARY')
174 header
, value
= msg
.items()[4]
175 eq(header
.lower(), 'content-type')
176 eq(value
, 'multipart/mixed; boundary="BOUNDARY"')
177 # And this one has no Content-Type: header at all.
178 msg
= self
._msgobj
('msg_03.txt')
179 self
.assertRaises(Errors
.HeaderParseError
,
180 msg
.set_boundary
, 'BOUNDARY')
182 def test_get_decoded_payload(self
):
183 eq
= self
.assertEqual
184 msg
= self
._msgobj
('msg_10.txt')
185 # The outer message is a multipart
186 eq(msg
.get_payload(decode
=True), None)
187 # Subpart 1 is 7bit encoded
188 eq(msg
.get_payload(0).get_payload(decode
=True),
189 'This is a 7bit encoded message.\n')
190 # Subpart 2 is quopri
191 eq(msg
.get_payload(1).get_payload(decode
=True),
192 '\xa1This is a Quoted Printable encoded message!\n')
193 # Subpart 3 is base64
194 eq(msg
.get_payload(2).get_payload(decode
=True),
195 'This is a Base64 encoded message.')
196 # Subpart 4 has no Content-Transfer-Encoding: header.
197 eq(msg
.get_payload(3).get_payload(decode
=True),
198 'This has no Content-Transfer-Encoding: header.\n')
200 def test_get_decoded_uu_payload(self
):
201 eq
= self
.assertEqual
203 msg
.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
204 for cte
in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
205 msg
['content-transfer-encoding'] = cte
206 eq(msg
.get_payload(decode
=True), 'hello world')
207 # Now try some bogus data
208 msg
.set_payload('foo')
209 eq(msg
.get_payload(decode
=True), 'foo')
211 def test_decode_bogus_uu_payload_quietly(self
):
213 msg
.set_payload('begin 664 foo.txt\n%<W1F=0000H \n \nend\n')
214 msg
['Content-Transfer-Encoding'] = 'x-uuencode'
215 old_stderr
= sys
.stderr
217 sys
.stderr
= sfp
= StringIO()
218 # We don't care about the payload
219 msg
.get_payload(decode
=True)
221 sys
.stderr
= old_stderr
222 self
.assertEqual(sfp
.getvalue(), '')
224 def test_decoded_generator(self
):
225 eq
= self
.assertEqual
226 msg
= self
._msgobj
('msg_07.txt')
227 fp
= openfile('msg_17.txt')
233 g
= DecodedGenerator(s
)
235 eq(s
.getvalue(), text
)
237 def test__contains__(self
):
241 # Check for case insensitivity
242 self
.failUnless('from' in msg
)
243 self
.failUnless('From' in msg
)
244 self
.failUnless('FROM' in msg
)
245 self
.failUnless('to' in msg
)
246 self
.failUnless('To' in msg
)
247 self
.failUnless('TO' in msg
)
249 def test_as_string(self
):
250 eq
= self
.assertEqual
251 msg
= self
._msgobj
('msg_01.txt')
252 fp
= openfile('msg_01.txt')
257 eq(text
, msg
.as_string())
259 lines
= fullrepr
.split('\n')
260 self
.failUnless(lines
[0].startswith('From '))
261 eq(text
, NL
.join(lines
[1:]))
263 def test_bad_param(self
):
264 msg
= email
.message_from_string("Content-Type: blarg; baz; boo\n")
265 self
.assertEqual(msg
.get_param('baz'), '')
267 def test_missing_filename(self
):
268 msg
= email
.message_from_string("From: foo\n")
269 self
.assertEqual(msg
.get_filename(), None)
271 def test_bogus_filename(self
):
272 msg
= email
.message_from_string(
273 "Content-Disposition: blarg; filename\n")
274 self
.assertEqual(msg
.get_filename(), '')
276 def test_missing_boundary(self
):
277 msg
= email
.message_from_string("From: foo\n")
278 self
.assertEqual(msg
.get_boundary(), None)
280 def test_get_params(self
):
281 eq
= self
.assertEqual
282 msg
= email
.message_from_string(
283 'X-Header: foo=one; bar=two; baz=three\n')
284 eq(msg
.get_params(header
='x-header'),
285 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
286 msg
= email
.message_from_string(
287 'X-Header: foo; bar=one; baz=two\n')
288 eq(msg
.get_params(header
='x-header'),
289 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
290 eq(msg
.get_params(), None)
291 msg
= email
.message_from_string(
292 'X-Header: foo; bar="one"; baz=two\n')
293 eq(msg
.get_params(header
='x-header'),
294 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
296 def test_get_param_liberal(self
):
298 msg
['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
299 self
.assertEqual(msg
.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
301 def test_get_param(self
):
302 eq
= self
.assertEqual
303 msg
= email
.message_from_string(
304 "X-Header: foo=one; bar=two; baz=three\n")
305 eq(msg
.get_param('bar', header
='x-header'), 'two')
306 eq(msg
.get_param('quuz', header
='x-header'), None)
307 eq(msg
.get_param('quuz'), None)
308 msg
= email
.message_from_string(
309 'X-Header: foo; bar="one"; baz=two\n')
310 eq(msg
.get_param('foo', header
='x-header'), '')
311 eq(msg
.get_param('bar', header
='x-header'), 'one')
312 eq(msg
.get_param('baz', header
='x-header'), 'two')
313 # XXX: We are not RFC-2045 compliant! We cannot parse:
314 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
315 # msg.get_param("weird")
318 def test_get_param_funky_continuation_lines(self
):
319 msg
= self
._msgobj
('msg_22.txt')
320 self
.assertEqual(msg
.get_payload(1).get_param('name'), 'wibble.JPG')
322 def test_get_param_with_semis_in_quotes(self
):
323 msg
= email
.message_from_string(
324 'Content-Type: image/pjpeg; name="Jim&&Jill"\n')
325 self
.assertEqual(msg
.get_param('name'), 'Jim&&Jill')
326 self
.assertEqual(msg
.get_param('name', unquote
=False),
327 '"Jim&&Jill"')
329 def test_has_key(self
):
330 msg
= email
.message_from_string('Header: exists')
331 self
.failUnless(msg
.has_key('header'))
332 self
.failUnless(msg
.has_key('Header'))
333 self
.failUnless(msg
.has_key('HEADER'))
334 self
.failIf(msg
.has_key('headeri'))
336 def test_set_param(self
):
337 eq
= self
.assertEqual
339 msg
.set_param('charset', 'iso-2022-jp')
340 eq(msg
.get_param('charset'), 'iso-2022-jp')
341 msg
.set_param('importance', 'high value')
342 eq(msg
.get_param('importance'), 'high value')
343 eq(msg
.get_param('importance', unquote
=False), '"high value"')
344 eq(msg
.get_params(), [('text/plain', ''),
345 ('charset', 'iso-2022-jp'),
346 ('importance', 'high value')])
347 eq(msg
.get_params(unquote
=False), [('text/plain', ''),
348 ('charset', '"iso-2022-jp"'),
349 ('importance', '"high value"')])
350 msg
.set_param('charset', 'iso-9999-xx', header
='X-Jimmy')
351 eq(msg
.get_param('charset', header
='X-Jimmy'), 'iso-9999-xx')
353 def test_del_param(self
):
354 eq
= self
.assertEqual
355 msg
= self
._msgobj
('msg_05.txt')
357 [('multipart/report', ''), ('report-type', 'delivery-status'),
358 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
359 old_val
= msg
.get_param("report-type")
360 msg
.del_param("report-type")
362 [('multipart/report', ''),
363 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
364 msg
.set_param("report-type", old_val
)
366 [('multipart/report', ''),
367 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
368 ('report-type', old_val
)])
370 def test_del_param_on_other_header(self
):
372 msg
.add_header('Content-Disposition', 'attachment', filename
='bud.gif')
373 msg
.del_param('filename', 'content-disposition')
374 self
.assertEqual(msg
['content-disposition'], 'attachment')
376 def test_set_type(self
):
377 eq
= self
.assertEqual
379 self
.assertRaises(ValueError, msg
.set_type
, 'text')
380 msg
.set_type('text/plain')
381 eq(msg
['content-type'], 'text/plain')
382 msg
.set_param('charset', 'us-ascii')
383 eq(msg
['content-type'], 'text/plain; charset="us-ascii"')
384 msg
.set_type('text/html')
385 eq(msg
['content-type'], 'text/html; charset="us-ascii"')
387 def test_set_type_on_other_header(self
):
389 msg
['X-Content-Type'] = 'text/plain'
390 msg
.set_type('application/octet-stream', 'X-Content-Type')
391 self
.assertEqual(msg
['x-content-type'], 'application/octet-stream')
393 def test_get_content_type_missing(self
):
395 self
.assertEqual(msg
.get_content_type(), 'text/plain')
397 def test_get_content_type_missing_with_default_type(self
):
399 msg
.set_default_type('message/rfc822')
400 self
.assertEqual(msg
.get_content_type(), 'message/rfc822')
402 def test_get_content_type_from_message_implicit(self
):
403 msg
= self
._msgobj
('msg_30.txt')
404 self
.assertEqual(msg
.get_payload(0).get_content_type(),
407 def test_get_content_type_from_message_explicit(self
):
408 msg
= self
._msgobj
('msg_28.txt')
409 self
.assertEqual(msg
.get_payload(0).get_content_type(),
412 def test_get_content_type_from_message_text_plain_implicit(self
):
413 msg
= self
._msgobj
('msg_03.txt')
414 self
.assertEqual(msg
.get_content_type(), 'text/plain')
416 def test_get_content_type_from_message_text_plain_explicit(self
):
417 msg
= self
._msgobj
('msg_01.txt')
418 self
.assertEqual(msg
.get_content_type(), 'text/plain')
420 def test_get_content_maintype_missing(self
):
422 self
.assertEqual(msg
.get_content_maintype(), 'text')
424 def test_get_content_maintype_missing_with_default_type(self
):
426 msg
.set_default_type('message/rfc822')
427 self
.assertEqual(msg
.get_content_maintype(), 'message')
429 def test_get_content_maintype_from_message_implicit(self
):
430 msg
= self
._msgobj
('msg_30.txt')
431 self
.assertEqual(msg
.get_payload(0).get_content_maintype(), 'message')
433 def test_get_content_maintype_from_message_explicit(self
):
434 msg
= self
._msgobj
('msg_28.txt')
435 self
.assertEqual(msg
.get_payload(0).get_content_maintype(), 'message')
437 def test_get_content_maintype_from_message_text_plain_implicit(self
):
438 msg
= self
._msgobj
('msg_03.txt')
439 self
.assertEqual(msg
.get_content_maintype(), 'text')
441 def test_get_content_maintype_from_message_text_plain_explicit(self
):
442 msg
= self
._msgobj
('msg_01.txt')
443 self
.assertEqual(msg
.get_content_maintype(), 'text')
445 def test_get_content_subtype_missing(self
):
447 self
.assertEqual(msg
.get_content_subtype(), 'plain')
449 def test_get_content_subtype_missing_with_default_type(self
):
451 msg
.set_default_type('message/rfc822')
452 self
.assertEqual(msg
.get_content_subtype(), 'rfc822')
454 def test_get_content_subtype_from_message_implicit(self
):
455 msg
= self
._msgobj
('msg_30.txt')
456 self
.assertEqual(msg
.get_payload(0).get_content_subtype(), 'rfc822')
458 def test_get_content_subtype_from_message_explicit(self
):
459 msg
= self
._msgobj
('msg_28.txt')
460 self
.assertEqual(msg
.get_payload(0).get_content_subtype(), 'rfc822')
462 def test_get_content_subtype_from_message_text_plain_implicit(self
):
463 msg
= self
._msgobj
('msg_03.txt')
464 self
.assertEqual(msg
.get_content_subtype(), 'plain')
466 def test_get_content_subtype_from_message_text_plain_explicit(self
):
467 msg
= self
._msgobj
('msg_01.txt')
468 self
.assertEqual(msg
.get_content_subtype(), 'plain')
470 def test_get_content_maintype_error(self
):
472 msg
['Content-Type'] = 'no-slash-in-this-string'
473 self
.assertEqual(msg
.get_content_maintype(), 'text')
475 def test_get_content_subtype_error(self
):
477 msg
['Content-Type'] = 'no-slash-in-this-string'
478 self
.assertEqual(msg
.get_content_subtype(), 'plain')
480 def test_replace_header(self
):
481 eq
= self
.assertEqual
483 msg
.add_header('First', 'One')
484 msg
.add_header('Second', 'Two')
485 msg
.add_header('Third', 'Three')
486 eq(msg
.keys(), ['First', 'Second', 'Third'])
487 eq(msg
.values(), ['One', 'Two', 'Three'])
488 msg
.replace_header('Second', 'Twenty')
489 eq(msg
.keys(), ['First', 'Second', 'Third'])
490 eq(msg
.values(), ['One', 'Twenty', 'Three'])
491 msg
.add_header('First', 'Eleven')
492 msg
.replace_header('First', 'One Hundred')
493 eq(msg
.keys(), ['First', 'Second', 'Third', 'First'])
494 eq(msg
.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
495 self
.assertRaises(KeyError, msg
.replace_header
, 'Fourth', 'Missing')
497 def test_broken_base64_payload(self
):
498 x
= 'AwDp0P7//y6LwKEAcPa/6Q=9'
500 msg
['content-type'] = 'audio/x-midi'
501 msg
['content-transfer-encoding'] = 'base64'
503 self
.assertEqual(msg
.get_payload(decode
=True), x
)
505 def test_get_content_charset(self
):
507 msg
.set_charset('us-ascii')
508 self
.assertEqual('us-ascii', msg
.get_content_charset())
509 msg
.set_charset(u
'us-ascii')
510 self
.assertEqual('us-ascii', msg
.get_content_charset())
514 # Test the email.Encoders module
515 class TestEncoders(unittest
.TestCase
):
516 def test_encode_empty_payload(self
):
517 eq
= self
.assertEqual
519 msg
.set_charset('us-ascii')
520 eq(msg
['content-transfer-encoding'], '7bit')
522 def test_default_cte(self
):
523 eq
= self
.assertEqual
524 msg
= MIMEText('hello world')
525 eq(msg
['content-transfer-encoding'], '7bit')
527 def test_default_cte(self
):
528 eq
= self
.assertEqual
529 # With no explicit _charset its us-ascii, and all are 7-bit
530 msg
= MIMEText('hello world')
531 eq(msg
['content-transfer-encoding'], '7bit')
532 # Similar, but with 8-bit data
533 msg
= MIMEText('hello \xf8 world')
534 eq(msg
['content-transfer-encoding'], '8bit')
535 # And now with a different charset
536 msg
= MIMEText('hello \xf8 world', _charset
='iso-8859-1')
537 eq(msg
['content-transfer-encoding'], 'quoted-printable')
541 # Test long header wrapping
542 class TestLongHeaders(TestEmailBase
):
543 def test_split_long_continuation(self
):
544 eq
= self
.ndiffAssertEqual
545 msg
= email
.message_from_string("""\
546 Subject: bug demonstration
547 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
555 eq(sfp
.getvalue(), """\
556 Subject: bug demonstration
557 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
563 def test_another_long_almost_unsplittable_header(self
):
564 eq
= self
.ndiffAssertEqual
567 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
569 h
= Header(hstr
, continuation_ws
='\t')
572 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
577 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
580 def test_long_nonstring(self
):
581 eq
= self
.ndiffAssertEqual
582 g
= Charset("iso-8859-1")
583 cz
= Charset("iso-8859-2")
584 utf8
= Charset("utf-8")
585 g_head
= "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
586 cz_head
= "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
587 utf8_head
= u
"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
588 h
= Header(g_head
, g
, header_name
='Subject')
589 h
.append(cz_head
, cz
)
590 h
.append(utf8_head
, utf8
)
596 eq(sfp
.getvalue(), """\
597 Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
598 =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
599 =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
600 =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
601 =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
602 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
603 =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
604 =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
605 =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
606 =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
607 =?utf-8?b?44Gm44GE44G+44GZ44CC?=
611 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
612 =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
613 =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
614 =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
615 =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
616 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
617 =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
618 =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
619 =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
620 =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
621 =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
623 def test_long_header_encode(self
):
624 eq
= self
.ndiffAssertEqual
625 h
= Header('wasnipoop; giraffes="very-long-necked-animals"; '
626 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
627 header_name
='X-Foobar-Spoink-Defrobnit')
629 wasnipoop; giraffes="very-long-necked-animals";
630 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
632 def test_long_header_encode_with_tab_continuation(self
):
633 eq
= self
.ndiffAssertEqual
634 h
= Header('wasnipoop; giraffes="very-long-necked-animals"; '
635 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
636 header_name
='X-Foobar-Spoink-Defrobnit',
637 continuation_ws
='\t')
639 wasnipoop; giraffes="very-long-necked-animals";
640 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
642 def test_header_splitter(self
):
643 eq
= self
.ndiffAssertEqual
645 # It'd be great if we could use add_header() here, but that doesn't
646 # guarantee an order of the parameters.
647 msg
['X-Foobar-Spoink-Defrobnit'] = (
648 'wasnipoop; giraffes="very-long-necked-animals"; '
649 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
653 eq(sfp
.getvalue(), '''\
654 Content-Type: text/plain; charset="us-ascii"
656 Content-Transfer-Encoding: 7bit
657 X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
658 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
662 def test_no_semis_header_splitter(self
):
663 eq
= self
.ndiffAssertEqual
665 msg
['From'] = 'test@dom.ain'
666 msg
['References'] = SPACE
.join(['<%d@dom.ain>' % i
for i
in range(10)])
667 msg
.set_payload('Test')
671 eq(sfp
.getvalue(), """\
673 References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
674 \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
678 def test_no_split_long_header(self
):
679 eq
= self
.ndiffAssertEqual
680 hstr
= 'References: ' + 'x' * 80
681 h
= Header(hstr
, continuation_ws
='\t')
683 References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
685 def test_splitting_multiple_long_lines(self
):
686 eq
= self
.ndiffAssertEqual
688 from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
689 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
690 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
692 h
= Header(hstr
, continuation_ws
='\t')
694 from babylon.socal-raves.org (localhost [127.0.0.1]);
695 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
696 \tfor <mailman-admin@babylon.socal-raves.org>;
697 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
698 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
699 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
700 \tfor <mailman-admin@babylon.socal-raves.org>;
701 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
702 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
703 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
704 \tfor <mailman-admin@babylon.socal-raves.org>;
705 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
707 def test_splitting_first_line_only_is_long(self
):
708 eq
= self
.ndiffAssertEqual
710 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
711 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
712 \tid 17k4h5-00034i-00
713 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
714 h
= Header(hstr
, maxlinelen
=78, header_name
='Received',
715 continuation_ws
='\t')
717 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
718 \thelo=cthulhu.gerg.ca)
719 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
720 \tid 17k4h5-00034i-00
721 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
723 def test_long_8bit_header(self
):
724 eq
= self
.ndiffAssertEqual
726 h
= Header('Britische Regierung gibt', 'iso-8859-1',
727 header_name
='Subject')
728 h
.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
730 eq(msg
.as_string(), """\
731 Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
732 =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
736 def test_long_8bit_header_no_charset(self
):
737 eq
= self
.ndiffAssertEqual
739 msg
['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
740 eq(msg
.as_string(), """\
741 Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
745 def test_long_to_header(self
):
746 eq
= self
.ndiffAssertEqual
747 to
= '"Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,"Someone Test #B" <someone@umich.edu>, "Someone Test #C" <someone@eecs.umich.edu>, "Someone Test #D" <someone@eecs.umich.edu>'
750 eq(msg
.as_string(0), '''\
751 To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
752 \t"Someone Test #B" <someone@umich.edu>,
753 \t"Someone Test #C" <someone@eecs.umich.edu>,
754 \t"Someone Test #D" <someone@eecs.umich.edu>
758 def test_long_line_after_append(self
):
759 eq
= self
.ndiffAssertEqual
760 s
= 'This is an example of string which has almost the limit of header length.'
762 h
.append('Add another line.')
764 This is an example of string which has almost the limit of header length.
765 Add another line.""")
767 def test_shorter_line_with_append(self
):
768 eq
= self
.ndiffAssertEqual
769 s
= 'This is a shorter line.'
771 h
.append('Add another sentence. (Surprise?)')
773 'This is a shorter line. Add another sentence. (Surprise?)')
775 def test_long_field_name(self
):
776 eq
= self
.ndiffAssertEqual
777 fn
= 'X-Very-Very-Very-Long-Header-Name'
778 gs
= "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
779 h
= Header(gs
, 'iso-8859-1', header_name
=fn
)
780 # BAW: this seems broken because the first line is too long
782 =?iso-8859-1?q?Die_Mieter_treten_hier_?=
783 =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
784 =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
785 =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
787 def test_long_received_header(self
):
788 h
= 'from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; Wed, 05 Mar 2003 18:10:18 -0700'
790 msg
['Received-1'] = Header(h
, continuation_ws
='\t')
791 msg
['Received-2'] = h
792 self
.assertEqual(msg
.as_string(), """\
793 Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
794 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
795 \tWed, 05 Mar 2003 18:10:18 -0700
796 Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
797 \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
798 \tWed, 05 Mar 2003 18:10:18 -0700
802 def test_string_headerinst_eq(self
):
803 h
= '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
805 msg
['Received-1'] = Header(h
, header_name
='Received-1',
806 continuation_ws
='\t')
807 msg
['Received-2'] = h
808 self
.assertEqual(msg
.as_string(), """\
809 Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
810 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
811 Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
812 \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
816 def test_long_unbreakable_lines_with_continuation(self
):
817 eq
= self
.ndiffAssertEqual
820 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
821 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
823 msg
['Face-2'] = Header(t
, header_name
='Face-2')
824 eq(msg
.as_string(), """\
825 Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
826 \tlocQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
827 Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
828 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
832 def test_another_long_multiline_header(self
):
833 eq
= self
.ndiffAssertEqual
835 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
836 \tWed, 16 Oct 2002 07:41:11 -0700'''
837 msg
= email
.message_from_string(m
)
838 eq(msg
.as_string(), '''\
839 Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
840 \tMicrosoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
844 def test_long_lines_with_different_header(self
):
845 eq
= self
.ndiffAssertEqual
847 List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
848 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
851 msg
['List'] = Header(h
, header_name
='List')
852 eq(msg
.as_string(), """\
853 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
854 \t<mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
855 List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
856 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
862 # Test mangling of "From " lines in the body of a message
863 class TestFromMangling(unittest
.TestCase
):
866 self
.msg
['From'] = 'aaa@bbb.org'
867 self
.msg
.set_payload("""\
868 From the desk of A.A.A.:
872 def test_mangled_from(self
):
874 g
= Generator(s
, mangle_from_
=True)
876 self
.assertEqual(s
.getvalue(), """\
879 >From the desk of A.A.A.:
883 def test_dont_mangle_from(self
):
885 g
= Generator(s
, mangle_from_
=False)
887 self
.assertEqual(s
.getvalue(), """\
890 From the desk of A.A.A.:
896 # Test the basic MIMEAudio class
897 class TestMIMEAudio(unittest
.TestCase
):
899 # Make sure we pick up the audiotest.au that lives in email/test/data.
900 # In Python, there's an audiotest.au living in Lib/test but that isn't
901 # included in some binary distros that don't include the test
902 # package. The trailing empty string on the .join() is significant
903 # since findfile() will do a dirname().
904 datadir
= os
.path
.join(os
.path
.dirname(landmark
), 'data', '')
905 fp
= open(findfile('audiotest.au', datadir
), 'rb')
907 self
._audiodata
= fp
.read()
910 self
._au
= MIMEAudio(self
._audiodata
)
912 def test_guess_minor_type(self
):
913 self
.assertEqual(self
._au
.get_content_type(), 'audio/basic')
915 def test_encoding(self
):
916 payload
= self
._au
.get_payload()
917 self
.assertEqual(base64
.decodestring(payload
), self
._audiodata
)
919 def test_checkSetMinor(self
):
920 au
= MIMEAudio(self
._audiodata
, 'fish')
921 self
.assertEqual(au
.get_content_type(), 'audio/fish')
923 def test_add_header(self
):
924 eq
= self
.assertEqual
925 unless
= self
.failUnless
926 self
._au
.add_header('Content-Disposition', 'attachment',
927 filename
='audiotest.au')
928 eq(self
._au
['content-disposition'],
929 'attachment; filename="audiotest.au"')
930 eq(self
._au
.get_params(header
='content-disposition'),
931 [('attachment', ''), ('filename', 'audiotest.au')])
932 eq(self
._au
.get_param('filename', header
='content-disposition'),
935 eq(self
._au
.get_param('attachment', header
='content-disposition'), '')
936 unless(self
._au
.get_param('foo', failobj
=missing
,
937 header
='content-disposition') is missing
)
938 # Try some missing stuff
939 unless(self
._au
.get_param('foobar', missing
) is missing
)
940 unless(self
._au
.get_param('attachment', missing
,
941 header
='foobar') is missing
)
945 # Test the basic MIMEImage class
946 class TestMIMEImage(unittest
.TestCase
):
948 fp
= openfile('PyBanner048.gif')
950 self
._imgdata
= fp
.read()
953 self
._im
= MIMEImage(self
._imgdata
)
955 def test_guess_minor_type(self
):
956 self
.assertEqual(self
._im
.get_content_type(), 'image/gif')
958 def test_encoding(self
):
959 payload
= self
._im
.get_payload()
960 self
.assertEqual(base64
.decodestring(payload
), self
._imgdata
)
962 def test_checkSetMinor(self
):
963 im
= MIMEImage(self
._imgdata
, 'fish')
964 self
.assertEqual(im
.get_content_type(), 'image/fish')
966 def test_add_header(self
):
967 eq
= self
.assertEqual
968 unless
= self
.failUnless
969 self
._im
.add_header('Content-Disposition', 'attachment',
970 filename
='dingusfish.gif')
971 eq(self
._im
['content-disposition'],
972 'attachment; filename="dingusfish.gif"')
973 eq(self
._im
.get_params(header
='content-disposition'),
974 [('attachment', ''), ('filename', 'dingusfish.gif')])
975 eq(self
._im
.get_param('filename', header
='content-disposition'),
978 eq(self
._im
.get_param('attachment', header
='content-disposition'), '')
979 unless(self
._im
.get_param('foo', failobj
=missing
,
980 header
='content-disposition') is missing
)
981 # Try some missing stuff
982 unless(self
._im
.get_param('foobar', missing
) is missing
)
983 unless(self
._im
.get_param('attachment', missing
,
984 header
='foobar') is missing
)
988 # Test the basic MIMEText class
989 class TestMIMEText(unittest
.TestCase
):
991 self
._msg
= MIMEText('hello there')
993 def test_types(self
):
994 eq
= self
.assertEqual
995 unless
= self
.failUnless
996 eq(self
._msg
.get_content_type(), 'text/plain')
997 eq(self
._msg
.get_param('charset'), 'us-ascii')
999 unless(self
._msg
.get_param('foobar', missing
) is missing
)
1000 unless(self
._msg
.get_param('charset', missing
, header
='foobar')
1003 def test_payload(self
):
1004 self
.assertEqual(self
._msg
.get_payload(), 'hello there')
1005 self
.failUnless(not self
._msg
.is_multipart())
1007 def test_charset(self
):
1008 eq
= self
.assertEqual
1009 msg
= MIMEText('hello there', _charset
='us-ascii')
1010 eq(msg
.get_charset().input_charset
, 'us-ascii')
1011 eq(msg
['content-type'], 'text/plain; charset="us-ascii"')
1015 # Test complicated multipart/* messages
1016 class TestMultipart(TestEmailBase
):
1018 fp
= openfile('PyBanner048.gif')
1024 container
= MIMEBase('multipart', 'mixed', boundary
='BOUNDARY')
1025 image
= MIMEImage(data
, name
='dingusfish.gif')
1026 image
.add_header('content-disposition', 'attachment',
1027 filename
='dingusfish.gif')
1028 intro
= MIMEText('''\
1031 This is the dingus fish.
1033 container
.attach(intro
)
1034 container
.attach(image
)
1035 container
['From'] = 'Barry <barry@digicool.com>'
1036 container
['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1037 container
['Subject'] = 'Here is your dingus fish'
1039 now
= 987809702.54848599
1040 timetuple
= time
.localtime(now
)
1041 if timetuple
[-1] == 0:
1042 tzsecs
= time
.timezone
1044 tzsecs
= time
.altzone
1049 tzoffset
= ' %s%04d' % (sign
, tzsecs
/ 36)
1050 container
['Date'] = time
.strftime(
1051 '%a, %d %b %Y %H:%M:%S',
1052 time
.localtime(now
)) + tzoffset
1053 self
._msg
= container
1057 def test_hierarchy(self
):
1059 eq
= self
.assertEqual
1060 unless
= self
.failUnless
1061 raises
= self
.assertRaises
1064 unless(m
.is_multipart())
1065 eq(m
.get_content_type(), 'multipart/mixed')
1066 eq(len(m
.get_payload()), 2)
1067 raises(IndexError, m
.get_payload
, 2)
1068 m0
= m
.get_payload(0)
1069 m1
= m
.get_payload(1)
1070 unless(m0
is self
._txt
)
1071 unless(m1
is self
._im
)
1072 eq(m
.get_payload(), [m0
, m1
])
1073 unless(not m0
.is_multipart())
1074 unless(not m1
.is_multipart())
1076 def test_empty_multipart_idempotent(self
):
1078 Content-Type: multipart/mixed; boundary="BOUNDARY"
1082 From: bperson@dom.ain
1090 msg
= Parser().parsestr(text
)
1091 self
.ndiffAssertEqual(text
, msg
.as_string())
1093 def test_no_parts_in_a_multipart_with_none_epilogue(self
):
1094 outer
= MIMEBase('multipart', 'mixed')
1095 outer
['Subject'] = 'A subject'
1096 outer
['To'] = 'aperson@dom.ain'
1097 outer
['From'] = 'bperson@dom.ain'
1098 outer
.set_boundary('BOUNDARY')
1099 self
.ndiffAssertEqual(outer
.as_string(), '''\
1100 Content-Type: multipart/mixed; boundary="BOUNDARY"
1104 From: bperson@dom.ain
1110 def test_no_parts_in_a_multipart_with_empty_epilogue(self
):
1111 outer
= MIMEBase('multipart', 'mixed')
1112 outer
['Subject'] = 'A subject'
1113 outer
['To'] = 'aperson@dom.ain'
1114 outer
['From'] = 'bperson@dom.ain'
1117 outer
.set_boundary('BOUNDARY')
1118 self
.ndiffAssertEqual(outer
.as_string(), '''\
1119 Content-Type: multipart/mixed; boundary="BOUNDARY"
1123 From: bperson@dom.ain
1131 def test_one_part_in_a_multipart(self
):
1132 eq
= self
.ndiffAssertEqual
1133 outer
= MIMEBase('multipart', 'mixed')
1134 outer
['Subject'] = 'A subject'
1135 outer
['To'] = 'aperson@dom.ain'
1136 outer
['From'] = 'bperson@dom.ain'
1137 outer
.set_boundary('BOUNDARY')
1138 msg
= MIMEText('hello world')
1140 eq(outer
.as_string(), '''\
1141 Content-Type: multipart/mixed; boundary="BOUNDARY"
1145 From: bperson@dom.ain
1148 Content-Type: text/plain; charset="us-ascii"
1150 Content-Transfer-Encoding: 7bit
1155 def test_seq_parts_in_a_multipart_with_empty_preamble(self
):
1156 eq
= self
.ndiffAssertEqual
1157 outer
= MIMEBase('multipart', 'mixed')
1158 outer
['Subject'] = 'A subject'
1159 outer
['To'] = 'aperson@dom.ain'
1160 outer
['From'] = 'bperson@dom.ain'
1162 msg
= MIMEText('hello world')
1164 outer
.set_boundary('BOUNDARY')
1165 eq(outer
.as_string(), '''\
1166 Content-Type: multipart/mixed; boundary="BOUNDARY"
1170 From: bperson@dom.ain
1174 Content-Type: text/plain; charset="us-ascii"
1176 Content-Transfer-Encoding: 7bit
1182 def test_seq_parts_in_a_multipart_with_none_preamble(self
):
1183 eq
= self
.ndiffAssertEqual
1184 outer
= MIMEBase('multipart', 'mixed')
1185 outer
['Subject'] = 'A subject'
1186 outer
['To'] = 'aperson@dom.ain'
1187 outer
['From'] = 'bperson@dom.ain'
1188 outer
.preamble
= None
1189 msg
= MIMEText('hello world')
1191 outer
.set_boundary('BOUNDARY')
1192 eq(outer
.as_string(), '''\
1193 Content-Type: multipart/mixed; boundary="BOUNDARY"
1197 From: bperson@dom.ain
1200 Content-Type: text/plain; charset="us-ascii"
1202 Content-Transfer-Encoding: 7bit
1208 def test_seq_parts_in_a_multipart_with_none_epilogue(self
):
1209 eq
= self
.ndiffAssertEqual
1210 outer
= MIMEBase('multipart', 'mixed')
1211 outer
['Subject'] = 'A subject'
1212 outer
['To'] = 'aperson@dom.ain'
1213 outer
['From'] = 'bperson@dom.ain'
1214 outer
.epilogue
= None
1215 msg
= MIMEText('hello world')
1217 outer
.set_boundary('BOUNDARY')
1218 eq(outer
.as_string(), '''\
1219 Content-Type: multipart/mixed; boundary="BOUNDARY"
1223 From: bperson@dom.ain
1226 Content-Type: text/plain; charset="us-ascii"
1228 Content-Transfer-Encoding: 7bit
1234 def test_seq_parts_in_a_multipart_with_empty_epilogue(self
):
1235 eq
= self
.ndiffAssertEqual
1236 outer
= MIMEBase('multipart', 'mixed')
1237 outer
['Subject'] = 'A subject'
1238 outer
['To'] = 'aperson@dom.ain'
1239 outer
['From'] = 'bperson@dom.ain'
1241 msg
= MIMEText('hello world')
1243 outer
.set_boundary('BOUNDARY')
1244 eq(outer
.as_string(), '''\
1245 Content-Type: multipart/mixed; boundary="BOUNDARY"
1249 From: bperson@dom.ain
1252 Content-Type: text/plain; charset="us-ascii"
1254 Content-Transfer-Encoding: 7bit
1261 def test_seq_parts_in_a_multipart_with_nl_epilogue(self
):
1262 eq
= self
.ndiffAssertEqual
1263 outer
= MIMEBase('multipart', 'mixed')
1264 outer
['Subject'] = 'A subject'
1265 outer
['To'] = 'aperson@dom.ain'
1266 outer
['From'] = 'bperson@dom.ain'
1267 outer
.epilogue
= '\n'
1268 msg
= MIMEText('hello world')
1270 outer
.set_boundary('BOUNDARY')
1271 eq(outer
.as_string(), '''\
1272 Content-Type: multipart/mixed; boundary="BOUNDARY"
1276 From: bperson@dom.ain
1279 Content-Type: text/plain; charset="us-ascii"
1281 Content-Transfer-Encoding: 7bit
1288 def test_message_external_body(self
):
1289 eq
= self
.assertEqual
1290 msg
= self
._msgobj
('msg_36.txt')
1291 eq(len(msg
.get_payload()), 2)
1292 msg1
= msg
.get_payload(1)
1293 eq(msg1
.get_content_type(), 'multipart/alternative')
1294 eq(len(msg1
.get_payload()), 2)
1295 for subpart
in msg1
.get_payload():
1296 eq(subpart
.get_content_type(), 'message/external-body')
1297 eq(len(subpart
.get_payload()), 1)
1298 subsubpart
= subpart
.get_payload(0)
1299 eq(subsubpart
.get_content_type(), 'text/plain')
1301 def test_double_boundary(self
):
1302 # msg_37.txt is a multipart that contains two dash-boundary's in a
1303 # row. Our interpretation of RFC 2046 calls for ignoring the second
1304 # and subsequent boundaries.
1305 msg
= self
._msgobj
('msg_37.txt')
1306 self
.assertEqual(len(msg
.get_payload()), 3)
1308 def test_nested_inner_contains_outer_boundary(self
):
1309 eq
= self
.ndiffAssertEqual
1310 # msg_38.txt has an inner part that contains outer boundaries. My
1311 # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
1312 # these are illegal and should be interpreted as unterminated inner
1314 msg
= self
._msgobj
('msg_38.txt')
1316 Iterators
._structure
(msg
, sfp
)
1317 eq(sfp
.getvalue(), """\
1320 multipart/alternative
1327 def test_nested_with_same_boundary(self
):
1328 eq
= self
.ndiffAssertEqual
1329 # msg 39.txt is similarly evil in that it's got inner parts that use
1330 # the same boundary as outer parts. Again, I believe the way this is
1331 # parsed is closest to the spirit of RFC 2046
1332 msg
= self
._msgobj
('msg_39.txt')
1334 Iterators
._structure
(msg
, sfp
)
1335 eq(sfp
.getvalue(), """\
1338 multipart/alternative
1339 application/octet-stream
1340 application/octet-stream
1344 def test_boundary_in_non_multipart(self
):
1345 msg
= self
._msgobj
('msg_40.txt')
1346 self
.assertEqual(msg
.as_string(), '''\
1348 Content-Type: text/html; boundary="--961284236552522269"
1350 ----961284236552522269
1351 Content-Type: text/html;
1352 Content-Transfer-Encoding: 7Bit
1356 ----961284236552522269--
1359 def test_boundary_with_leading_space(self
):
1360 eq
= self
.assertEqual
1361 msg
= email
.message_from_string('''\
1363 Content-Type: multipart/mixed; boundary=" XXXX"
1366 Content-Type: text/plain
1370 Content-Type: text/plain
1374 self
.failUnless(msg
.is_multipart())
1375 eq(msg
.get_boundary(), ' XXXX')
1376 eq(len(msg
.get_payload()), 2)
1378 def test_boundary_without_trailing_newline(self
):
1379 m
= Parser().parsestr("""\
1380 Content-Type: multipart/mixed; boundary="===============0012394164=="
1383 --===============0012394164==
1384 Content-Type: image/file1.jpg
1386 Content-Transfer-Encoding: base64
1389 --===============0012394164==--""")
1390 self
.assertEquals(m
.get_payload(0).get_payload(), 'YXNkZg==')
1394 # Test some badly formatted messages
1395 class TestNonConformant(TestEmailBase
):
1396 def test_parse_missing_minor_type(self
):
1397 eq
= self
.assertEqual
1398 msg
= self
._msgobj
('msg_14.txt')
1399 eq(msg
.get_content_type(), 'text/plain')
1400 eq(msg
.get_content_maintype(), 'text')
1401 eq(msg
.get_content_subtype(), 'plain')
1403 def test_same_boundary_inner_outer(self
):
1404 unless
= self
.failUnless
1405 msg
= self
._msgobj
('msg_15.txt')
1406 # XXX We can probably eventually do better
1407 inner
= msg
.get_payload(0)
1408 unless(hasattr(inner
, 'defects'))
1409 self
.assertEqual(len(inner
.defects
), 1)
1410 unless(isinstance(inner
.defects
[0],
1411 Errors
.StartBoundaryNotFoundDefect
))
1413 def test_multipart_no_boundary(self
):
1414 unless
= self
.failUnless
1415 msg
= self
._msgobj
('msg_25.txt')
1416 unless(isinstance(msg
.get_payload(), str))
1417 self
.assertEqual(len(msg
.defects
), 2)
1418 unless(isinstance(msg
.defects
[0], Errors
.NoBoundaryInMultipartDefect
))
1419 unless(isinstance(msg
.defects
[1],
1420 Errors
.MultipartInvariantViolationDefect
))
1422 def test_invalid_content_type(self
):
1423 eq
= self
.assertEqual
1424 neq
= self
.ndiffAssertEqual
1426 # RFC 2045, $5.2 says invalid yields text/plain
1427 msg
['Content-Type'] = 'text'
1428 eq(msg
.get_content_maintype(), 'text')
1429 eq(msg
.get_content_subtype(), 'plain')
1430 eq(msg
.get_content_type(), 'text/plain')
1431 # Clear the old value and try something /really/ invalid
1432 del msg
['content-type']
1433 msg
['Content-Type'] = 'foo'
1434 eq(msg
.get_content_maintype(), 'text')
1435 eq(msg
.get_content_subtype(), 'plain')
1436 eq(msg
.get_content_type(), 'text/plain')
1437 # Still, make sure that the message is idempotently generated
1441 neq(s
.getvalue(), 'Content-Type: foo\n\n')
1443 def test_no_start_boundary(self
):
1444 eq
= self
.ndiffAssertEqual
1445 msg
= self
._msgobj
('msg_31.txt')
1446 eq(msg
.get_payload(), """\
1448 Content-Type: text/plain
1453 Content-Type: text/plain
1460 def test_no_separating_blank_line(self
):
1461 eq
= self
.ndiffAssertEqual
1462 msg
= self
._msgobj
('msg_35.txt')
1463 eq(msg
.as_string(), """\
1464 From: aperson@dom.ain
1466 Subject: here's something interesting
1468 counter to RFC 2822, there's no separating newline here
1471 def test_lying_multipart(self
):
1472 unless
= self
.failUnless
1473 msg
= self
._msgobj
('msg_41.txt')
1474 unless(hasattr(msg
, 'defects'))
1475 self
.assertEqual(len(msg
.defects
), 2)
1476 unless(isinstance(msg
.defects
[0], Errors
.NoBoundaryInMultipartDefect
))
1477 unless(isinstance(msg
.defects
[1],
1478 Errors
.MultipartInvariantViolationDefect
))
1480 def test_missing_start_boundary(self
):
1481 outer
= self
._msgobj
('msg_42.txt')
1482 # The message structure is:
1487 # multipart/mixed [*]
1489 # [*] This message is missing its start boundary
1490 bad
= outer
.get_payload(1).get_payload(0)
1491 self
.assertEqual(len(bad
.defects
), 1)
1492 self
.failUnless(isinstance(bad
.defects
[0],
1493 Errors
.StartBoundaryNotFoundDefect
))
1495 def test_first_line_is_continuation_header(self
):
1496 eq
= self
.assertEqual
1497 m
= ' Line 1\nLine 2\nLine 3'
1498 msg
= email
.message_from_string(m
)
1500 eq(msg
.get_payload(), 'Line 2\nLine 3')
1501 eq(len(msg
.defects
), 1)
1502 self
.failUnless(isinstance(msg
.defects
[0],
1503 Errors
.FirstHeaderLineIsContinuationDefect
))
1504 eq(msg
.defects
[0].line
, ' Line 1\n')
1509 # Test RFC 2047 header encoding and decoding
1510 class TestRFC2047(unittest
.TestCase
):
1511 def test_rfc2047_multiline(self
):
1512 eq
= self
.assertEqual
1513 s
= """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1514 foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1515 dh
= decode_header(s
)
1518 ('r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1519 ('baz foo bar', None),
1520 ('r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1521 eq(str(make_header(dh
)),
1522 """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar
1523 =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""")
1525 def test_whitespace_eater_unicode(self
):
1526 eq
= self
.assertEqual
1527 s
= '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1528 dh
= decode_header(s
)
1529 eq(dh
, [('Andr\xe9', 'iso-8859-1'), ('Pirard <pirard@dom.ain>', None)])
1530 hu
= unicode(make_header(dh
)).encode('latin-1')
1531 eq(hu
, 'Andr\xe9 Pirard <pirard@dom.ain>')
1533 def test_whitespace_eater_unicode_2(self
):
1534 eq
= self
.assertEqual
1535 s
= 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1536 dh
= decode_header(s
)
1537 eq(dh
, [('The', None), ('quick brown fox', 'iso-8859-1'),
1538 ('jumped over the', None), ('lazy dog', 'iso-8859-1')])
1539 hu
= make_header(dh
).__unicode
__()
1540 eq(hu
, u
'The quick brown fox jumped over the lazy dog')
1542 def test_rfc2047_without_whitespace(self
):
1543 s
= 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
1544 dh
= decode_header(s
)
1545 self
.assertEqual(dh
, [(s
, None)])
1547 def test_rfc2047_with_whitespace(self
):
1548 s
= 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
1549 dh
= decode_header(s
)
1550 self
.assertEqual(dh
, [('Sm', None), ('\xf6', 'iso-8859-1'),
1551 ('rg', None), ('\xe5', 'iso-8859-1'),
1556 # Test the MIMEMessage class
1557 class TestMIMEMessage(TestEmailBase
):
1559 fp
= openfile('msg_11.txt')
1561 self
._text
= fp
.read()
1565 def test_type_error(self
):
1566 self
.assertRaises(TypeError, MIMEMessage
, 'a plain string')
1568 def test_valid_argument(self
):
1569 eq
= self
.assertEqual
1570 unless
= self
.failUnless
1571 subject
= 'A sub-message'
1573 m
['Subject'] = subject
1575 eq(r
.get_content_type(), 'message/rfc822')
1576 payload
= r
.get_payload()
1577 unless(isinstance(payload
, list))
1579 subpart
= payload
[0]
1580 unless(subpart
is m
)
1581 eq(subpart
['subject'], subject
)
1583 def test_bad_multipart(self
):
1584 eq
= self
.assertEqual
1586 msg1
['Subject'] = 'subpart 1'
1588 msg2
['Subject'] = 'subpart 2'
1589 r
= MIMEMessage(msg1
)
1590 self
.assertRaises(Errors
.MultipartConversionError
, r
.attach
, msg2
)
1592 def test_generate(self
):
1593 # First craft the message to be encapsulated
1595 m
['Subject'] = 'An enclosed message'
1596 m
.set_payload('Here is the body of the message.\n')
1598 r
['Subject'] = 'The enclosing message'
1602 self
.assertEqual(s
.getvalue(), """\
1603 Content-Type: message/rfc822
1605 Subject: The enclosing message
1607 Subject: An enclosed message
1609 Here is the body of the message.
1612 def test_parse_message_rfc822(self
):
1613 eq
= self
.assertEqual
1614 unless
= self
.failUnless
1615 msg
= self
._msgobj
('msg_11.txt')
1616 eq(msg
.get_content_type(), 'message/rfc822')
1617 payload
= msg
.get_payload()
1618 unless(isinstance(payload
, list))
1621 self
.failUnless(isinstance(submsg
, Message
))
1622 eq(submsg
['subject'], 'An enclosed message')
1623 eq(submsg
.get_payload(), 'Here is the body of the message.\n')
1626 eq
= self
.assertEqual
1627 unless
= self
.failUnless
1628 # msg 16 is a Delivery Status Notification, see RFC 1894
1629 msg
= self
._msgobj
('msg_16.txt')
1630 eq(msg
.get_content_type(), 'multipart/report')
1631 unless(msg
.is_multipart())
1632 eq(len(msg
.get_payload()), 3)
1633 # Subpart 1 is a text/plain, human readable section
1634 subpart
= msg
.get_payload(0)
1635 eq(subpart
.get_content_type(), 'text/plain')
1636 eq(subpart
.get_payload(), """\
1637 This report relates to a message you sent with the following header fields:
1639 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1640 Date: Sun, 23 Sep 2001 20:10:55 -0700
1641 From: "Ian T. Henry" <henryi@oxy.edu>
1642 To: SoCal Raves <scr@socal-raves.org>
1643 Subject: [scr] yeah for Ians!!
1645 Your message cannot be delivered to the following recipients:
1647 Recipient address: jangel1@cougar.noc.ucla.edu
1648 Reason: recipient reached disk quota
1651 # Subpart 2 contains the machine parsable DSN information. It
1652 # consists of two blocks of headers, represented by two nested Message
1654 subpart
= msg
.get_payload(1)
1655 eq(subpart
.get_content_type(), 'message/delivery-status')
1656 eq(len(subpart
.get_payload()), 2)
1657 # message/delivery-status should treat each block as a bunch of
1658 # headers, i.e. a bunch of Message objects.
1659 dsn1
= subpart
.get_payload(0)
1660 unless(isinstance(dsn1
, Message
))
1661 eq(dsn1
['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1662 eq(dsn1
.get_param('dns', header
='reporting-mta'), '')
1663 # Try a missing one <wink>
1664 eq(dsn1
.get_param('nsd', header
='reporting-mta'), None)
1665 dsn2
= subpart
.get_payload(1)
1666 unless(isinstance(dsn2
, Message
))
1667 eq(dsn2
['action'], 'failed')
1668 eq(dsn2
.get_params(header
='original-recipient'),
1669 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1670 eq(dsn2
.get_param('rfc822', header
='final-recipient'), '')
1671 # Subpart 3 is the original message
1672 subpart
= msg
.get_payload(2)
1673 eq(subpart
.get_content_type(), 'message/rfc822')
1674 payload
= subpart
.get_payload()
1675 unless(isinstance(payload
, list))
1677 subsubpart
= payload
[0]
1678 unless(isinstance(subsubpart
, Message
))
1679 eq(subsubpart
.get_content_type(), 'text/plain')
1680 eq(subsubpart
['message-id'],
1681 '<002001c144a6$8752e060$56104586@oxy.edu>')
1683 def test_epilogue(self
):
1684 eq
= self
.ndiffAssertEqual
1685 fp
= openfile('msg_21.txt')
1691 msg
['From'] = 'aperson@dom.ain'
1692 msg
['To'] = 'bperson@dom.ain'
1693 msg
['Subject'] = 'Test'
1694 msg
.preamble
= 'MIME message'
1695 msg
.epilogue
= 'End of MIME message\n'
1696 msg1
= MIMEText('One')
1697 msg2
= MIMEText('Two')
1698 msg
.add_header('Content-Type', 'multipart/mixed', boundary
='BOUNDARY')
1704 eq(sfp
.getvalue(), text
)
1706 def test_no_nl_preamble(self
):
1707 eq
= self
.ndiffAssertEqual
1709 msg
['From'] = 'aperson@dom.ain'
1710 msg
['To'] = 'bperson@dom.ain'
1711 msg
['Subject'] = 'Test'
1712 msg
.preamble
= 'MIME message'
1714 msg1
= MIMEText('One')
1715 msg2
= MIMEText('Two')
1716 msg
.add_header('Content-Type', 'multipart/mixed', boundary
='BOUNDARY')
1719 eq(msg
.as_string(), """\
1720 From: aperson@dom.ain
1723 Content-Type: multipart/mixed; boundary="BOUNDARY"
1727 Content-Type: text/plain; charset="us-ascii"
1729 Content-Transfer-Encoding: 7bit
1733 Content-Type: text/plain; charset="us-ascii"
1735 Content-Transfer-Encoding: 7bit
1741 def test_default_type(self
):
1742 eq
= self
.assertEqual
1743 fp
= openfile('msg_30.txt')
1745 msg
= email
.message_from_file(fp
)
1748 container1
= msg
.get_payload(0)
1749 eq(container1
.get_default_type(), 'message/rfc822')
1750 eq(container1
.get_content_type(), 'message/rfc822')
1751 container2
= msg
.get_payload(1)
1752 eq(container2
.get_default_type(), 'message/rfc822')
1753 eq(container2
.get_content_type(), 'message/rfc822')
1754 container1a
= container1
.get_payload(0)
1755 eq(container1a
.get_default_type(), 'text/plain')
1756 eq(container1a
.get_content_type(), 'text/plain')
1757 container2a
= container2
.get_payload(0)
1758 eq(container2a
.get_default_type(), 'text/plain')
1759 eq(container2a
.get_content_type(), 'text/plain')
1761 def test_default_type_with_explicit_container_type(self
):
1762 eq
= self
.assertEqual
1763 fp
= openfile('msg_28.txt')
1765 msg
= email
.message_from_file(fp
)
1768 container1
= msg
.get_payload(0)
1769 eq(container1
.get_default_type(), 'message/rfc822')
1770 eq(container1
.get_content_type(), 'message/rfc822')
1771 container2
= msg
.get_payload(1)
1772 eq(container2
.get_default_type(), 'message/rfc822')
1773 eq(container2
.get_content_type(), 'message/rfc822')
1774 container1a
= container1
.get_payload(0)
1775 eq(container1a
.get_default_type(), 'text/plain')
1776 eq(container1a
.get_content_type(), 'text/plain')
1777 container2a
= container2
.get_payload(0)
1778 eq(container2a
.get_default_type(), 'text/plain')
1779 eq(container2a
.get_content_type(), 'text/plain')
1781 def test_default_type_non_parsed(self
):
1782 eq
= self
.assertEqual
1783 neq
= self
.ndiffAssertEqual
1785 container
= MIMEMultipart('digest', 'BOUNDARY')
1786 container
.epilogue
= ''
1788 subpart1a
= MIMEText('message 1\n')
1789 subpart2a
= MIMEText('message 2\n')
1790 subpart1
= MIMEMessage(subpart1a
)
1791 subpart2
= MIMEMessage(subpart2a
)
1792 container
.attach(subpart1
)
1793 container
.attach(subpart2
)
1794 eq(subpart1
.get_content_type(), 'message/rfc822')
1795 eq(subpart1
.get_default_type(), 'message/rfc822')
1796 eq(subpart2
.get_content_type(), 'message/rfc822')
1797 eq(subpart2
.get_default_type(), 'message/rfc822')
1798 neq(container
.as_string(0), '''\
1799 Content-Type: multipart/digest; boundary="BOUNDARY"
1803 Content-Type: message/rfc822
1806 Content-Type: text/plain; charset="us-ascii"
1808 Content-Transfer-Encoding: 7bit
1813 Content-Type: message/rfc822
1816 Content-Type: text/plain; charset="us-ascii"
1818 Content-Transfer-Encoding: 7bit
1824 del subpart1
['content-type']
1825 del subpart1
['mime-version']
1826 del subpart2
['content-type']
1827 del subpart2
['mime-version']
1828 eq(subpart1
.get_content_type(), 'message/rfc822')
1829 eq(subpart1
.get_default_type(), 'message/rfc822')
1830 eq(subpart2
.get_content_type(), 'message/rfc822')
1831 eq(subpart2
.get_default_type(), 'message/rfc822')
1832 neq(container
.as_string(0), '''\
1833 Content-Type: multipart/digest; boundary="BOUNDARY"
1838 Content-Type: text/plain; charset="us-ascii"
1840 Content-Transfer-Encoding: 7bit
1846 Content-Type: text/plain; charset="us-ascii"
1848 Content-Transfer-Encoding: 7bit
1855 def test_mime_attachments_in_constructor(self
):
1856 eq
= self
.assertEqual
1857 text1
= MIMEText('')
1858 text2
= MIMEText('')
1859 msg
= MIMEMultipart(_subparts
=(text1
, text2
))
1860 eq(len(msg
.get_payload()), 2)
1861 eq(msg
.get_payload(0), text1
)
1862 eq(msg
.get_payload(1), text2
)
1866 # A general test of parser->model->generator idempotency. IOW, read a message
1867 # in, parse it into a message object tree, then without touching the tree,
1868 # regenerate the plain text. The original text and the transformed text
1869 # should be identical. Note: that we ignore the Unix-From since that may
1870 # contain a changed date.
1871 class TestIdempotent(TestEmailBase
):
1872 def _msgobj(self
, filename
):
1873 fp
= openfile(filename
)
1878 msg
= email
.message_from_string(data
)
1881 def _idempotent(self
, msg
, text
):
1882 eq
= self
.ndiffAssertEqual
1884 g
= Generator(s
, maxheaderlen
=0)
1886 eq(text
, s
.getvalue())
1888 def test_parse_text_message(self
):
1889 eq
= self
.assertEquals
1890 msg
, text
= self
._msgobj
('msg_01.txt')
1891 eq(msg
.get_content_type(), 'text/plain')
1892 eq(msg
.get_content_maintype(), 'text')
1893 eq(msg
.get_content_subtype(), 'plain')
1894 eq(msg
.get_params()[1], ('charset', 'us-ascii'))
1895 eq(msg
.get_param('charset'), 'us-ascii')
1896 eq(msg
.preamble
, None)
1897 eq(msg
.epilogue
, None)
1898 self
._idempotent
(msg
, text
)
1900 def test_parse_untyped_message(self
):
1901 eq
= self
.assertEquals
1902 msg
, text
= self
._msgobj
('msg_03.txt')
1903 eq(msg
.get_content_type(), 'text/plain')
1904 eq(msg
.get_params(), None)
1905 eq(msg
.get_param('charset'), None)
1906 self
._idempotent
(msg
, text
)
1908 def test_simple_multipart(self
):
1909 msg
, text
= self
._msgobj
('msg_04.txt')
1910 self
._idempotent
(msg
, text
)
1912 def test_MIME_digest(self
):
1913 msg
, text
= self
._msgobj
('msg_02.txt')
1914 self
._idempotent
(msg
, text
)
1916 def test_long_header(self
):
1917 msg
, text
= self
._msgobj
('msg_27.txt')
1918 self
._idempotent
(msg
, text
)
1920 def test_MIME_digest_with_part_headers(self
):
1921 msg
, text
= self
._msgobj
('msg_28.txt')
1922 self
._idempotent
(msg
, text
)
1924 def test_mixed_with_image(self
):
1925 msg
, text
= self
._msgobj
('msg_06.txt')
1926 self
._idempotent
(msg
, text
)
1928 def test_multipart_report(self
):
1929 msg
, text
= self
._msgobj
('msg_05.txt')
1930 self
._idempotent
(msg
, text
)
1933 msg
, text
= self
._msgobj
('msg_16.txt')
1934 self
._idempotent
(msg
, text
)
1936 def test_preamble_epilogue(self
):
1937 msg
, text
= self
._msgobj
('msg_21.txt')
1938 self
._idempotent
(msg
, text
)
1940 def test_multipart_one_part(self
):
1941 msg
, text
= self
._msgobj
('msg_23.txt')
1942 self
._idempotent
(msg
, text
)
1944 def test_multipart_no_parts(self
):
1945 msg
, text
= self
._msgobj
('msg_24.txt')
1946 self
._idempotent
(msg
, text
)
1948 def test_no_start_boundary(self
):
1949 msg
, text
= self
._msgobj
('msg_31.txt')
1950 self
._idempotent
(msg
, text
)
1952 def test_rfc2231_charset(self
):
1953 msg
, text
= self
._msgobj
('msg_32.txt')
1954 self
._idempotent
(msg
, text
)
1956 def test_more_rfc2231_parameters(self
):
1957 msg
, text
= self
._msgobj
('msg_33.txt')
1958 self
._idempotent
(msg
, text
)
1960 def test_text_plain_in_a_multipart_digest(self
):
1961 msg
, text
= self
._msgobj
('msg_34.txt')
1962 self
._idempotent
(msg
, text
)
1964 def test_nested_multipart_mixeds(self
):
1965 msg
, text
= self
._msgobj
('msg_12a.txt')
1966 self
._idempotent
(msg
, text
)
1968 def test_message_external_body_idempotent(self
):
1969 msg
, text
= self
._msgobj
('msg_36.txt')
1970 self
._idempotent
(msg
, text
)
1972 def test_content_type(self
):
1973 eq
= self
.assertEquals
1974 unless
= self
.failUnless
1975 # Get a message object and reset the seek pointer for other tests
1976 msg
, text
= self
._msgobj
('msg_05.txt')
1977 eq(msg
.get_content_type(), 'multipart/report')
1978 # Test the Content-Type: parameters
1980 for pk
, pv
in msg
.get_params():
1982 eq(params
['report-type'], 'delivery-status')
1983 eq(params
['boundary'], 'D1690A7AC1.996856090/mail.example.com')
1984 eq(msg
.preamble
, 'This is a MIME-encapsulated message.\n')
1985 eq(msg
.epilogue
, '\n')
1986 eq(len(msg
.get_payload()), 3)
1987 # Make sure the subparts are what we expect
1988 msg1
= msg
.get_payload(0)
1989 eq(msg1
.get_content_type(), 'text/plain')
1990 eq(msg1
.get_payload(), 'Yadda yadda yadda\n')
1991 msg2
= msg
.get_payload(1)
1992 eq(msg2
.get_content_type(), 'text/plain')
1993 eq(msg2
.get_payload(), 'Yadda yadda yadda\n')
1994 msg3
= msg
.get_payload(2)
1995 eq(msg3
.get_content_type(), 'message/rfc822')
1996 self
.failUnless(isinstance(msg3
, Message
))
1997 payload
= msg3
.get_payload()
1998 unless(isinstance(payload
, list))
2001 unless(isinstance(msg4
, Message
))
2002 eq(msg4
.get_payload(), 'Yadda yadda yadda\n')
2004 def test_parser(self
):
2005 eq
= self
.assertEquals
2006 unless
= self
.failUnless
2007 msg
, text
= self
._msgobj
('msg_06.txt')
2008 # Check some of the outer headers
2009 eq(msg
.get_content_type(), 'message/rfc822')
2010 # Make sure the payload is a list of exactly one sub-Message, and that
2011 # that submessage has a type of text/plain
2012 payload
= msg
.get_payload()
2013 unless(isinstance(payload
, list))
2016 self
.failUnless(isinstance(msg1
, Message
))
2017 eq(msg1
.get_content_type(), 'text/plain')
2018 self
.failUnless(isinstance(msg1
.get_payload(), str))
2019 eq(msg1
.get_payload(), '\n')
2023 # Test various other bits of the package's functionality
2024 class TestMiscellaneous(TestEmailBase
):
2025 def test_message_from_string(self
):
2026 fp
= openfile('msg_01.txt')
2031 msg
= email
.message_from_string(text
)
2033 # Don't wrap/continue long headers since we're trying to test
2035 g
= Generator(s
, maxheaderlen
=0)
2037 self
.assertEqual(text
, s
.getvalue())
2039 def test_message_from_file(self
):
2040 fp
= openfile('msg_01.txt')
2044 msg
= email
.message_from_file(fp
)
2046 # Don't wrap/continue long headers since we're trying to test
2048 g
= Generator(s
, maxheaderlen
=0)
2050 self
.assertEqual(text
, s
.getvalue())
2054 def test_message_from_string_with_class(self
):
2055 unless
= self
.failUnless
2056 fp
= openfile('msg_01.txt')
2062 class MyMessage(Message
):
2065 msg
= email
.message_from_string(text
, MyMessage
)
2066 unless(isinstance(msg
, MyMessage
))
2067 # Try something more complicated
2068 fp
= openfile('msg_02.txt')
2073 msg
= email
.message_from_string(text
, MyMessage
)
2074 for subpart
in msg
.walk():
2075 unless(isinstance(subpart
, MyMessage
))
2077 def test_message_from_file_with_class(self
):
2078 unless
= self
.failUnless
2080 class MyMessage(Message
):
2083 fp
= openfile('msg_01.txt')
2085 msg
= email
.message_from_file(fp
, MyMessage
)
2088 unless(isinstance(msg
, MyMessage
))
2089 # Try something more complicated
2090 fp
= openfile('msg_02.txt')
2092 msg
= email
.message_from_file(fp
, MyMessage
)
2095 for subpart
in msg
.walk():
2096 unless(isinstance(subpart
, MyMessage
))
2098 def test__all__(self
):
2099 module
= __import__('email')
2100 all
= module
.__all
__
2102 self
.assertEqual(all
, [
2104 'Charset', 'Encoders', 'Errors', 'Generator',
2105 'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
2106 'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
2107 'MIMENonMultipart', 'MIMEText', 'Message',
2108 'Parser', 'Utils', 'base64MIME',
2110 'base64mime', 'charset', 'encoders', 'errors', 'generator',
2111 'header', 'iterators', 'message', 'message_from_file',
2112 'message_from_string', 'mime', 'parser',
2113 'quopriMIME', 'quoprimime', 'utils',
2116 def test_formatdate(self
):
2118 self
.assertEqual(Utils
.parsedate(Utils
.formatdate(now
))[:6],
2119 time
.gmtime(now
)[:6])
2121 def test_formatdate_localtime(self
):
2124 Utils
.parsedate(Utils
.formatdate(now
, localtime
=True))[:6],
2125 time
.localtime(now
)[:6])
2127 def test_formatdate_usegmt(self
):
2130 Utils
.formatdate(now
, localtime
=False),
2131 time
.strftime('%a, %d %b %Y %H:%M:%S -0000', time
.gmtime(now
)))
2133 Utils
.formatdate(now
, localtime
=False, usegmt
=True),
2134 time
.strftime('%a, %d %b %Y %H:%M:%S GMT', time
.gmtime(now
)))
2136 def test_parsedate_none(self
):
2137 self
.assertEqual(Utils
.parsedate(''), None)
2139 def test_parsedate_compact(self
):
2140 # The FWS after the comma is optional
2141 self
.assertEqual(Utils
.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2142 Utils
.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2144 def test_parsedate_no_dayofweek(self
):
2145 eq
= self
.assertEqual
2146 eq(Utils
.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2147 (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
2149 def test_parsedate_compact_no_dayofweek(self
):
2150 eq
= self
.assertEqual
2151 eq(Utils
.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2152 (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
2154 def test_parsedate_acceptable_to_time_functions(self
):
2155 eq
= self
.assertEqual
2156 timetup
= Utils
.parsedate('5 Feb 2003 13:47:26 -0800')
2157 t
= int(time
.mktime(timetup
))
2158 eq(time
.localtime(t
)[:6], timetup
[:6])
2159 eq(int(time
.strftime('%Y', timetup
)), 2003)
2160 timetup
= Utils
.parsedate_tz('5 Feb 2003 13:47:26 -0800')
2161 t
= int(time
.mktime(timetup
[:9]))
2162 eq(time
.localtime(t
)[:6], timetup
[:6])
2163 eq(int(time
.strftime('%Y', timetup
[:9])), 2003)
2165 def test_parseaddr_empty(self
):
2166 self
.assertEqual(Utils
.parseaddr('<>'), ('', ''))
2167 self
.assertEqual(Utils
.formataddr(Utils
.parseaddr('<>')), '')
2169 def test_noquote_dump(self
):
2171 Utils
.formataddr(('A Silly Person', 'person@dom.ain')),
2172 'A Silly Person <person@dom.ain>')
2174 def test_escape_dump(self
):
2176 Utils
.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
2177 r
'"A \(Very\) Silly Person" <person@dom.ain>')
2178 a
= r
'A \(Special\) Person'
2179 b
= 'person@dom.ain'
2180 self
.assertEqual(Utils
.parseaddr(Utils
.formataddr((a
, b
))), (a
, b
))
2182 def test_escape_backslashes(self
):
2184 Utils
.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
2185 r
'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
2186 a
= r
'Arthur \Backslash\ Foobar'
2187 b
= 'person@dom.ain'
2188 self
.assertEqual(Utils
.parseaddr(Utils
.formataddr((a
, b
))), (a
, b
))
2190 def test_name_with_dot(self
):
2191 x
= 'John X. Doe <jxd@example.com>'
2192 y
= '"John X. Doe" <jxd@example.com>'
2193 a
, b
= ('John X. Doe', 'jxd@example.com')
2194 self
.assertEqual(Utils
.parseaddr(x
), (a
, b
))
2195 self
.assertEqual(Utils
.parseaddr(y
), (a
, b
))
2196 # formataddr() quotes the name if there's a dot in it
2197 self
.assertEqual(Utils
.formataddr((a
, b
)), y
)
2199 def test_multiline_from_comment(self
):
2202 \tBar <foo@example.com>"""
2203 self
.assertEqual(Utils
.parseaddr(x
), ('Foo Bar', 'foo@example.com'))
2205 def test_quote_dump(self
):
2207 Utils
.formataddr(('A Silly; Person', 'person@dom.ain')),
2208 r
'"A Silly; Person" <person@dom.ain>')
2210 def test_fix_eols(self
):
2211 eq
= self
.assertEqual
2212 eq(Utils
.fix_eols('hello'), 'hello')
2213 eq(Utils
.fix_eols('hello\n'), 'hello\r\n')
2214 eq(Utils
.fix_eols('hello\r'), 'hello\r\n')
2215 eq(Utils
.fix_eols('hello\r\n'), 'hello\r\n')
2216 eq(Utils
.fix_eols('hello\n\r'), 'hello\r\n\r\n')
2218 def test_charset_richcomparisons(self
):
2219 eq
= self
.assertEqual
2220 ne
= self
.failIfEqual
2223 eq(cset1
, 'us-ascii')
2224 eq(cset1
, 'US-ASCII')
2225 eq(cset1
, 'Us-AsCiI')
2226 eq('us-ascii', cset1
)
2227 eq('US-ASCII', cset1
)
2228 eq('Us-AsCiI', cset1
)
2229 ne(cset1
, 'usascii')
2230 ne(cset1
, 'USASCII')
2231 ne(cset1
, 'UsAsCiI')
2232 ne('usascii', cset1
)
2233 ne('USASCII', cset1
)
2234 ne('UsAsCiI', cset1
)
2238 def test_getaddresses(self
):
2239 eq
= self
.assertEqual
2240 eq(Utils
.getaddresses(['aperson@dom.ain (Al Person)',
2241 'Bud Person <bperson@dom.ain>']),
2242 [('Al Person', 'aperson@dom.ain'),
2243 ('Bud Person', 'bperson@dom.ain')])
2245 def test_getaddresses_nasty(self
):
2246 eq
= self
.assertEqual
2247 eq(Utils
.getaddresses(['foo: ;']), [('', '')])
2248 eq(Utils
.getaddresses(
2250 [('', ''), ('', ''), ('', '*--')])
2251 eq(Utils
.getaddresses(
2252 ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
2253 [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
2255 def test_getaddresses_embedded_comment(self
):
2256 """Test proper handling of a nested comment"""
2257 eq
= self
.assertEqual
2258 addrs
= Utils
.getaddresses(['User ((nested comment)) <foo@bar.com>'])
2259 eq(addrs
[0][1], 'foo@bar.com')
2261 def test_utils_quote_unquote(self
):
2262 eq
= self
.assertEqual
2264 msg
.add_header('content-disposition', 'attachment',
2265 filename
='foo\\wacky"name')
2266 eq(msg
.get_filename(), 'foo\\wacky"name')
2268 def test_get_body_encoding_with_bogus_charset(self
):
2269 charset
= Charset('not a charset')
2270 self
.assertEqual(charset
.get_body_encoding(), 'base64')
2272 def test_get_body_encoding_with_uppercase_charset(self
):
2273 eq
= self
.assertEqual
2275 msg
['Content-Type'] = 'text/plain; charset=UTF-8'
2276 eq(msg
['content-type'], 'text/plain; charset=UTF-8')
2277 charsets
= msg
.get_charsets()
2278 eq(len(charsets
), 1)
2279 eq(charsets
[0], 'utf-8')
2280 charset
= Charset(charsets
[0])
2281 eq(charset
.get_body_encoding(), 'base64')
2282 msg
.set_payload('hello world', charset
=charset
)
2283 eq(msg
.get_payload(), 'aGVsbG8gd29ybGQ=\n')
2284 eq(msg
.get_payload(decode
=True), 'hello world')
2285 eq(msg
['content-transfer-encoding'], 'base64')
2288 msg
['Content-Type'] = 'text/plain; charset="US-ASCII"'
2289 charsets
= msg
.get_charsets()
2290 eq(len(charsets
), 1)
2291 eq(charsets
[0], 'us-ascii')
2292 charset
= Charset(charsets
[0])
2293 eq(charset
.get_body_encoding(), Encoders
.encode_7or8bit
)
2294 msg
.set_payload('hello world', charset
=charset
)
2295 eq(msg
.get_payload(), 'hello world')
2296 eq(msg
['content-transfer-encoding'], '7bit')
2298 def test_charsets_case_insensitive(self
):
2299 lc
= Charset('us-ascii')
2300 uc
= Charset('US-ASCII')
2301 self
.assertEqual(lc
.get_body_encoding(), uc
.get_body_encoding())
2303 def test_partial_falls_inside_message_delivery_status(self
):
2304 eq
= self
.ndiffAssertEqual
2305 # The Parser interface provides chunks of data to FeedParser in 8192
2306 # byte gulps. SF bug #1076485 found one of those chunks inside
2307 # message/delivery-status header block, which triggered an
2308 # unreadline() of NeedMoreData.
2309 msg
= self
._msgobj
('msg_43.txt')
2311 Iterators
._structure
(msg
, sfp
)
2312 eq(sfp
.getvalue(), """\
2315 message/delivery-status
2347 # Test the iterator/generators
2348 class TestIterators(TestEmailBase
):
2349 def test_body_line_iterator(self
):
2350 eq
= self
.assertEqual
2351 neq
= self
.ndiffAssertEqual
2352 # First a simple non-multipart message
2353 msg
= self
._msgobj
('msg_01.txt')
2354 it
= Iterators
.body_line_iterator(msg
)
2357 neq(EMPTYSTRING
.join(lines
), msg
.get_payload())
2358 # Now a more complicated multipart
2359 msg
= self
._msgobj
('msg_02.txt')
2360 it
= Iterators
.body_line_iterator(msg
)
2363 fp
= openfile('msg_19.txt')
2365 neq(EMPTYSTRING
.join(lines
), fp
.read())
2369 def test_typed_subpart_iterator(self
):
2370 eq
= self
.assertEqual
2371 msg
= self
._msgobj
('msg_04.txt')
2372 it
= Iterators
.typed_subpart_iterator(msg
, 'text')
2377 lines
.append(subpart
.get_payload())
2379 eq(EMPTYSTRING
.join(lines
), """\
2380 a simple kind of mirror
2381 to reflect upon our own
2382 a simple kind of mirror
2383 to reflect upon our own
2386 def test_typed_subpart_iterator_default_type(self
):
2387 eq
= self
.assertEqual
2388 msg
= self
._msgobj
('msg_03.txt')
2389 it
= Iterators
.typed_subpart_iterator(msg
, 'text', 'plain')
2394 lines
.append(subpart
.get_payload())
2396 eq(EMPTYSTRING
.join(lines
), """\
2400 Do you like this message?
2407 class TestParsers(TestEmailBase
):
2408 def test_header_parser(self
):
2409 eq
= self
.assertEqual
2410 # Parse only the headers of a complex multipart MIME document
2411 fp
= openfile('msg_02.txt')
2413 msg
= HeaderParser().parse(fp
)
2416 eq(msg
['from'], 'ppp-request@zzz.org')
2417 eq(msg
['to'], 'ppp@zzz.org')
2418 eq(msg
.get_content_type(), 'multipart/mixed')
2419 self
.failIf(msg
.is_multipart())
2420 self
.failUnless(isinstance(msg
.get_payload(), str))
2422 def test_whitespace_continuation(self
):
2423 eq
= self
.assertEqual
2424 # This message contains a line after the Subject: header that has only
2425 # whitespace, but it is not empty!
2426 msg
= email
.message_from_string("""\
2427 From: aperson@dom.ain
2429 Subject: the next line has a space on it
2431 Date: Mon, 8 Apr 2002 15:09:19 -0400
2434 Here's the message body
2436 eq(msg
['subject'], 'the next line has a space on it\n ')
2437 eq(msg
['message-id'], 'spam')
2438 eq(msg
.get_payload(), "Here's the message body\n")
2440 def test_whitespace_continuation_last_header(self
):
2441 eq
= self
.assertEqual
2442 # Like the previous test, but the subject line is the last
2444 msg
= email
.message_from_string("""\
2445 From: aperson@dom.ain
2447 Date: Mon, 8 Apr 2002 15:09:19 -0400
2449 Subject: the next line has a space on it
2452 Here's the message body
2454 eq(msg
['subject'], 'the next line has a space on it\n ')
2455 eq(msg
['message-id'], 'spam')
2456 eq(msg
.get_payload(), "Here's the message body\n")
2458 def test_crlf_separation(self
):
2459 eq
= self
.assertEqual
2460 fp
= openfile('msg_26.txt', mode
='rb')
2462 msg
= Parser().parse(fp
)
2465 eq(len(msg
.get_payload()), 2)
2466 part1
= msg
.get_payload(0)
2467 eq(part1
.get_content_type(), 'text/plain')
2468 eq(part1
.get_payload(), 'Simple email with attachment.\r\n\r\n')
2469 part2
= msg
.get_payload(1)
2470 eq(part2
.get_content_type(), 'application/riscos')
2472 def test_multipart_digest_with_extra_mime_headers(self
):
2473 eq
= self
.assertEqual
2474 neq
= self
.ndiffAssertEqual
2475 fp
= openfile('msg_28.txt')
2477 msg
= email
.message_from_file(fp
)
2486 eq(msg
.is_multipart(), 1)
2487 eq(len(msg
.get_payload()), 2)
2488 part1
= msg
.get_payload(0)
2489 eq(part1
.get_content_type(), 'message/rfc822')
2490 eq(part1
.is_multipart(), 1)
2491 eq(len(part1
.get_payload()), 1)
2492 part1a
= part1
.get_payload(0)
2493 eq(part1a
.is_multipart(), 0)
2494 eq(part1a
.get_content_type(), 'text/plain')
2495 neq(part1a
.get_payload(), 'message 1\n')
2496 # next message/rfc822
2497 part2
= msg
.get_payload(1)
2498 eq(part2
.get_content_type(), 'message/rfc822')
2499 eq(part2
.is_multipart(), 1)
2500 eq(len(part2
.get_payload()), 1)
2501 part2a
= part2
.get_payload(0)
2502 eq(part2a
.is_multipart(), 0)
2503 eq(part2a
.get_content_type(), 'text/plain')
2504 neq(part2a
.get_payload(), 'message 2\n')
2506 def test_three_lines(self
):
2507 # A bug report by Andrew McNamara
2508 lines
= ['From: Andrew Person <aperson@dom.ain',
2510 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2511 msg
= email
.message_from_string(NL
.join(lines
))
2512 self
.assertEqual(msg
['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2514 def test_strip_line_feed_and_carriage_return_in_headers(self
):
2515 eq
= self
.assertEqual
2516 # For [ 1002475 ] email message parser doesn't handle \r\n correctly
2518 value2
= 'more text'
2519 m
= 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
2521 msg
= email
.message_from_string(m
)
2522 eq(msg
.get('Header'), value1
)
2523 eq(msg
.get('Next-Header'), value2
)
2525 def test_rfc2822_header_syntax(self
):
2526 eq
= self
.assertEqual
2527 m
= '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2528 msg
= email
.message_from_string(m
)
2529 eq(len(msg
.keys()), 3)
2532 eq(keys
, ['!"#QUX;~', '>From', 'From'])
2533 eq(msg
.get_payload(), 'body')
2535 def test_rfc2822_space_not_allowed_in_header(self
):
2536 eq
= self
.assertEqual
2537 m
= '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2538 msg
= email
.message_from_string(m
)
2539 eq(len(msg
.keys()), 0)
2541 def test_rfc2822_one_character_header(self
):
2542 eq
= self
.assertEqual
2543 m
= 'A: first header\nB: second header\nCC: third header\n\nbody'
2544 msg
= email
.message_from_string(m
)
2545 headers
= msg
.keys()
2547 eq(headers
, ['A', 'B', 'CC'])
2548 eq(msg
.get_payload(), 'body')
2552 class TestBase64(unittest
.TestCase
):
2554 eq
= self
.assertEqual
2555 eq(base64MIME
.base64_len('hello'),
2556 len(base64MIME
.encode('hello', eol
='')))
2557 for size
in range(15):
2558 if size
== 0 : bsize
= 0
2559 elif size
<= 3 : bsize
= 4
2560 elif size
<= 6 : bsize
= 8
2561 elif size
<= 9 : bsize
= 12
2562 elif size
<= 12: bsize
= 16
2564 eq(base64MIME
.base64_len('x'*size
), bsize
)
2566 def test_decode(self
):
2567 eq
= self
.assertEqual
2568 eq(base64MIME
.decode(''), '')
2569 eq(base64MIME
.decode('aGVsbG8='), 'hello')
2570 eq(base64MIME
.decode('aGVsbG8=', 'X'), 'hello')
2571 eq(base64MIME
.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
2573 def test_encode(self
):
2574 eq
= self
.assertEqual
2575 eq(base64MIME
.encode(''), '')
2576 eq(base64MIME
.encode('hello'), 'aGVsbG8=\n')
2577 # Test the binary flag
2578 eq(base64MIME
.encode('hello\n'), 'aGVsbG8K\n')
2579 eq(base64MIME
.encode('hello\n', 0), 'aGVsbG8NCg==\n')
2580 # Test the maxlinelen arg
2581 eq(base64MIME
.encode('xxxx ' * 20, maxlinelen
=40), """\
2582 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2583 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2584 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2587 # Test the eol argument
2588 eq(base64MIME
.encode('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2589 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2590 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2591 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2595 def test_header_encode(self
):
2596 eq
= self
.assertEqual
2597 he
= base64MIME
.header_encode
2598 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
2599 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
2600 # Test the charset option
2601 eq(he('hello', charset
='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
2602 # Test the keep_eols flag
2603 eq(he('hello\nworld', keep_eols
=True),
2604 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
2605 # Test the maxlinelen argument
2606 eq(he('xxxx ' * 20, maxlinelen
=40), """\
2607 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
2608 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
2609 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
2610 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
2611 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
2612 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2613 # Test the eol argument
2614 eq(he('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2615 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
2616 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
2617 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
2618 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
2619 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
2620 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2624 class TestQuopri(unittest
.TestCase
):
2626 self
.hlit
= [chr(x
) for x
in range(ord('a'), ord('z')+1)] + \
2627 [chr(x
) for x
in range(ord('A'), ord('Z')+1)] + \
2628 [chr(x
) for x
in range(ord('0'), ord('9')+1)] + \
2629 ['!', '*', '+', '-', '/', ' ']
2630 self
.hnon
= [chr(x
) for x
in range(256) if chr(x
) not in self
.hlit
]
2631 assert len(self
.hlit
) + len(self
.hnon
) == 256
2632 self
.blit
= [chr(x
) for x
in range(ord(' '), ord('~')+1)] + ['\t']
2633 self
.blit
.remove('=')
2634 self
.bnon
= [chr(x
) for x
in range(256) if chr(x
) not in self
.blit
]
2635 assert len(self
.blit
) + len(self
.bnon
) == 256
2637 def test_header_quopri_check(self
):
2639 self
.failIf(quopriMIME
.header_quopri_check(c
))
2641 self
.failUnless(quopriMIME
.header_quopri_check(c
))
2643 def test_body_quopri_check(self
):
2645 self
.failIf(quopriMIME
.body_quopri_check(c
))
2647 self
.failUnless(quopriMIME
.body_quopri_check(c
))
2649 def test_header_quopri_len(self
):
2650 eq
= self
.assertEqual
2651 hql
= quopriMIME
.header_quopri_len
2652 enc
= quopriMIME
.header_encode
2653 for s
in ('hello', 'h@e@l@l@o@'):
2654 # Empty charset and no line-endings. 7 == RFC chrome
2655 eq(hql(s
), len(enc(s
, charset
='', eol
=''))-7)
2661 def test_body_quopri_len(self
):
2662 eq
= self
.assertEqual
2663 bql
= quopriMIME
.body_quopri_len
2669 def test_quote_unquote_idempotent(self
):
2670 for x
in range(256):
2672 self
.assertEqual(quopriMIME
.unquote(quopriMIME
.quote(c
)), c
)
2674 def test_header_encode(self
):
2675 eq
= self
.assertEqual
2676 he
= quopriMIME
.header_encode
2677 eq(he('hello'), '=?iso-8859-1?q?hello?=')
2678 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
2679 # Test the charset option
2680 eq(he('hello', charset
='iso-8859-2'), '=?iso-8859-2?q?hello?=')
2681 # Test the keep_eols flag
2682 eq(he('hello\nworld', keep_eols
=True), '=?iso-8859-1?q?hello=0Aworld?=')
2683 # Test a non-ASCII character
2684 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
2685 # Test the maxlinelen argument
2686 eq(he('xxxx ' * 20, maxlinelen
=40), """\
2687 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
2688 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
2689 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
2690 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
2691 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2692 # Test the eol argument
2693 eq(he('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2694 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
2695 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
2696 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
2697 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
2698 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2700 def test_decode(self
):
2701 eq
= self
.assertEqual
2702 eq(quopriMIME
.decode(''), '')
2703 eq(quopriMIME
.decode('hello'), 'hello')
2704 eq(quopriMIME
.decode('hello', 'X'), 'hello')
2705 eq(quopriMIME
.decode('hello\nworld', 'X'), 'helloXworld')
2707 def test_encode(self
):
2708 eq
= self
.assertEqual
2709 eq(quopriMIME
.encode(''), '')
2710 eq(quopriMIME
.encode('hello'), 'hello')
2711 # Test the binary flag
2712 eq(quopriMIME
.encode('hello\r\nworld'), 'hello\nworld')
2713 eq(quopriMIME
.encode('hello\r\nworld', 0), 'hello\nworld')
2714 # Test the maxlinelen arg
2715 eq(quopriMIME
.encode('xxxx ' * 20, maxlinelen
=40), """\
2716 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
2717 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
2718 x xxxx xxxx xxxx xxxx=20""")
2719 # Test the eol argument
2720 eq(quopriMIME
.encode('xxxx ' * 20, maxlinelen
=40, eol
='\r\n'), """\
2721 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
2722 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
2723 x xxxx xxxx xxxx xxxx=20""")
2724 eq(quopriMIME
.encode("""\
2734 # Test the Charset class
2735 class TestCharset(unittest
.TestCase
):
2737 from email
import Charset
as CharsetModule
2739 del CharsetModule
.CHARSETS
['fake']
2743 def test_idempotent(self
):
2744 eq
= self
.assertEqual
2745 # Make sure us-ascii = no Unicode conversion
2746 c
= Charset('us-ascii')
2748 sp
= c
.to_splittable(s
)
2749 eq(s
, c
.from_splittable(sp
))
2750 # test 8-bit idempotency with us-ascii
2751 s
= '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
2752 sp
= c
.to_splittable(s
)
2753 eq(s
, c
.from_splittable(sp
))
2755 def test_body_encode(self
):
2756 eq
= self
.assertEqual
2757 # Try a charset with QP body encoding
2758 c
= Charset('iso-8859-1')
2759 eq('hello w=F6rld', c
.body_encode('hello w\xf6rld'))
2760 # Try a charset with Base64 body encoding
2761 c
= Charset('utf-8')
2762 eq('aGVsbG8gd29ybGQ=\n', c
.body_encode('hello world'))
2763 # Try a charset with None body encoding
2764 c
= Charset('us-ascii')
2765 eq('hello world', c
.body_encode('hello world'))
2766 # Try the convert argument, where input codec <> output codec
2767 c
= Charset('euc-jp')
2768 # With apologies to Tokio Kikuchi ;)
2770 eq('\x1b$B5FCO;~IW\x1b(B',
2771 c
.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
2772 eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
2773 c
.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
2775 # We probably don't have the Japanese codecs installed
2777 # Testing SF bug #625509, which we have to fake, since there are no
2778 # built-in encodings where the header encoding is QP but the body
2780 from email
import Charset
as CharsetModule
2781 CharsetModule
.add_charset('fake', CharsetModule
.QP
, None)
2783 eq('hello w\xf6rld', c
.body_encode('hello w\xf6rld'))
2785 def test_unicode_charset_name(self
):
2786 charset
= Charset(u
'us-ascii')
2787 self
.assertEqual(str(charset
), 'us-ascii')
2788 self
.assertRaises(Errors
.CharsetError
, Charset
, 'asc\xffii')
2792 # Test multilingual MIME headers.
2793 class TestHeader(TestEmailBase
):
2794 def test_simple(self
):
2795 eq
= self
.ndiffAssertEqual
2796 h
= Header('Hello World!')
2797 eq(h
.encode(), 'Hello World!')
2798 h
.append(' Goodbye World!')
2799 eq(h
.encode(), 'Hello World! Goodbye World!')
2801 def test_simple_surprise(self
):
2802 eq
= self
.ndiffAssertEqual
2803 h
= Header('Hello World!')
2804 eq(h
.encode(), 'Hello World!')
2805 h
.append('Goodbye World!')
2806 eq(h
.encode(), 'Hello World! Goodbye World!')
2808 def test_header_needs_no_decoding(self
):
2809 h
= 'no decoding needed'
2810 self
.assertEqual(decode_header(h
), [(h
, None)])
2812 def test_long(self
):
2813 h
= Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
2815 for l
in h
.encode(splitchars
=' ').split('\n '):
2816 self
.failUnless(len(l
) <= 76)
2818 def test_multilingual(self
):
2819 eq
= self
.ndiffAssertEqual
2820 g
= Charset("iso-8859-1")
2821 cz
= Charset("iso-8859-2")
2822 utf8
= Charset("utf-8")
2823 g_head
= "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
2824 cz_head
= "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
2825 utf8_head
= u
"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
2826 h
= Header(g_head
, g
)
2827 h
.append(cz_head
, cz
)
2828 h
.append(utf8_head
, utf8
)
2831 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_ko?=
2832 =?iso-8859-1?q?mfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wan?=
2833 =?iso-8859-1?q?dgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6?=
2834 =?iso-8859-1?q?rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
2835 =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
2836 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
2837 =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
2838 =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
2839 =?utf-8?q?_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das_Oder_die_Fl?=
2840 =?utf-8?b?aXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBo+OBpuOBhOOBvuOBmQ==?=
2841 =?utf-8?b?44CC?=""")
2842 eq(decode_header(enc
),
2843 [(g_head
, "iso-8859-1"), (cz_head
, "iso-8859-2"),
2844 (utf8_head
, "utf-8")])
2846 eq(ustr
.encode('utf-8'),
2847 'Die Mieter treten hier ein werden mit einem Foerderband '
2848 'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
2849 'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
2850 'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
2851 'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
2852 '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
2853 '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
2854 '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
2855 '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
2856 '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
2857 '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
2858 '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
2859 '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
2860 'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
2861 'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
2862 '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
2863 # Test make_header()
2864 newh
= make_header(decode_header(enc
))
2867 def test_header_ctor_default_args(self
):
2868 eq
= self
.ndiffAssertEqual
2871 h
.append('foo', Charset('iso-8859-1'))
2872 eq(h
, '=?iso-8859-1?q?foo?=')
2874 def test_explicit_maxlinelen(self
):
2875 eq
= self
.ndiffAssertEqual
2876 hstr
= 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
2879 A very long line that must get split to something other than at the 76th
2880 character boundary to test the non-default behavior''')
2881 h
= Header(hstr
, header_name
='Subject')
2883 A very long line that must get split to something other than at the
2884 76th character boundary to test the non-default behavior''')
2885 h
= Header(hstr
, maxlinelen
=1024, header_name
='Subject')
2886 eq(h
.encode(), hstr
)
2888 def test_us_ascii_header(self
):
2889 eq
= self
.assertEqual
2891 x
= decode_header(s
)
2892 eq(x
, [('hello', None)])
2896 def test_string_charset(self
):
2897 eq
= self
.assertEqual
2899 h
.append('hello', 'iso-8859-1')
2900 eq(h
, '=?iso-8859-1?q?hello?=')
2902 ## def test_unicode_error(self):
2903 ## raises = self.assertRaises
2904 ## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
2905 ## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
2907 ## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
2908 ## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
2909 ## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
2911 def test_utf8_shortest(self
):
2912 eq
= self
.assertEqual
2913 h
= Header(u
'p\xf6stal', 'utf-8')
2914 eq(h
.encode(), '=?utf-8?q?p=C3=B6stal?=')
2915 h
= Header(u
'\u83ca\u5730\u6642\u592b', 'utf-8')
2916 eq(h
.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
2918 def test_bad_8bit_header(self
):
2919 raises
= self
.assertRaises
2920 eq
= self
.assertEqual
2921 x
= 'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
2922 raises(UnicodeError, Header
, x
)
2924 raises(UnicodeError, h
.append
, x
)
2925 eq(str(Header(x
, errors
='replace')), x
)
2926 h
.append(x
, errors
='replace')
2929 def test_encoded_adjacent_nonencoded(self
):
2930 eq
= self
.assertEqual
2932 h
.append('hello', 'iso-8859-1')
2935 eq(s
, '=?iso-8859-1?q?hello?= world')
2936 h
= make_header(decode_header(s
))
2939 def test_whitespace_eater(self
):
2940 eq
= self
.assertEqual
2941 s
= 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
2942 parts
= decode_header(s
)
2943 eq(parts
, [('Subject:', None), ('\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), ('zz.', None)])
2944 hdr
= make_header(parts
)
2946 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
2948 def test_broken_base64_header(self
):
2949 raises
= self
.assertRaises
2950 s
= 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ?='
2951 raises(Errors
.HeaderParseError
, decode_header
, s
)
2955 # Test RFC 2231 header parameters (en/de)coding
2956 class TestRFC2231(TestEmailBase
):
2957 def test_get_param(self
):
2958 eq
= self
.assertEqual
2959 msg
= self
._msgobj
('msg_29.txt')
2960 eq(msg
.get_param('title'),
2961 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2962 eq(msg
.get_param('title', unquote
=False),
2963 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
2965 def test_set_param(self
):
2966 eq
= self
.assertEqual
2968 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
2970 eq(msg
.get_param('title'),
2971 ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
2972 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
2973 charset
='us-ascii', language
='en')
2974 eq(msg
.get_param('title'),
2975 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2976 msg
= self
._msgobj
('msg_01.txt')
2977 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
2978 charset
='us-ascii', language
='en')
2979 eq(msg
.as_string(), """\
2980 Return-Path: <bbb@zzz.org>
2981 Delivered-To: bbb@zzz.org
2982 Received: by mail.zzz.org (Postfix, from userid 889)
2983 \tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
2985 Content-Transfer-Encoding: 7bit
2986 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2987 From: bbb@ddd.com (John X. Doe)
2989 Subject: This is a test message
2990 Date: Fri, 4 May 2001 14:05:44 -0400
2991 Content-Type: text/plain; charset=us-ascii;
2992 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2997 Do you like this message?
3002 def test_del_param(self
):
3003 eq
= self
.ndiffAssertEqual
3004 msg
= self
._msgobj
('msg_01.txt')
3005 msg
.set_param('foo', 'bar', charset
='us-ascii', language
='en')
3006 msg
.set_param('title', 'This is even more ***fun*** isn\'t it!',
3007 charset
='us-ascii', language
='en')
3008 msg
.del_param('foo', header
='Content-Type')
3009 eq(msg
.as_string(), """\
3010 Return-Path: <bbb@zzz.org>
3011 Delivered-To: bbb@zzz.org
3012 Received: by mail.zzz.org (Postfix, from userid 889)
3013 \tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3015 Content-Transfer-Encoding: 7bit
3016 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
3017 From: bbb@ddd.com (John X. Doe)
3019 Subject: This is a test message
3020 Date: Fri, 4 May 2001 14:05:44 -0400
3021 Content-Type: text/plain; charset="us-ascii";
3022 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
3027 Do you like this message?
3032 def test_rfc2231_get_content_charset(self
):
3033 eq
= self
.assertEqual
3034 msg
= self
._msgobj
('msg_32.txt')
3035 eq(msg
.get_content_charset(), 'us-ascii')
3037 def test_rfc2231_no_language_or_charset(self
):
3039 Content-Transfer-Encoding: 8bit
3040 Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
3041 Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
3044 msg
= email
.message_from_string(m
)
3045 param
= msg
.get_param('NAME')
3046 self
.failIf(isinstance(param
, tuple))
3049 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
3051 def test_rfc2231_no_language_or_charset_in_filename(self
):
3053 Content-Disposition: inline;
3054 \tfilename*0*="''This%20is%20even%20more%20";
3055 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3056 \tfilename*2="is it not.pdf"
3059 msg
= email
.message_from_string(m
)
3060 self
.assertEqual(msg
.get_filename(),
3061 'This is even more ***fun*** is it not.pdf')
3063 def test_rfc2231_no_language_or_charset_in_filename_encoded(self
):
3065 Content-Disposition: inline;
3066 \tfilename*0*="''This%20is%20even%20more%20";
3067 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3068 \tfilename*2="is it not.pdf"
3071 msg
= email
.message_from_string(m
)
3072 self
.assertEqual(msg
.get_filename(),
3073 'This is even more ***fun*** is it not.pdf')
3075 def test_rfc2231_partly_encoded(self
):
3077 Content-Disposition: inline;
3078 \tfilename*0="''This%20is%20even%20more%20";
3079 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3080 \tfilename*2="is it not.pdf"
3083 msg
= email
.message_from_string(m
)
3086 'This%20is%20even%20more%20***fun*** is it not.pdf')
3088 def test_rfc2231_partly_nonencoded(self
):
3090 Content-Disposition: inline;
3091 \tfilename*0="This%20is%20even%20more%20";
3092 \tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
3093 \tfilename*2="is it not.pdf"
3096 msg
= email
.message_from_string(m
)
3099 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
3101 def test_rfc2231_no_language_or_charset_in_boundary(self
):
3103 Content-Type: multipart/alternative;
3104 \tboundary*0*="''This%20is%20even%20more%20";
3105 \tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
3106 \tboundary*2="is it not.pdf"
3109 msg
= email
.message_from_string(m
)
3110 self
.assertEqual(msg
.get_boundary(),
3111 'This is even more ***fun*** is it not.pdf')
3113 def test_rfc2231_no_language_or_charset_in_charset(self
):
3114 # This is a nonsensical charset value, but tests the code anyway
3116 Content-Type: text/plain;
3117 \tcharset*0*="This%20is%20even%20more%20";
3118 \tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
3119 \tcharset*2="is it not.pdf"
3122 msg
= email
.message_from_string(m
)
3123 self
.assertEqual(msg
.get_content_charset(),
3124 'this is even more ***fun*** is it not.pdf')
3126 def test_rfc2231_bad_encoding_in_filename(self
):
3128 Content-Disposition: inline;
3129 \tfilename*0*="bogus'xx'This%20is%20even%20more%20";
3130 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3131 \tfilename*2="is it not.pdf"
3134 msg
= email
.message_from_string(m
)
3135 self
.assertEqual(msg
.get_filename(),
3136 'This is even more ***fun*** is it not.pdf')
3138 def test_rfc2231_bad_encoding_in_charset(self
):
3140 Content-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
3143 msg
= email
.message_from_string(m
)
3144 # This should return None because non-ascii characters in the charset
3146 self
.assertEqual(msg
.get_content_charset(), None)
3148 def test_rfc2231_bad_character_in_charset(self
):
3150 Content-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
3153 msg
= email
.message_from_string(m
)
3154 # This should return None because non-ascii characters in the charset
3156 self
.assertEqual(msg
.get_content_charset(), None)
3158 def test_rfc2231_bad_character_in_filename(self
):
3160 Content-Disposition: inline;
3161 \tfilename*0*="ascii'xx'This%20is%20even%20more%20";
3162 \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3163 \tfilename*2*="is it not.pdf%E2"
3166 msg
= email
.message_from_string(m
)
3167 self
.assertEqual(msg
.get_filename(),
3168 u
'This is even more ***fun*** is it not.pdf\ufffd')
3170 def test_rfc2231_unknown_encoding(self
):
3172 Content-Transfer-Encoding: 8bit
3173 Content-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
3176 msg
= email
.message_from_string(m
)
3177 self
.assertEqual(msg
.get_filename(), 'myfile.txt')
3179 def test_rfc2231_single_tick_in_filename_extended(self
):
3180 eq
= self
.assertEqual
3182 Content-Type: application/x-foo;
3183 \tname*0*=\"Frank's\"; name*1*=\" Document\"
3186 msg
= email
.message_from_string(m
)
3187 charset
, language
, s
= msg
.get_param('name')
3190 eq(s
, "Frank's Document")
3192 def test_rfc2231_single_tick_in_filename(self
):
3194 Content-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
3197 msg
= email
.message_from_string(m
)
3198 param
= msg
.get_param('name')
3199 self
.failIf(isinstance(param
, tuple))
3200 self
.assertEqual(param
, "Frank's Document")
3202 def test_rfc2231_tick_attack_extended(self
):
3203 eq
= self
.assertEqual
3205 Content-Type: application/x-foo;
3206 \tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
3209 msg
= email
.message_from_string(m
)
3210 charset
, language
, s
= msg
.get_param('name')
3211 eq(charset
, 'us-ascii')
3212 eq(language
, 'en-us')
3213 eq(s
, "Frank's Document")
3215 def test_rfc2231_tick_attack(self
):
3217 Content-Type: application/x-foo;
3218 \tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
3221 msg
= email
.message_from_string(m
)
3222 param
= msg
.get_param('name')
3223 self
.failIf(isinstance(param
, tuple))
3224 self
.assertEqual(param
, "us-ascii'en-us'Frank's Document")
3226 def test_rfc2231_no_extended_values(self
):
3227 eq
= self
.assertEqual
3229 Content-Type: application/x-foo; name=\"Frank's Document\"
3232 msg
= email
.message_from_string(m
)
3233 eq(msg
.get_param('name'), "Frank's Document")
3235 def test_rfc2231_encoded_then_unencoded_segments(self
):
3236 eq
= self
.assertEqual
3238 Content-Type: application/x-foo;
3239 \tname*0*=\"us-ascii'en-us'My\";
3240 \tname*1=\" Document\";
3241 \tname*2*=\" For You\"
3244 msg
= email
.message_from_string(m
)
3245 charset
, language
, s
= msg
.get_param('name')
3246 eq(charset
, 'us-ascii')
3247 eq(language
, 'en-us')
3248 eq(s
, 'My Document For You')
3250 def test_rfc2231_unencoded_then_encoded_segments(self
):
3251 eq
= self
.assertEqual
3253 Content-Type: application/x-foo;
3254 \tname*0=\"us-ascii'en-us'My\";
3255 \tname*1*=\" Document\";
3256 \tname*2*=\" For You\"
3259 msg
= email
.message_from_string(m
)
3260 charset
, language
, s
= msg
.get_param('name')
3261 eq(charset
, 'us-ascii')
3262 eq(language
, 'en-us')
3263 eq(s
, 'My Document For You')
3268 mod
= sys
.modules
[__name__
]
3269 return [getattr(mod
, name
) for name
in dir(mod
) if name
.startswith('Test')]
3273 suite
= unittest
.TestSuite()
3274 for testclass
in _testclasses():
3275 suite
.addTest(unittest
.makeSuite(testclass
))
3280 for testclass
in _testclasses():
3281 run_unittest(testclass
)
3285 if __name__
== '__main__':
3286 unittest
.main(defaultTest
='suite')