This commit was manufactured by cvs2svn to create tag 'r222'.
[python/dscho.git] / Lib / email / test / test_email.py
blob176e7740c225e298b2bfda4f3479cf3f0e0596e1
1 # Copyright (C) 2001,2002 Python Software Foundation
2 # email package unit tests
4 import sys
5 import os
6 import time
7 import unittest
8 import base64
9 import difflib
10 from cStringIO import StringIO
11 from types import StringType, ListType
12 import warnings
14 import email
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
38 NL = '\n'
39 EMPTYSTRING = ''
40 SPACE = ' '
42 # We don't care about DeprecationWarnings
43 warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
47 def openfile(filename):
48 path = os.path.join(os.path.dirname(landmark), 'data', filename)
49 return open(path, 'r')
53 # Base test class
54 class TestEmailBase(unittest.TestCase):
55 if hasattr(difflib, 'ndiff'):
56 # Python 2.2 and beyond
57 def ndiffAssertEqual(self, first, second):
58 """Like failUnlessEqual except use ndiff for readable output."""
59 if first <> second:
60 sfirst = str(first)
61 ssecond = str(second)
62 diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
63 fp = StringIO()
64 print >> fp, NL, NL.join(diff)
65 raise self.failureException, fp.getvalue()
66 else:
67 # Python 2.1
68 ndiffAssertEqual = unittest.TestCase.assertEqual
70 def _msgobj(self, filename):
71 fp = openfile(findfile(filename))
72 try:
73 msg = email.message_from_file(fp)
74 finally:
75 fp.close()
76 return msg
80 # Test various aspects of the Message class's API
81 class TestMessageAPI(TestEmailBase):
82 def test_get_all(self):
83 eq = self.assertEqual
84 msg = self._msgobj('msg_20.txt')
85 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
86 eq(msg.get_all('xx', 'n/a'), 'n/a')
88 def test_getset_charset(self):
89 eq = self.assertEqual
90 msg = Message()
91 eq(msg.get_charset(), None)
92 charset = Charset('iso-8859-1')
93 msg.set_charset(charset)
94 eq(msg['mime-version'], '1.0')
95 eq(msg.get_type(), 'text/plain')
96 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
97 eq(msg.get_param('charset'), 'iso-8859-1')
98 eq(msg['content-transfer-encoding'], 'quoted-printable')
99 eq(msg.get_charset().input_charset, 'iso-8859-1')
100 # Remove the charset
101 msg.set_charset(None)
102 eq(msg.get_charset(), None)
103 eq(msg['content-type'], 'text/plain')
104 # Try adding a charset when there's already MIME headers present
105 msg = Message()
106 msg['MIME-Version'] = '2.0'
107 msg['Content-Type'] = 'text/x-weird'
108 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
109 msg.set_charset(charset)
110 eq(msg['mime-version'], '2.0')
111 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
112 eq(msg['content-transfer-encoding'], 'quinted-puntable')
114 def test_set_charset_from_string(self):
115 eq = self.assertEqual
116 msg = Message()
117 msg.set_charset('us-ascii')
118 eq(msg.get_charset().input_charset, 'us-ascii')
119 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
121 def test_set_payload_with_charset(self):
122 msg = Message()
123 charset = Charset('iso-8859-1')
124 msg.set_payload('This is a string payload', charset)
125 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
127 def test_get_charsets(self):
128 eq = self.assertEqual
130 msg = self._msgobj('msg_08.txt')
131 charsets = msg.get_charsets()
132 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
134 msg = self._msgobj('msg_09.txt')
135 charsets = msg.get_charsets('dingbat')
136 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
137 'koi8-r'])
139 msg = self._msgobj('msg_12.txt')
140 charsets = msg.get_charsets()
141 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
142 'iso-8859-3', 'us-ascii', 'koi8-r'])
144 def test_get_filename(self):
145 eq = self.assertEqual
147 msg = self._msgobj('msg_04.txt')
148 filenames = [p.get_filename() for p in msg.get_payload()]
149 eq(filenames, ['msg.txt', 'msg.txt'])
151 msg = self._msgobj('msg_07.txt')
152 subpart = msg.get_payload(1)
153 eq(subpart.get_filename(), 'dingusfish.gif')
155 def test_get_boundary(self):
156 eq = self.assertEqual
157 msg = self._msgobj('msg_07.txt')
158 # No quotes!
159 eq(msg.get_boundary(), 'BOUNDARY')
161 def test_set_boundary(self):
162 eq = self.assertEqual
163 # This one has no existing boundary parameter, but the Content-Type:
164 # header appears fifth.
165 msg = self._msgobj('msg_01.txt')
166 msg.set_boundary('BOUNDARY')
167 header, value = msg.items()[4]
168 eq(header.lower(), 'content-type')
169 eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
170 # This one has a Content-Type: header, with a boundary, stuck in the
171 # middle of its headers. Make sure the order is preserved; it should
172 # be fifth.
173 msg = self._msgobj('msg_04.txt')
174 msg.set_boundary('BOUNDARY')
175 header, value = msg.items()[4]
176 eq(header.lower(), 'content-type')
177 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
178 # And this one has no Content-Type: header at all.
179 msg = self._msgobj('msg_03.txt')
180 self.assertRaises(Errors.HeaderParseError,
181 msg.set_boundary, 'BOUNDARY')
183 def test_get_decoded_payload(self):
184 eq = self.assertEqual
185 msg = self._msgobj('msg_10.txt')
186 # The outer message is a multipart
187 eq(msg.get_payload(decode=1), None)
188 # Subpart 1 is 7bit encoded
189 eq(msg.get_payload(0).get_payload(decode=1),
190 'This is a 7bit encoded message.\n')
191 # Subpart 2 is quopri
192 eq(msg.get_payload(1).get_payload(decode=1),
193 '\xa1This is a Quoted Printable encoded message!\n')
194 # Subpart 3 is base64
195 eq(msg.get_payload(2).get_payload(decode=1),
196 'This is a Base64 encoded message.')
197 # Subpart 4 has no Content-Transfer-Encoding: header.
198 eq(msg.get_payload(3).get_payload(decode=1),
199 'This has no Content-Transfer-Encoding: header.\n')
201 def test_decoded_generator(self):
202 eq = self.assertEqual
203 msg = self._msgobj('msg_07.txt')
204 fp = openfile('msg_17.txt')
205 try:
206 text = fp.read()
207 finally:
208 fp.close()
209 s = StringIO()
210 g = DecodedGenerator(s)
211 g.flatten(msg)
212 eq(s.getvalue(), text)
214 def test__contains__(self):
215 msg = Message()
216 msg['From'] = 'Me'
217 msg['to'] = 'You'
218 # Check for case insensitivity
219 self.failUnless('from' in msg)
220 self.failUnless('From' in msg)
221 self.failUnless('FROM' in msg)
222 self.failUnless('to' in msg)
223 self.failUnless('To' in msg)
224 self.failUnless('TO' in msg)
226 def test_as_string(self):
227 eq = self.assertEqual
228 msg = self._msgobj('msg_01.txt')
229 fp = openfile('msg_01.txt')
230 try:
231 text = fp.read()
232 finally:
233 fp.close()
234 eq(text, msg.as_string())
235 fullrepr = str(msg)
236 lines = fullrepr.split('\n')
237 self.failUnless(lines[0].startswith('From '))
238 eq(text, NL.join(lines[1:]))
240 def test_bad_param(self):
241 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
242 self.assertEqual(msg.get_param('baz'), '')
244 def test_missing_filename(self):
245 msg = email.message_from_string("From: foo\n")
246 self.assertEqual(msg.get_filename(), None)
248 def test_bogus_filename(self):
249 msg = email.message_from_string(
250 "Content-Disposition: blarg; filename\n")
251 self.assertEqual(msg.get_filename(), '')
253 def test_missing_boundary(self):
254 msg = email.message_from_string("From: foo\n")
255 self.assertEqual(msg.get_boundary(), None)
257 def test_get_params(self):
258 eq = self.assertEqual
259 msg = email.message_from_string(
260 'X-Header: foo=one; bar=two; baz=three\n')
261 eq(msg.get_params(header='x-header'),
262 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
263 msg = email.message_from_string(
264 'X-Header: foo; bar=one; baz=two\n')
265 eq(msg.get_params(header='x-header'),
266 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
267 eq(msg.get_params(), None)
268 msg = email.message_from_string(
269 'X-Header: foo; bar="one"; baz=two\n')
270 eq(msg.get_params(header='x-header'),
271 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
273 def test_get_param_liberal(self):
274 msg = Message()
275 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
276 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
278 def test_get_param(self):
279 eq = self.assertEqual
280 msg = email.message_from_string(
281 "X-Header: foo=one; bar=two; baz=three\n")
282 eq(msg.get_param('bar', header='x-header'), 'two')
283 eq(msg.get_param('quuz', header='x-header'), None)
284 eq(msg.get_param('quuz'), None)
285 msg = email.message_from_string(
286 'X-Header: foo; bar="one"; baz=two\n')
287 eq(msg.get_param('foo', header='x-header'), '')
288 eq(msg.get_param('bar', header='x-header'), 'one')
289 eq(msg.get_param('baz', header='x-header'), 'two')
290 # XXX: We are not RFC-2045 compliant! We cannot parse:
291 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
292 # msg.get_param("weird")
293 # yet.
295 def test_get_param_funky_continuation_lines(self):
296 msg = self._msgobj('msg_22.txt')
297 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
299 def test_has_key(self):
300 msg = email.message_from_string('Header: exists')
301 self.failUnless(msg.has_key('header'))
302 self.failUnless(msg.has_key('Header'))
303 self.failUnless(msg.has_key('HEADER'))
304 self.failIf(msg.has_key('headeri'))
306 def test_set_param(self):
307 eq = self.assertEqual
308 msg = Message()
309 msg.set_param('charset', 'iso-2022-jp')
310 eq(msg.get_param('charset'), 'iso-2022-jp')
311 msg.set_param('importance', 'high value')
312 eq(msg.get_param('importance'), 'high value')
313 eq(msg.get_param('importance', unquote=0), '"high value"')
314 eq(msg.get_params(), [('text/plain', ''),
315 ('charset', 'iso-2022-jp'),
316 ('importance', 'high value')])
317 eq(msg.get_params(unquote=0), [('text/plain', ''),
318 ('charset', '"iso-2022-jp"'),
319 ('importance', '"high value"')])
320 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
321 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
323 def test_del_param(self):
324 eq = self.assertEqual
325 msg = self._msgobj('msg_05.txt')
326 eq(msg.get_params(),
327 [('multipart/report', ''), ('report-type', 'delivery-status'),
328 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
329 old_val = msg.get_param("report-type")
330 msg.del_param("report-type")
331 eq(msg.get_params(),
332 [('multipart/report', ''),
333 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
334 msg.set_param("report-type", old_val)
335 eq(msg.get_params(),
336 [('multipart/report', ''),
337 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
338 ('report-type', old_val)])
340 def test_set_type(self):
341 eq = self.assertEqual
342 msg = Message()
343 self.assertRaises(ValueError, msg.set_type, 'text')
344 msg.set_type('text/plain')
345 eq(msg['content-type'], 'text/plain')
346 msg.set_param('charset', 'us-ascii')
347 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
348 msg.set_type('text/html')
349 eq(msg['content-type'], 'text/html; charset="us-ascii"')
351 def test_get_content_type_missing(self):
352 msg = Message()
353 self.assertEqual(msg.get_content_type(), 'text/plain')
355 def test_get_content_type_missing_with_default_type(self):
356 msg = Message()
357 msg.set_default_type('message/rfc822')
358 self.assertEqual(msg.get_content_type(), 'message/rfc822')
360 def test_get_content_type_from_message_implicit(self):
361 msg = self._msgobj('msg_30.txt')
362 self.assertEqual(msg.get_payload(0).get_content_type(),
363 'message/rfc822')
365 def test_get_content_type_from_message_explicit(self):
366 msg = self._msgobj('msg_28.txt')
367 self.assertEqual(msg.get_payload(0).get_content_type(),
368 'message/rfc822')
370 def test_get_content_type_from_message_text_plain_implicit(self):
371 msg = self._msgobj('msg_03.txt')
372 self.assertEqual(msg.get_content_type(), 'text/plain')
374 def test_get_content_type_from_message_text_plain_explicit(self):
375 msg = self._msgobj('msg_01.txt')
376 self.assertEqual(msg.get_content_type(), 'text/plain')
378 def test_get_content_maintype_missing(self):
379 msg = Message()
380 self.assertEqual(msg.get_content_maintype(), 'text')
382 def test_get_content_maintype_missing_with_default_type(self):
383 msg = Message()
384 msg.set_default_type('message/rfc822')
385 self.assertEqual(msg.get_content_maintype(), 'message')
387 def test_get_content_maintype_from_message_implicit(self):
388 msg = self._msgobj('msg_30.txt')
389 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
391 def test_get_content_maintype_from_message_explicit(self):
392 msg = self._msgobj('msg_28.txt')
393 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
395 def test_get_content_maintype_from_message_text_plain_implicit(self):
396 msg = self._msgobj('msg_03.txt')
397 self.assertEqual(msg.get_content_maintype(), 'text')
399 def test_get_content_maintype_from_message_text_plain_explicit(self):
400 msg = self._msgobj('msg_01.txt')
401 self.assertEqual(msg.get_content_maintype(), 'text')
403 def test_get_content_subtype_missing(self):
404 msg = Message()
405 self.assertEqual(msg.get_content_subtype(), 'plain')
407 def test_get_content_subtype_missing_with_default_type(self):
408 msg = Message()
409 msg.set_default_type('message/rfc822')
410 self.assertEqual(msg.get_content_subtype(), 'rfc822')
412 def test_get_content_subtype_from_message_implicit(self):
413 msg = self._msgobj('msg_30.txt')
414 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
416 def test_get_content_subtype_from_message_explicit(self):
417 msg = self._msgobj('msg_28.txt')
418 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
420 def test_get_content_subtype_from_message_text_plain_implicit(self):
421 msg = self._msgobj('msg_03.txt')
422 self.assertEqual(msg.get_content_subtype(), 'plain')
424 def test_get_content_subtype_from_message_text_plain_explicit(self):
425 msg = self._msgobj('msg_01.txt')
426 self.assertEqual(msg.get_content_subtype(), 'plain')
428 def test_get_content_maintype_error(self):
429 msg = Message()
430 msg['Content-Type'] = 'no-slash-in-this-string'
431 self.assertEqual(msg.get_content_maintype(), 'text')
433 def test_get_content_subtype_error(self):
434 msg = Message()
435 msg['Content-Type'] = 'no-slash-in-this-string'
436 self.assertEqual(msg.get_content_subtype(), 'plain')
438 def test_replace_header(self):
439 eq = self.assertEqual
440 msg = Message()
441 msg.add_header('First', 'One')
442 msg.add_header('Second', 'Two')
443 msg.add_header('Third', 'Three')
444 eq(msg.keys(), ['First', 'Second', 'Third'])
445 eq(msg.values(), ['One', 'Two', 'Three'])
446 msg.replace_header('Second', 'Twenty')
447 eq(msg.keys(), ['First', 'Second', 'Third'])
448 eq(msg.values(), ['One', 'Twenty', 'Three'])
449 msg.add_header('First', 'Eleven')
450 msg.replace_header('First', 'One Hundred')
451 eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
452 eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
453 self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
457 # Test the email.Encoders module
458 class TestEncoders(unittest.TestCase):
459 def test_encode_noop(self):
460 eq = self.assertEqual
461 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
462 eq(msg.get_payload(), 'hello world\n')
464 def test_encode_7bit(self):
465 eq = self.assertEqual
466 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
467 eq(msg.get_payload(), 'hello world\n')
468 eq(msg['content-transfer-encoding'], '7bit')
469 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
470 eq(msg.get_payload(), 'hello \x7f world\n')
471 eq(msg['content-transfer-encoding'], '7bit')
473 def test_encode_8bit(self):
474 eq = self.assertEqual
475 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
476 eq(msg.get_payload(), 'hello \x80 world\n')
477 eq(msg['content-transfer-encoding'], '8bit')
479 def test_encode_empty_payload(self):
480 eq = self.assertEqual
481 msg = Message()
482 msg.set_charset('us-ascii')
483 eq(msg['content-transfer-encoding'], '7bit')
485 def test_encode_base64(self):
486 eq = self.assertEqual
487 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
488 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
489 eq(msg['content-transfer-encoding'], 'base64')
491 def test_encode_quoted_printable(self):
492 eq = self.assertEqual
493 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
494 eq(msg.get_payload(), 'hello=20world\n')
495 eq(msg['content-transfer-encoding'], 'quoted-printable')
497 def test_default_cte(self):
498 eq = self.assertEqual
499 msg = MIMEText('hello world')
500 eq(msg['content-transfer-encoding'], '7bit')
502 def test_default_cte(self):
503 eq = self.assertEqual
504 # With no explicit _charset its us-ascii, and all are 7-bit
505 msg = MIMEText('hello world')
506 eq(msg['content-transfer-encoding'], '7bit')
507 # Similar, but with 8-bit data
508 msg = MIMEText('hello \xf8 world')
509 eq(msg['content-transfer-encoding'], '8bit')
510 # And now with a different charset
511 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
512 eq(msg['content-transfer-encoding'], 'quoted-printable')
516 # Test long header wrapping
517 class TestLongHeaders(TestEmailBase):
518 def test_split_long_continuation(self):
519 eq = self.ndiffAssertEqual
520 msg = email.message_from_string("""\
521 Subject: bug demonstration
522 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
523 \tmore text
525 test
526 """)
527 sfp = StringIO()
528 g = Generator(sfp)
529 g.flatten(msg)
530 eq(sfp.getvalue(), """\
531 Subject: bug demonstration
532 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
533 \tmore text
535 test
536 """)
538 def test_another_long_almost_unsplittable_header(self):
539 eq = self.ndiffAssertEqual
540 hstr = """\
541 bug demonstration
542 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
543 \tmore text"""
544 h = Header(hstr, continuation_ws='\t')
545 eq(h.encode(), """\
546 bug demonstration
547 \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
548 \tmore text""")
549 h = Header(hstr)
550 eq(h.encode(), """\
551 bug demonstration
552 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
553 more text""")
555 def test_long_nonstring(self):
556 eq = self.ndiffAssertEqual
557 g = Charset("iso-8859-1")
558 cz = Charset("iso-8859-2")
559 utf8 = Charset("utf-8")
560 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. "
561 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
562 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")
563 h = Header(g_head, g)
564 h.append(cz_head, cz)
565 h.append(utf8_head, utf8)
566 msg = Message()
567 msg['Subject'] = h
568 sfp = StringIO()
569 g = Generator(sfp)
570 g.flatten(msg)
571 eq(sfp.getvalue(), '''\
572 Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
573 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
574 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
575 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
576 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
577 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
578 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
579 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
580 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
581 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
582 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
583 =?utf-8?q?s_Nunstuck_git_und?=
584 =?utf-8?q?_Slotermeyer=3F_Ja!_Beiherhund_das_Ode?=
585 =?utf-8?q?r_die_Flipperwaldt?=
586 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=
588 ''')
589 eq(h.encode(), '''\
590 =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
591 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
592 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
593 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
594 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
595 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
596 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
597 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
598 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
599 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
600 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
601 =?utf-8?q?s_Nunstuck_git_und?=
602 =?utf-8?q?_Slotermeyer=3F_Ja!_Beiherhund_das_Ode?=
603 =?utf-8?q?r_die_Flipperwaldt?=
604 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=''')
606 def test_long_header_encode(self):
607 eq = self.ndiffAssertEqual
608 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
609 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
610 header_name='X-Foobar-Spoink-Defrobnit')
611 eq(h.encode(), '''\
612 wasnipoop; giraffes="very-long-necked-animals";
613 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
615 def test_long_header_encode_with_tab_continuation(self):
616 eq = self.ndiffAssertEqual
617 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
618 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
619 header_name='X-Foobar-Spoink-Defrobnit',
620 continuation_ws='\t')
621 eq(h.encode(), '''\
622 wasnipoop; giraffes="very-long-necked-animals";
623 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
625 def test_header_splitter(self):
626 eq = self.ndiffAssertEqual
627 msg = MIMEText('')
628 # It'd be great if we could use add_header() here, but that doesn't
629 # guarantee an order of the parameters.
630 msg['X-Foobar-Spoink-Defrobnit'] = (
631 'wasnipoop; giraffes="very-long-necked-animals"; '
632 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
633 sfp = StringIO()
634 g = Generator(sfp)
635 g.flatten(msg)
636 eq(sfp.getvalue(), '''\
637 Content-Type: text/plain; charset="us-ascii"
638 MIME-Version: 1.0
639 Content-Transfer-Encoding: 7bit
640 X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
641 \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
643 ''')
645 def test_no_semis_header_splitter(self):
646 eq = self.ndiffAssertEqual
647 msg = Message()
648 msg['From'] = 'test@dom.ain'
649 msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
650 msg.set_payload('Test')
651 sfp = StringIO()
652 g = Generator(sfp)
653 g.flatten(msg)
654 eq(sfp.getvalue(), """\
655 From: test@dom.ain
656 References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
657 \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
659 Test""")
661 def test_no_split_long_header(self):
662 eq = self.ndiffAssertEqual
663 hstr = 'References: ' + 'x' * 80
664 h = Header(hstr, continuation_ws='\t')
665 eq(h.encode(), """\
666 References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
668 def test_splitting_multiple_long_lines(self):
669 eq = self.ndiffAssertEqual
670 hstr = """\
671 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)
672 \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)
673 \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)
675 h = Header(hstr, continuation_ws='\t')
676 eq(h.encode(), """\
677 from babylon.socal-raves.org (localhost [127.0.0.1]);
678 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
679 \tfor <mailman-admin@babylon.socal-raves.org>;
680 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
681 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
682 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
683 \tfor <mailman-admin@babylon.socal-raves.org>;
684 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
685 \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
686 \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
687 \tfor <mailman-admin@babylon.socal-raves.org>;
688 \tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
690 def test_splitting_first_line_only_is_long(self):
691 eq = self.ndiffAssertEqual
692 hstr = """\
693 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
694 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
695 \tid 17k4h5-00034i-00
696 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
697 h = Header(hstr, maxlinelen=78, header_name='Received',
698 continuation_ws='\t')
699 eq(h.encode(), """\
700 from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
701 \thelo=cthulhu.gerg.ca)
702 \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
703 \tid 17k4h5-00034i-00
704 \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
706 def test_long_8bit_header(self):
707 eq = self.ndiffAssertEqual
708 msg = Message()
709 h = Header('Britische Regierung gibt', 'iso-8859-1')
710 h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
711 msg['Subject'] = h
712 eq(msg.as_string(), """\
713 Subject: =?iso-8859-1?q?Britische_Regierung_gibt?=
714 =?iso-8859-1?q?gr=FCnes_Licht_f=FCr_Offshore-Windkraftprojekte?=
716 """)
718 def test_long_8bit_header_no_charset(self):
719 eq = self.ndiffAssertEqual
720 msg = Message()
721 msg['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
722 eq(msg.as_string(), """\
723 Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
725 """)
729 # Test mangling of "From " lines in the body of a message
730 class TestFromMangling(unittest.TestCase):
731 def setUp(self):
732 self.msg = Message()
733 self.msg['From'] = 'aaa@bbb.org'
734 self.msg.set_payload("""\
735 From the desk of A.A.A.:
736 Blah blah blah
737 """)
739 def test_mangled_from(self):
740 s = StringIO()
741 g = Generator(s, mangle_from_=1)
742 g.flatten(self.msg)
743 self.assertEqual(s.getvalue(), """\
744 From: aaa@bbb.org
746 >From the desk of A.A.A.:
747 Blah blah blah
748 """)
750 def test_dont_mangle_from(self):
751 s = StringIO()
752 g = Generator(s, mangle_from_=0)
753 g.flatten(self.msg)
754 self.assertEqual(s.getvalue(), """\
755 From: aaa@bbb.org
757 From the desk of A.A.A.:
758 Blah blah blah
759 """)
763 # Test the basic MIMEAudio class
764 class TestMIMEAudio(unittest.TestCase):
765 def setUp(self):
766 # In Python, audiotest.au lives in Lib/test not Lib/test/data
767 fp = open(findfile('audiotest.au'), 'rb')
768 try:
769 self._audiodata = fp.read()
770 finally:
771 fp.close()
772 self._au = MIMEAudio(self._audiodata)
774 def test_guess_minor_type(self):
775 self.assertEqual(self._au.get_type(), 'audio/basic')
777 def test_encoding(self):
778 payload = self._au.get_payload()
779 self.assertEqual(base64.decodestring(payload), self._audiodata)
781 def checkSetMinor(self):
782 au = MIMEAudio(self._audiodata, 'fish')
783 self.assertEqual(im.get_type(), 'audio/fish')
785 def test_custom_encoder(self):
786 eq = self.assertEqual
787 def encoder(msg):
788 orig = msg.get_payload()
789 msg.set_payload(0)
790 msg['Content-Transfer-Encoding'] = 'broken64'
791 au = MIMEAudio(self._audiodata, _encoder=encoder)
792 eq(au.get_payload(), 0)
793 eq(au['content-transfer-encoding'], 'broken64')
795 def test_add_header(self):
796 eq = self.assertEqual
797 unless = self.failUnless
798 self._au.add_header('Content-Disposition', 'attachment',
799 filename='audiotest.au')
800 eq(self._au['content-disposition'],
801 'attachment; filename="audiotest.au"')
802 eq(self._au.get_params(header='content-disposition'),
803 [('attachment', ''), ('filename', 'audiotest.au')])
804 eq(self._au.get_param('filename', header='content-disposition'),
805 'audiotest.au')
806 missing = []
807 eq(self._au.get_param('attachment', header='content-disposition'), '')
808 unless(self._au.get_param('foo', failobj=missing,
809 header='content-disposition') is missing)
810 # Try some missing stuff
811 unless(self._au.get_param('foobar', missing) is missing)
812 unless(self._au.get_param('attachment', missing,
813 header='foobar') is missing)
817 # Test the basic MIMEImage class
818 class TestMIMEImage(unittest.TestCase):
819 def setUp(self):
820 fp = openfile('PyBanner048.gif')
821 try:
822 self._imgdata = fp.read()
823 finally:
824 fp.close()
825 self._im = MIMEImage(self._imgdata)
827 def test_guess_minor_type(self):
828 self.assertEqual(self._im.get_type(), 'image/gif')
830 def test_encoding(self):
831 payload = self._im.get_payload()
832 self.assertEqual(base64.decodestring(payload), self._imgdata)
834 def checkSetMinor(self):
835 im = MIMEImage(self._imgdata, 'fish')
836 self.assertEqual(im.get_type(), 'image/fish')
838 def test_custom_encoder(self):
839 eq = self.assertEqual
840 def encoder(msg):
841 orig = msg.get_payload()
842 msg.set_payload(0)
843 msg['Content-Transfer-Encoding'] = 'broken64'
844 im = MIMEImage(self._imgdata, _encoder=encoder)
845 eq(im.get_payload(), 0)
846 eq(im['content-transfer-encoding'], 'broken64')
848 def test_add_header(self):
849 eq = self.assertEqual
850 unless = self.failUnless
851 self._im.add_header('Content-Disposition', 'attachment',
852 filename='dingusfish.gif')
853 eq(self._im['content-disposition'],
854 'attachment; filename="dingusfish.gif"')
855 eq(self._im.get_params(header='content-disposition'),
856 [('attachment', ''), ('filename', 'dingusfish.gif')])
857 eq(self._im.get_param('filename', header='content-disposition'),
858 'dingusfish.gif')
859 missing = []
860 eq(self._im.get_param('attachment', header='content-disposition'), '')
861 unless(self._im.get_param('foo', failobj=missing,
862 header='content-disposition') is missing)
863 # Try some missing stuff
864 unless(self._im.get_param('foobar', missing) is missing)
865 unless(self._im.get_param('attachment', missing,
866 header='foobar') is missing)
870 # Test the basic MIMEText class
871 class TestMIMEText(unittest.TestCase):
872 def setUp(self):
873 self._msg = MIMEText('hello there')
875 def test_types(self):
876 eq = self.assertEqual
877 unless = self.failUnless
878 eq(self._msg.get_type(), 'text/plain')
879 eq(self._msg.get_param('charset'), 'us-ascii')
880 missing = []
881 unless(self._msg.get_param('foobar', missing) is missing)
882 unless(self._msg.get_param('charset', missing, header='foobar')
883 is missing)
885 def test_payload(self):
886 self.assertEqual(self._msg.get_payload(), 'hello there\n')
887 self.failUnless(not self._msg.is_multipart())
889 def test_charset(self):
890 eq = self.assertEqual
891 msg = MIMEText('hello there', _charset='us-ascii')
892 eq(msg.get_charset().input_charset, 'us-ascii')
893 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
897 # Test a more complicated multipart/mixed type message
898 class TestMultipartMixed(unittest.TestCase):
899 def setUp(self):
900 fp = openfile('PyBanner048.gif')
901 try:
902 data = fp.read()
903 finally:
904 fp.close()
906 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
907 image = MIMEImage(data, name='dingusfish.gif')
908 image.add_header('content-disposition', 'attachment',
909 filename='dingusfish.gif')
910 intro = MIMEText('''\
911 Hi there,
913 This is the dingus fish.
914 ''')
915 container.attach(intro)
916 container.attach(image)
917 container['From'] = 'Barry <barry@digicool.com>'
918 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
919 container['Subject'] = 'Here is your dingus fish'
921 now = 987809702.54848599
922 timetuple = time.localtime(now)
923 if timetuple[-1] == 0:
924 tzsecs = time.timezone
925 else:
926 tzsecs = time.altzone
927 if tzsecs > 0:
928 sign = '-'
929 else:
930 sign = '+'
931 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
932 container['Date'] = time.strftime(
933 '%a, %d %b %Y %H:%M:%S',
934 time.localtime(now)) + tzoffset
935 self._msg = container
936 self._im = image
937 self._txt = intro
939 def test_hierarchy(self):
940 # convenience
941 eq = self.assertEqual
942 unless = self.failUnless
943 raises = self.assertRaises
944 # tests
945 m = self._msg
946 unless(m.is_multipart())
947 eq(m.get_type(), 'multipart/mixed')
948 eq(len(m.get_payload()), 2)
949 raises(IndexError, m.get_payload, 2)
950 m0 = m.get_payload(0)
951 m1 = m.get_payload(1)
952 unless(m0 is self._txt)
953 unless(m1 is self._im)
954 eq(m.get_payload(), [m0, m1])
955 unless(not m0.is_multipart())
956 unless(not m1.is_multipart())
958 def test_no_parts_in_a_multipart(self):
959 outer = MIMEBase('multipart', 'mixed')
960 outer['Subject'] = 'A subject'
961 outer['To'] = 'aperson@dom.ain'
962 outer['From'] = 'bperson@dom.ain'
963 outer.preamble = ''
964 outer.epilogue = ''
965 outer.set_boundary('BOUNDARY')
966 msg = MIMEText('hello world')
967 self.assertEqual(outer.as_string(), '''\
968 Content-Type: multipart/mixed; boundary="BOUNDARY"
969 MIME-Version: 1.0
970 Subject: A subject
971 To: aperson@dom.ain
972 From: bperson@dom.ain
974 --BOUNDARY
977 --BOUNDARY--
978 ''')
980 def test_one_part_in_a_multipart(self):
981 outer = MIMEBase('multipart', 'mixed')
982 outer['Subject'] = 'A subject'
983 outer['To'] = 'aperson@dom.ain'
984 outer['From'] = 'bperson@dom.ain'
985 outer.preamble = ''
986 outer.epilogue = ''
987 outer.set_boundary('BOUNDARY')
988 msg = MIMEText('hello world')
989 outer.attach(msg)
990 self.assertEqual(outer.as_string(), '''\
991 Content-Type: multipart/mixed; boundary="BOUNDARY"
992 MIME-Version: 1.0
993 Subject: A subject
994 To: aperson@dom.ain
995 From: bperson@dom.ain
997 --BOUNDARY
998 Content-Type: text/plain; charset="us-ascii"
999 MIME-Version: 1.0
1000 Content-Transfer-Encoding: 7bit
1002 hello world
1004 --BOUNDARY--
1005 ''')
1007 def test_seq_parts_in_a_multipart(self):
1008 outer = MIMEBase('multipart', 'mixed')
1009 outer['Subject'] = 'A subject'
1010 outer['To'] = 'aperson@dom.ain'
1011 outer['From'] = 'bperson@dom.ain'
1012 outer.preamble = ''
1013 outer.epilogue = ''
1014 msg = MIMEText('hello world')
1015 outer.attach(msg)
1016 outer.set_boundary('BOUNDARY')
1017 self.assertEqual(outer.as_string(), '''\
1018 Content-Type: multipart/mixed; boundary="BOUNDARY"
1019 MIME-Version: 1.0
1020 Subject: A subject
1021 To: aperson@dom.ain
1022 From: bperson@dom.ain
1024 --BOUNDARY
1025 Content-Type: text/plain; charset="us-ascii"
1026 MIME-Version: 1.0
1027 Content-Transfer-Encoding: 7bit
1029 hello world
1031 --BOUNDARY--
1032 ''')
1036 # Test some badly formatted messages
1037 class TestNonConformant(TestEmailBase):
1038 def test_parse_missing_minor_type(self):
1039 eq = self.assertEqual
1040 msg = self._msgobj('msg_14.txt')
1041 eq(msg.get_type(), 'text')
1042 eq(msg.get_main_type(), None)
1043 eq(msg.get_subtype(), None)
1045 def test_bogus_boundary(self):
1046 fp = openfile(findfile('msg_15.txt'))
1047 try:
1048 data = fp.read()
1049 finally:
1050 fp.close()
1051 p = Parser(strict=1)
1052 # Note, under a future non-strict parsing mode, this would parse the
1053 # message into the intended message tree.
1054 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
1056 def test_multipart_no_boundary(self):
1057 fp = openfile(findfile('msg_25.txt'))
1058 try:
1059 self.assertRaises(Errors.BoundaryError,
1060 email.message_from_file, fp)
1061 finally:
1062 fp.close()
1064 def test_invalid_content_type(self):
1065 eq = self.assertEqual
1066 neq = self.ndiffAssertEqual
1067 msg = Message()
1068 # RFC 2045, $5.2 says invalid yields text/plain
1069 msg['Content-Type'] = 'text'
1070 eq(msg.get_content_maintype(), 'text')
1071 eq(msg.get_content_subtype(), 'plain')
1072 eq(msg.get_content_type(), 'text/plain')
1073 # Clear the old value and try something /really/ invalid
1074 del msg['content-type']
1075 msg['Content-Type'] = 'foo'
1076 eq(msg.get_content_maintype(), 'text')
1077 eq(msg.get_content_subtype(), 'plain')
1078 eq(msg.get_content_type(), 'text/plain')
1079 # Still, make sure that the message is idempotently generated
1080 s = StringIO()
1081 g = Generator(s)
1082 g.flatten(msg)
1083 neq(s.getvalue(), 'Content-Type: foo\n\n')
1085 def test_no_start_boundary(self):
1086 eq = self.ndiffAssertEqual
1087 msg = self._msgobj('msg_31.txt')
1088 eq(msg.get_payload(), """\
1089 --BOUNDARY
1090 Content-Type: text/plain
1092 message 1
1094 --BOUNDARY
1095 Content-Type: text/plain
1097 message 2
1099 --BOUNDARY--
1100 """)
1104 # Test RFC 2047 header encoding and decoding
1105 class TestRFC2047(unittest.TestCase):
1106 def test_iso_8859_1(self):
1107 eq = self.assertEqual
1108 s = '=?iso-8859-1?q?this=20is=20some=20text?='
1109 eq(Utils.decode(s), 'this is some text')
1110 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
1111 eq(Utils.decode(s), u'Keld J\xf8rn Simonsen')
1112 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
1113 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
1114 eq(Utils.decode(s), 'If you can read this you understand the example.')
1115 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
1116 eq(Utils.decode(s),
1117 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
1118 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
1119 eq(Utils.decode(s), u'this issome text')
1120 s = '=?iso-8859-1?q?this=20is_?= =?iso-8859-1?q?some=20text?='
1121 eq(Utils.decode(s), u'this is some text')
1123 def test_encode_header(self):
1124 eq = self.assertEqual
1125 s = 'this is some text'
1126 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
1127 s = 'Keld_J\xf8rn_Simonsen'
1128 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
1129 s1 = 'If you can read this yo'
1130 s2 = 'u understand the example.'
1131 eq(Utils.encode(s1, encoding='b'),
1132 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
1133 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
1134 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
1138 # Test the MIMEMessage class
1139 class TestMIMEMessage(TestEmailBase):
1140 def setUp(self):
1141 fp = openfile('msg_11.txt')
1142 try:
1143 self._text = fp.read()
1144 finally:
1145 fp.close()
1147 def test_type_error(self):
1148 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
1150 def test_valid_argument(self):
1151 eq = self.assertEqual
1152 unless = self.failUnless
1153 subject = 'A sub-message'
1154 m = Message()
1155 m['Subject'] = subject
1156 r = MIMEMessage(m)
1157 eq(r.get_type(), 'message/rfc822')
1158 payload = r.get_payload()
1159 unless(type(payload), ListType)
1160 eq(len(payload), 1)
1161 subpart = payload[0]
1162 unless(subpart is m)
1163 eq(subpart['subject'], subject)
1165 def test_bad_multipart(self):
1166 eq = self.assertEqual
1167 msg1 = Message()
1168 msg1['Subject'] = 'subpart 1'
1169 msg2 = Message()
1170 msg2['Subject'] = 'subpart 2'
1171 r = MIMEMessage(msg1)
1172 self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
1174 def test_generate(self):
1175 # First craft the message to be encapsulated
1176 m = Message()
1177 m['Subject'] = 'An enclosed message'
1178 m.set_payload('Here is the body of the message.\n')
1179 r = MIMEMessage(m)
1180 r['Subject'] = 'The enclosing message'
1181 s = StringIO()
1182 g = Generator(s)
1183 g.flatten(r)
1184 self.assertEqual(s.getvalue(), """\
1185 Content-Type: message/rfc822
1186 MIME-Version: 1.0
1187 Subject: The enclosing message
1189 Subject: An enclosed message
1191 Here is the body of the message.
1192 """)
1194 def test_parse_message_rfc822(self):
1195 eq = self.assertEqual
1196 unless = self.failUnless
1197 msg = self._msgobj('msg_11.txt')
1198 eq(msg.get_type(), 'message/rfc822')
1199 payload = msg.get_payload()
1200 unless(isinstance(payload, ListType))
1201 eq(len(payload), 1)
1202 submsg = payload[0]
1203 self.failUnless(isinstance(submsg, Message))
1204 eq(submsg['subject'], 'An enclosed message')
1205 eq(submsg.get_payload(), 'Here is the body of the message.\n')
1207 def test_dsn(self):
1208 eq = self.assertEqual
1209 unless = self.failUnless
1210 # msg 16 is a Delivery Status Notification, see RFC 1894
1211 msg = self._msgobj('msg_16.txt')
1212 eq(msg.get_type(), 'multipart/report')
1213 unless(msg.is_multipart())
1214 eq(len(msg.get_payload()), 3)
1215 # Subpart 1 is a text/plain, human readable section
1216 subpart = msg.get_payload(0)
1217 eq(subpart.get_type(), 'text/plain')
1218 eq(subpart.get_payload(), """\
1219 This report relates to a message you sent with the following header fields:
1221 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1222 Date: Sun, 23 Sep 2001 20:10:55 -0700
1223 From: "Ian T. Henry" <henryi@oxy.edu>
1224 To: SoCal Raves <scr@socal-raves.org>
1225 Subject: [scr] yeah for Ians!!
1227 Your message cannot be delivered to the following recipients:
1229 Recipient address: jangel1@cougar.noc.ucla.edu
1230 Reason: recipient reached disk quota
1232 """)
1233 # Subpart 2 contains the machine parsable DSN information. It
1234 # consists of two blocks of headers, represented by two nested Message
1235 # objects.
1236 subpart = msg.get_payload(1)
1237 eq(subpart.get_type(), 'message/delivery-status')
1238 eq(len(subpart.get_payload()), 2)
1239 # message/delivery-status should treat each block as a bunch of
1240 # headers, i.e. a bunch of Message objects.
1241 dsn1 = subpart.get_payload(0)
1242 unless(isinstance(dsn1, Message))
1243 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1244 eq(dsn1.get_param('dns', header='reporting-mta'), '')
1245 # Try a missing one <wink>
1246 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1247 dsn2 = subpart.get_payload(1)
1248 unless(isinstance(dsn2, Message))
1249 eq(dsn2['action'], 'failed')
1250 eq(dsn2.get_params(header='original-recipient'),
1251 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1252 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1253 # Subpart 3 is the original message
1254 subpart = msg.get_payload(2)
1255 eq(subpart.get_type(), 'message/rfc822')
1256 payload = subpart.get_payload()
1257 unless(isinstance(payload, ListType))
1258 eq(len(payload), 1)
1259 subsubpart = payload[0]
1260 unless(isinstance(subsubpart, Message))
1261 eq(subsubpart.get_type(), 'text/plain')
1262 eq(subsubpart['message-id'],
1263 '<002001c144a6$8752e060$56104586@oxy.edu>')
1265 def test_epilogue(self):
1266 fp = openfile('msg_21.txt')
1267 try:
1268 text = fp.read()
1269 finally:
1270 fp.close()
1271 msg = Message()
1272 msg['From'] = 'aperson@dom.ain'
1273 msg['To'] = 'bperson@dom.ain'
1274 msg['Subject'] = 'Test'
1275 msg.preamble = 'MIME message\n'
1276 msg.epilogue = 'End of MIME message\n'
1277 msg1 = MIMEText('One')
1278 msg2 = MIMEText('Two')
1279 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1280 msg.attach(msg1)
1281 msg.attach(msg2)
1282 sfp = StringIO()
1283 g = Generator(sfp)
1284 g.flatten(msg)
1285 self.assertEqual(sfp.getvalue(), text)
1287 def test_default_type(self):
1288 eq = self.assertEqual
1289 fp = openfile('msg_30.txt')
1290 try:
1291 msg = email.message_from_file(fp)
1292 finally:
1293 fp.close()
1294 container1 = msg.get_payload(0)
1295 eq(container1.get_default_type(), 'message/rfc822')
1296 eq(container1.get_type(), None)
1297 container2 = msg.get_payload(1)
1298 eq(container2.get_default_type(), 'message/rfc822')
1299 eq(container2.get_type(), None)
1300 container1a = container1.get_payload(0)
1301 eq(container1a.get_default_type(), 'text/plain')
1302 eq(container1a.get_type(), 'text/plain')
1303 container2a = container2.get_payload(0)
1304 eq(container2a.get_default_type(), 'text/plain')
1305 eq(container2a.get_type(), 'text/plain')
1307 def test_default_type_with_explicit_container_type(self):
1308 eq = self.assertEqual
1309 fp = openfile('msg_28.txt')
1310 try:
1311 msg = email.message_from_file(fp)
1312 finally:
1313 fp.close()
1314 container1 = msg.get_payload(0)
1315 eq(container1.get_default_type(), 'message/rfc822')
1316 eq(container1.get_type(), 'message/rfc822')
1317 container2 = msg.get_payload(1)
1318 eq(container2.get_default_type(), 'message/rfc822')
1319 eq(container2.get_type(), 'message/rfc822')
1320 container1a = container1.get_payload(0)
1321 eq(container1a.get_default_type(), 'text/plain')
1322 eq(container1a.get_type(), 'text/plain')
1323 container2a = container2.get_payload(0)
1324 eq(container2a.get_default_type(), 'text/plain')
1325 eq(container2a.get_type(), 'text/plain')
1327 def test_default_type_non_parsed(self):
1328 eq = self.assertEqual
1329 neq = self.ndiffAssertEqual
1330 # Set up container
1331 container = MIMEMultipart('digest', 'BOUNDARY')
1332 container.epilogue = '\n'
1333 # Set up subparts
1334 subpart1a = MIMEText('message 1\n')
1335 subpart2a = MIMEText('message 2\n')
1336 subpart1 = MIMEMessage(subpart1a)
1337 subpart2 = MIMEMessage(subpart2a)
1338 container.attach(subpart1)
1339 container.attach(subpart2)
1340 eq(subpart1.get_type(), 'message/rfc822')
1341 eq(subpart1.get_default_type(), 'message/rfc822')
1342 eq(subpart2.get_type(), 'message/rfc822')
1343 eq(subpart2.get_default_type(), 'message/rfc822')
1344 neq(container.as_string(0), '''\
1345 Content-Type: multipart/digest; boundary="BOUNDARY"
1346 MIME-Version: 1.0
1348 --BOUNDARY
1349 Content-Type: message/rfc822
1350 MIME-Version: 1.0
1352 Content-Type: text/plain; charset="us-ascii"
1353 MIME-Version: 1.0
1354 Content-Transfer-Encoding: 7bit
1356 message 1
1358 --BOUNDARY
1359 Content-Type: message/rfc822
1360 MIME-Version: 1.0
1362 Content-Type: text/plain; charset="us-ascii"
1363 MIME-Version: 1.0
1364 Content-Transfer-Encoding: 7bit
1366 message 2
1368 --BOUNDARY--
1369 ''')
1370 del subpart1['content-type']
1371 del subpart1['mime-version']
1372 del subpart2['content-type']
1373 del subpart2['mime-version']
1374 eq(subpart1.get_type(), None)
1375 eq(subpart1.get_default_type(), 'message/rfc822')
1376 eq(subpart2.get_type(), None)
1377 eq(subpart2.get_default_type(), 'message/rfc822')
1378 neq(container.as_string(0), '''\
1379 Content-Type: multipart/digest; boundary="BOUNDARY"
1380 MIME-Version: 1.0
1382 --BOUNDARY
1384 Content-Type: text/plain; charset="us-ascii"
1385 MIME-Version: 1.0
1386 Content-Transfer-Encoding: 7bit
1388 message 1
1390 --BOUNDARY
1392 Content-Type: text/plain; charset="us-ascii"
1393 MIME-Version: 1.0
1394 Content-Transfer-Encoding: 7bit
1396 message 2
1398 --BOUNDARY--
1399 ''')
1403 # A general test of parser->model->generator idempotency. IOW, read a message
1404 # in, parse it into a message object tree, then without touching the tree,
1405 # regenerate the plain text. The original text and the transformed text
1406 # should be identical. Note: that we ignore the Unix-From since that may
1407 # contain a changed date.
1408 class TestIdempotent(TestEmailBase):
1409 def _msgobj(self, filename):
1410 fp = openfile(filename)
1411 try:
1412 data = fp.read()
1413 finally:
1414 fp.close()
1415 msg = email.message_from_string(data)
1416 return msg, data
1418 def _idempotent(self, msg, text):
1419 eq = self.ndiffAssertEqual
1420 s = StringIO()
1421 g = Generator(s, maxheaderlen=0)
1422 g.flatten(msg)
1423 eq(text, s.getvalue())
1425 def test_parse_text_message(self):
1426 eq = self.assertEquals
1427 msg, text = self._msgobj('msg_01.txt')
1428 eq(msg.get_type(), 'text/plain')
1429 eq(msg.get_main_type(), 'text')
1430 eq(msg.get_subtype(), 'plain')
1431 eq(msg.get_params()[1], ('charset', 'us-ascii'))
1432 eq(msg.get_param('charset'), 'us-ascii')
1433 eq(msg.preamble, None)
1434 eq(msg.epilogue, None)
1435 self._idempotent(msg, text)
1437 def test_parse_untyped_message(self):
1438 eq = self.assertEquals
1439 msg, text = self._msgobj('msg_03.txt')
1440 eq(msg.get_type(), None)
1441 eq(msg.get_params(), None)
1442 eq(msg.get_param('charset'), None)
1443 self._idempotent(msg, text)
1445 def test_simple_multipart(self):
1446 msg, text = self._msgobj('msg_04.txt')
1447 self._idempotent(msg, text)
1449 def test_MIME_digest(self):
1450 msg, text = self._msgobj('msg_02.txt')
1451 self._idempotent(msg, text)
1453 def test_long_header(self):
1454 msg, text = self._msgobj('msg_27.txt')
1455 self._idempotent(msg, text)
1457 def test_MIME_digest_with_part_headers(self):
1458 msg, text = self._msgobj('msg_28.txt')
1459 self._idempotent(msg, text)
1461 def test_mixed_with_image(self):
1462 msg, text = self._msgobj('msg_06.txt')
1463 self._idempotent(msg, text)
1465 def test_multipart_report(self):
1466 msg, text = self._msgobj('msg_05.txt')
1467 self._idempotent(msg, text)
1469 def test_dsn(self):
1470 msg, text = self._msgobj('msg_16.txt')
1471 self._idempotent(msg, text)
1473 def test_preamble_epilogue(self):
1474 msg, text = self._msgobj('msg_21.txt')
1475 self._idempotent(msg, text)
1477 def test_multipart_one_part(self):
1478 msg, text = self._msgobj('msg_23.txt')
1479 self._idempotent(msg, text)
1481 def test_multipart_no_parts(self):
1482 msg, text = self._msgobj('msg_24.txt')
1483 self._idempotent(msg, text)
1485 def test_no_start_boundary(self):
1486 msg, text = self._msgobj('msg_31.txt')
1487 self._idempotent(msg, text)
1489 def test_rfc2231_charset(self):
1490 msg, text = self._msgobj('msg_32.txt')
1491 self._idempotent(msg, text)
1493 def test_more_rfc2231_parameters(self):
1494 msg, text = self._msgobj('msg_33.txt')
1495 self._idempotent(msg, text)
1497 def test_content_type(self):
1498 eq = self.assertEquals
1499 unless = self.failUnless
1500 # Get a message object and reset the seek pointer for other tests
1501 msg, text = self._msgobj('msg_05.txt')
1502 eq(msg.get_type(), 'multipart/report')
1503 # Test the Content-Type: parameters
1504 params = {}
1505 for pk, pv in msg.get_params():
1506 params[pk] = pv
1507 eq(params['report-type'], 'delivery-status')
1508 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
1509 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
1510 eq(msg.epilogue, '\n\n')
1511 eq(len(msg.get_payload()), 3)
1512 # Make sure the subparts are what we expect
1513 msg1 = msg.get_payload(0)
1514 eq(msg1.get_type(), 'text/plain')
1515 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1516 msg2 = msg.get_payload(1)
1517 eq(msg2.get_type(), None)
1518 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1519 msg3 = msg.get_payload(2)
1520 eq(msg3.get_type(), 'message/rfc822')
1521 self.failUnless(isinstance(msg3, Message))
1522 payload = msg3.get_payload()
1523 unless(isinstance(payload, ListType))
1524 eq(len(payload), 1)
1525 msg4 = payload[0]
1526 unless(isinstance(msg4, Message))
1527 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1529 def test_parser(self):
1530 eq = self.assertEquals
1531 unless = self.failUnless
1532 msg, text = self._msgobj('msg_06.txt')
1533 # Check some of the outer headers
1534 eq(msg.get_type(), 'message/rfc822')
1535 # Make sure the payload is a list of exactly one sub-Message, and that
1536 # that submessage has a type of text/plain
1537 payload = msg.get_payload()
1538 unless(isinstance(payload, ListType))
1539 eq(len(payload), 1)
1540 msg1 = payload[0]
1541 self.failUnless(isinstance(msg1, Message))
1542 eq(msg1.get_type(), 'text/plain')
1543 self.failUnless(isinstance(msg1.get_payload(), StringType))
1544 eq(msg1.get_payload(), '\n')
1548 # Test various other bits of the package's functionality
1549 class TestMiscellaneous(unittest.TestCase):
1550 def test_message_from_string(self):
1551 fp = openfile('msg_01.txt')
1552 try:
1553 text = fp.read()
1554 finally:
1555 fp.close()
1556 msg = email.message_from_string(text)
1557 s = StringIO()
1558 # Don't wrap/continue long headers since we're trying to test
1559 # idempotency.
1560 g = Generator(s, maxheaderlen=0)
1561 g.flatten(msg)
1562 self.assertEqual(text, s.getvalue())
1564 def test_message_from_file(self):
1565 fp = openfile('msg_01.txt')
1566 try:
1567 text = fp.read()
1568 fp.seek(0)
1569 msg = email.message_from_file(fp)
1570 s = StringIO()
1571 # Don't wrap/continue long headers since we're trying to test
1572 # idempotency.
1573 g = Generator(s, maxheaderlen=0)
1574 g.flatten(msg)
1575 self.assertEqual(text, s.getvalue())
1576 finally:
1577 fp.close()
1579 def test_message_from_string_with_class(self):
1580 unless = self.failUnless
1581 fp = openfile('msg_01.txt')
1582 try:
1583 text = fp.read()
1584 finally:
1585 fp.close()
1586 # Create a subclass
1587 class MyMessage(Message):
1588 pass
1590 msg = email.message_from_string(text, MyMessage)
1591 unless(isinstance(msg, MyMessage))
1592 # Try something more complicated
1593 fp = openfile('msg_02.txt')
1594 try:
1595 text = fp.read()
1596 finally:
1597 fp.close()
1598 msg = email.message_from_string(text, MyMessage)
1599 for subpart in msg.walk():
1600 unless(isinstance(subpart, MyMessage))
1602 def test_message_from_file_with_class(self):
1603 unless = self.failUnless
1604 # Create a subclass
1605 class MyMessage(Message):
1606 pass
1608 fp = openfile('msg_01.txt')
1609 try:
1610 msg = email.message_from_file(fp, MyMessage)
1611 finally:
1612 fp.close()
1613 unless(isinstance(msg, MyMessage))
1614 # Try something more complicated
1615 fp = openfile('msg_02.txt')
1616 try:
1617 msg = email.message_from_file(fp, MyMessage)
1618 finally:
1619 fp.close()
1620 for subpart in msg.walk():
1621 unless(isinstance(subpart, MyMessage))
1623 def test__all__(self):
1624 module = __import__('email')
1625 all = module.__all__
1626 all.sort()
1627 self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
1628 'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
1629 'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
1630 'MIMENonMultipart', 'MIMEText', 'Message',
1631 'Parser', 'Utils', 'base64MIME',
1632 'message_from_file', 'message_from_string',
1633 'quopriMIME'])
1635 def test_formatdate(self):
1636 now = time.time()
1637 self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
1638 time.gmtime(now)[:6])
1640 def test_formatdate_localtime(self):
1641 now = time.time()
1642 self.assertEqual(
1643 Utils.parsedate(Utils.formatdate(now, localtime=1))[:6],
1644 time.localtime(now)[:6])
1646 def test_parsedate_none(self):
1647 self.assertEqual(Utils.parsedate(''), None)
1649 def test_parseaddr_empty(self):
1650 self.assertEqual(Utils.parseaddr('<>'), ('', ''))
1651 self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
1653 def test_noquote_dump(self):
1654 self.assertEqual(
1655 Utils.formataddr(('A Silly Person', 'person@dom.ain')),
1656 'A Silly Person <person@dom.ain>')
1658 def test_escape_dump(self):
1659 self.assertEqual(
1660 Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
1661 r'"A \(Very\) Silly Person" <person@dom.ain>')
1662 a = r'A \(Special\) Person'
1663 b = 'person@dom.ain'
1664 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1666 def test_quote_dump(self):
1667 self.assertEqual(
1668 Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
1669 r'"A Silly; Person" <person@dom.ain>')
1671 def test_fix_eols(self):
1672 eq = self.assertEqual
1673 eq(Utils.fix_eols('hello'), 'hello')
1674 eq(Utils.fix_eols('hello\n'), 'hello\r\n')
1675 eq(Utils.fix_eols('hello\r'), 'hello\r\n')
1676 eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
1677 eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
1679 def test_charset_richcomparisons(self):
1680 eq = self.assertEqual
1681 ne = self.failIfEqual
1682 cset1 = Charset()
1683 cset2 = Charset()
1684 eq(cset1, 'us-ascii')
1685 eq(cset1, 'US-ASCII')
1686 eq(cset1, 'Us-AsCiI')
1687 eq('us-ascii', cset1)
1688 eq('US-ASCII', cset1)
1689 eq('Us-AsCiI', cset1)
1690 ne(cset1, 'usascii')
1691 ne(cset1, 'USASCII')
1692 ne(cset1, 'UsAsCiI')
1693 ne('usascii', cset1)
1694 ne('USASCII', cset1)
1695 ne('UsAsCiI', cset1)
1696 eq(cset1, cset2)
1697 eq(cset2, cset1)
1699 def test_getaddresses(self):
1700 eq = self.assertEqual
1701 eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
1702 'Bud Person <bperson@dom.ain>']),
1703 [('Al Person', 'aperson@dom.ain'),
1704 ('Bud Person', 'bperson@dom.ain')])
1706 def test_utils_quote_unquote(self):
1707 eq = self.assertEqual
1708 msg = Message()
1709 msg.add_header('content-disposition', 'attachment',
1710 filename='foo\\wacky"name')
1711 eq(msg.get_filename(), 'foo\\wacky"name')
1713 def test_get_body_encoding_with_bogus_charset(self):
1714 charset = Charset('not a charset')
1715 self.assertEqual(charset.get_body_encoding(), 'base64')
1717 def test_get_body_encoding_with_uppercase_charset(self):
1718 eq = self.assertEqual
1719 msg = Message()
1720 msg['Content-Type'] = 'text/plain; charset=UTF-8'
1721 eq(msg['content-type'], 'text/plain; charset=UTF-8')
1722 charsets = msg.get_charsets()
1723 eq(len(charsets), 1)
1724 eq(charsets[0], 'utf-8')
1725 charset = Charset(charsets[0])
1726 eq(charset.get_body_encoding(), 'base64')
1727 msg.set_payload('hello world', charset=charset)
1728 eq(msg.get_payload(), 'hello world')
1729 eq(msg['content-transfer-encoding'], 'base64')
1730 # Try another one
1731 msg = Message()
1732 msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
1733 charsets = msg.get_charsets()
1734 eq(len(charsets), 1)
1735 eq(charsets[0], 'us-ascii')
1736 charset = Charset(charsets[0])
1737 eq(charset.get_body_encoding(), Encoders.encode_7or8bit)
1738 msg.set_payload('hello world', charset=charset)
1739 eq(msg.get_payload(), 'hello world')
1740 eq(msg['content-transfer-encoding'], '7bit')
1742 def test_charsets_case_insensitive(self):
1743 lc = Charset('us-ascii')
1744 uc = Charset('US-ASCII')
1745 self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
1749 # Test the iterator/generators
1750 class TestIterators(TestEmailBase):
1751 def test_body_line_iterator(self):
1752 eq = self.assertEqual
1753 # First a simple non-multipart message
1754 msg = self._msgobj('msg_01.txt')
1755 it = Iterators.body_line_iterator(msg)
1756 lines = list(it)
1757 eq(len(lines), 6)
1758 eq(EMPTYSTRING.join(lines), msg.get_payload())
1759 # Now a more complicated multipart
1760 msg = self._msgobj('msg_02.txt')
1761 it = Iterators.body_line_iterator(msg)
1762 lines = list(it)
1763 eq(len(lines), 43)
1764 fp = openfile('msg_19.txt')
1765 try:
1766 eq(EMPTYSTRING.join(lines), fp.read())
1767 finally:
1768 fp.close()
1770 def test_typed_subpart_iterator(self):
1771 eq = self.assertEqual
1772 msg = self._msgobj('msg_04.txt')
1773 it = Iterators.typed_subpart_iterator(msg, 'text')
1774 lines = []
1775 subparts = 0
1776 for subpart in it:
1777 subparts += 1
1778 lines.append(subpart.get_payload())
1779 eq(subparts, 2)
1780 eq(EMPTYSTRING.join(lines), """\
1781 a simple kind of mirror
1782 to reflect upon our own
1783 a simple kind of mirror
1784 to reflect upon our own
1785 """)
1787 def test_typed_subpart_iterator_default_type(self):
1788 eq = self.assertEqual
1789 msg = self._msgobj('msg_03.txt')
1790 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
1791 lines = []
1792 subparts = 0
1793 for subpart in it:
1794 subparts += 1
1795 lines.append(subpart.get_payload())
1796 eq(subparts, 1)
1797 eq(EMPTYSTRING.join(lines), """\
1801 Do you like this message?
1804 """)
1808 class TestParsers(TestEmailBase):
1809 def test_header_parser(self):
1810 eq = self.assertEqual
1811 # Parse only the headers of a complex multipart MIME document
1812 fp = openfile('msg_02.txt')
1813 try:
1814 msg = HeaderParser().parse(fp)
1815 finally:
1816 fp.close()
1817 eq(msg['from'], 'ppp-request@zzz.org')
1818 eq(msg['to'], 'ppp@zzz.org')
1819 eq(msg.get_type(), 'multipart/mixed')
1820 eq(msg.is_multipart(), 0)
1821 self.failUnless(isinstance(msg.get_payload(), StringType))
1823 def test_whitespace_continuaton(self):
1824 eq = self.assertEqual
1825 # This message contains a line after the Subject: header that has only
1826 # whitespace, but it is not empty!
1827 msg = email.message_from_string("""\
1828 From: aperson@dom.ain
1829 To: bperson@dom.ain
1830 Subject: the next line has a space on it
1831 \x20
1832 Date: Mon, 8 Apr 2002 15:09:19 -0400
1833 Message-ID: spam
1835 Here's the message body
1836 """)
1837 eq(msg['subject'], 'the next line has a space on it\n ')
1838 eq(msg['message-id'], 'spam')
1839 eq(msg.get_payload(), "Here's the message body\n")
1841 def test_crlf_separation(self):
1842 eq = self.assertEqual
1843 fp = openfile('msg_26.txt')
1844 try:
1845 msg = Parser().parse(fp)
1846 finally:
1847 fp.close()
1848 eq(len(msg.get_payload()), 2)
1849 part1 = msg.get_payload(0)
1850 eq(part1.get_type(), 'text/plain')
1851 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
1852 part2 = msg.get_payload(1)
1853 eq(part2.get_type(), 'application/riscos')
1855 def test_multipart_digest_with_extra_mime_headers(self):
1856 eq = self.assertEqual
1857 neq = self.ndiffAssertEqual
1858 fp = openfile('msg_28.txt')
1859 try:
1860 msg = email.message_from_file(fp)
1861 finally:
1862 fp.close()
1863 # Structure is:
1864 # multipart/digest
1865 # message/rfc822
1866 # text/plain
1867 # message/rfc822
1868 # text/plain
1869 eq(msg.is_multipart(), 1)
1870 eq(len(msg.get_payload()), 2)
1871 part1 = msg.get_payload(0)
1872 eq(part1.get_type(), 'message/rfc822')
1873 eq(part1.is_multipart(), 1)
1874 eq(len(part1.get_payload()), 1)
1875 part1a = part1.get_payload(0)
1876 eq(part1a.is_multipart(), 0)
1877 eq(part1a.get_type(), 'text/plain')
1878 neq(part1a.get_payload(), 'message 1\n')
1879 # next message/rfc822
1880 part2 = msg.get_payload(1)
1881 eq(part2.get_type(), 'message/rfc822')
1882 eq(part2.is_multipart(), 1)
1883 eq(len(part2.get_payload()), 1)
1884 part2a = part2.get_payload(0)
1885 eq(part2a.is_multipart(), 0)
1886 eq(part2a.get_type(), 'text/plain')
1887 neq(part2a.get_payload(), 'message 2\n')
1889 def test_three_lines(self):
1890 # A bug report by Andrew McNamara
1891 lines = ['From: Andrew Person <aperson@dom.ain',
1892 'Subject: Test',
1893 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
1894 msg = email.message_from_string(NL.join(lines))
1895 self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
1899 class TestBase64(unittest.TestCase):
1900 def test_len(self):
1901 eq = self.assertEqual
1902 eq(base64MIME.base64_len('hello'),
1903 len(base64MIME.encode('hello', eol='')))
1904 for size in range(15):
1905 if size == 0 : bsize = 0
1906 elif size <= 3 : bsize = 4
1907 elif size <= 6 : bsize = 8
1908 elif size <= 9 : bsize = 12
1909 elif size <= 12: bsize = 16
1910 else : bsize = 20
1911 eq(base64MIME.base64_len('x'*size), bsize)
1913 def test_decode(self):
1914 eq = self.assertEqual
1915 eq(base64MIME.decode(''), '')
1916 eq(base64MIME.decode('aGVsbG8='), 'hello')
1917 eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
1918 eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
1920 def test_encode(self):
1921 eq = self.assertEqual
1922 eq(base64MIME.encode(''), '')
1923 eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
1924 # Test the binary flag
1925 eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
1926 eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
1927 # Test the maxlinelen arg
1928 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
1929 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1930 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1931 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1932 eHh4eCB4eHh4IA==
1933 """)
1934 # Test the eol argument
1935 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1936 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1937 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1938 eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1939 eHh4eCB4eHh4IA==\r
1940 """)
1942 def test_header_encode(self):
1943 eq = self.assertEqual
1944 he = base64MIME.header_encode
1945 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
1946 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
1947 # Test the charset option
1948 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
1949 # Test the keep_eols flag
1950 eq(he('hello\nworld', keep_eols=1),
1951 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
1952 # Test the maxlinelen argument
1953 eq(he('xxxx ' * 20, maxlinelen=40), """\
1954 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
1955 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
1956 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
1957 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
1958 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
1959 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1960 # Test the eol argument
1961 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1962 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
1963 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
1964 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
1965 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
1966 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
1967 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1971 class TestQuopri(unittest.TestCase):
1972 def setUp(self):
1973 self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
1974 [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
1975 [chr(x) for x in range(ord('0'), ord('9')+1)] + \
1976 ['!', '*', '+', '-', '/', ' ']
1977 self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
1978 assert len(self.hlit) + len(self.hnon) == 256
1979 self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
1980 self.blit.remove('=')
1981 self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
1982 assert len(self.blit) + len(self.bnon) == 256
1984 def test_header_quopri_check(self):
1985 for c in self.hlit:
1986 self.failIf(quopriMIME.header_quopri_check(c))
1987 for c in self.hnon:
1988 self.failUnless(quopriMIME.header_quopri_check(c))
1990 def test_body_quopri_check(self):
1991 for c in self.blit:
1992 self.failIf(quopriMIME.body_quopri_check(c))
1993 for c in self.bnon:
1994 self.failUnless(quopriMIME.body_quopri_check(c))
1996 def test_header_quopri_len(self):
1997 eq = self.assertEqual
1998 hql = quopriMIME.header_quopri_len
1999 enc = quopriMIME.header_encode
2000 for s in ('hello', 'h@e@l@l@o@'):
2001 # Empty charset and no line-endings. 7 == RFC chrome
2002 eq(hql(s), len(enc(s, charset='', eol=''))-7)
2003 for c in self.hlit:
2004 eq(hql(c), 1)
2005 for c in self.hnon:
2006 eq(hql(c), 3)
2008 def test_body_quopri_len(self):
2009 eq = self.assertEqual
2010 bql = quopriMIME.body_quopri_len
2011 for c in self.blit:
2012 eq(bql(c), 1)
2013 for c in self.bnon:
2014 eq(bql(c), 3)
2016 def test_quote_unquote_idempotent(self):
2017 for x in range(256):
2018 c = chr(x)
2019 self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
2021 def test_header_encode(self):
2022 eq = self.assertEqual
2023 he = quopriMIME.header_encode
2024 eq(he('hello'), '=?iso-8859-1?q?hello?=')
2025 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
2026 # Test the charset option
2027 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
2028 # Test the keep_eols flag
2029 eq(he('hello\nworld', keep_eols=1), '=?iso-8859-1?q?hello=0Aworld?=')
2030 # Test a non-ASCII character
2031 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
2032 # Test the maxlinelen argument
2033 eq(he('xxxx ' * 20, maxlinelen=40), """\
2034 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
2035 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
2036 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
2037 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
2038 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2039 # Test the eol argument
2040 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2041 =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
2042 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
2043 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
2044 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
2045 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2047 def test_decode(self):
2048 eq = self.assertEqual
2049 eq(quopriMIME.decode(''), '')
2050 eq(quopriMIME.decode('hello'), 'hello')
2051 eq(quopriMIME.decode('hello', 'X'), 'hello')
2052 eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
2054 def test_encode(self):
2055 eq = self.assertEqual
2056 eq(quopriMIME.encode(''), '')
2057 eq(quopriMIME.encode('hello'), 'hello')
2058 # Test the binary flag
2059 eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
2060 eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
2061 # Test the maxlinelen arg
2062 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
2063 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
2064 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
2065 x xxxx xxxx xxxx xxxx=20""")
2066 # Test the eol argument
2067 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2068 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
2069 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
2070 x xxxx xxxx xxxx xxxx=20""")
2071 eq(quopriMIME.encode("""\
2072 one line
2074 two line"""), """\
2075 one line
2077 two line""")
2081 # Test the Charset class
2082 class TestCharset(unittest.TestCase):
2083 def test_idempotent(self):
2084 eq = self.assertEqual
2085 # Make sure us-ascii = no Unicode conversion
2086 c = Charset('us-ascii')
2087 s = 'Hello World!'
2088 sp = c.to_splittable(s)
2089 eq(s, c.from_splittable(sp))
2090 # test 8-bit idempotency with us-ascii
2091 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
2092 sp = c.to_splittable(s)
2093 eq(s, c.from_splittable(sp))
2097 # Test multilingual MIME headers.
2098 class TestHeader(TestEmailBase):
2099 def test_simple(self):
2100 eq = self.ndiffAssertEqual
2101 h = Header('Hello World!')
2102 eq(h.encode(), 'Hello World!')
2103 h.append(' Goodbye World!')
2104 eq(h.encode(), 'Hello World! Goodbye World!')
2106 def test_simple_surprise(self):
2107 eq = self.ndiffAssertEqual
2108 h = Header('Hello World!')
2109 eq(h.encode(), 'Hello World!')
2110 h.append('Goodbye World!')
2111 eq(h.encode(), 'Hello World!Goodbye World!')
2113 def test_header_needs_no_decoding(self):
2114 h = 'no decoding needed'
2115 self.assertEqual(decode_header(h), [(h, None)])
2117 def test_long(self):
2118 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.",
2119 maxlinelen=76)
2120 for l in h.encode().split('\n '):
2121 self.failUnless(len(l) <= 76)
2123 def test_multilingual(self):
2124 eq = self.ndiffAssertEqual
2125 g = Charset("iso-8859-1")
2126 cz = Charset("iso-8859-2")
2127 utf8 = Charset("utf-8")
2128 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. "
2129 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
2130 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")
2131 h = Header(g_head, g)
2132 h.append(cz_head, cz)
2133 h.append(utf8_head, utf8)
2134 enc = h.encode()
2135 eq(enc, """=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
2136 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
2137 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
2138 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
2139 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
2140 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
2141 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
2142 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
2143 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
2144 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
2145 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
2146 =?utf-8?q?s_Nunstuck_git_und?=
2147 =?utf-8?q?_Slotermeyer=3F_Ja!_Beiherhund_das_Ode?=
2148 =?utf-8?q?r_die_Flipperwaldt?=
2149 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
2150 eq(decode_header(enc),
2151 [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
2152 (utf8_head, "utf-8")])
2153 # Test for conversion to unicode. BAW: Python 2.1 doesn't support the
2154 # __unicode__() protocol, so do things this way for compatibility.
2155 ustr = h.__unicode__()
2156 # For Python 2.2 and beyond
2157 #ustr = unicode(h)
2158 eq(ustr.encode('utf-8'),
2159 'Die Mieter treten hier ein werden mit einem Foerderband '
2160 'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
2161 'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
2162 'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
2163 'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
2164 '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
2165 '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
2166 '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
2167 '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
2168 '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
2169 '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
2170 '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
2171 '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
2172 'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
2173 'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
2174 '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
2175 # Test make_header()
2176 newh = make_header(decode_header(enc))
2177 eq(newh, enc)
2179 def test_header_ctor_default_args(self):
2180 eq = self.ndiffAssertEqual
2181 h = Header()
2182 eq(h, '')
2183 h.append('foo', Charset('iso-8859-1'))
2184 eq(h, '=?iso-8859-1?q?foo?=')
2186 def test_explicit_maxlinelen(self):
2187 eq = self.ndiffAssertEqual
2188 hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
2189 h = Header(hstr)
2190 eq(h.encode(), '''\
2191 A very long line that must get split to something other than at the 76th
2192 character boundary to test the non-default behavior''')
2193 h = Header(hstr, header_name='Subject')
2194 eq(h.encode(), '''\
2195 A very long line that must get split to something other than at the
2196 76th character boundary to test the non-default behavior''')
2197 h = Header(hstr, maxlinelen=1024, header_name='Subject')
2198 eq(h.encode(), hstr)
2200 def test_us_ascii_header(self):
2201 eq = self.assertEqual
2202 s = 'hello'
2203 x = decode_header(s)
2204 eq(x, [('hello', None)])
2205 h = make_header(x)
2206 eq(s, h.encode())
2208 def test_string_charset(self):
2209 eq = self.assertEqual
2210 h = Header()
2211 h.append('hello', 'iso-8859-1')
2212 eq(h, '=?iso-8859-1?q?hello?=')
2214 ## def test_unicode_error(self):
2215 ## raises = self.assertRaises
2216 ## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
2217 ## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
2218 ## h = Header()
2219 ## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
2220 ## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
2221 ## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
2223 def test_utf8_shortest(self):
2224 eq = self.assertEqual
2225 h = Header(u'p\xf6stal', 'utf-8')
2226 eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
2227 h = Header(u'\u83ca\u5730\u6642\u592b', 'utf-8')
2228 eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
2232 # Test RFC 2231 header parameters (en/de)coding
2233 class TestRFC2231(TestEmailBase):
2234 def test_get_param(self):
2235 eq = self.assertEqual
2236 msg = self._msgobj('msg_29.txt')
2237 eq(msg.get_param('title'),
2238 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2239 eq(msg.get_param('title', unquote=0),
2240 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
2242 def test_set_param(self):
2243 eq = self.assertEqual
2244 msg = Message()
2245 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2246 charset='us-ascii')
2247 eq(msg.get_param('title'),
2248 ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
2249 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2250 charset='us-ascii', language='en')
2251 eq(msg.get_param('title'),
2252 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2253 msg = self._msgobj('msg_01.txt')
2254 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2255 charset='us-ascii', language='en')
2256 eq(msg.as_string(), """\
2257 Return-Path: <bbb@zzz.org>
2258 Delivered-To: bbb@zzz.org
2259 Received: by mail.zzz.org (Postfix, from userid 889)
2260 \tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
2261 MIME-Version: 1.0
2262 Content-Transfer-Encoding: 7bit
2263 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2264 From: bbb@ddd.com (John X. Doe)
2265 To: bbb@zzz.org
2266 Subject: This is a test message
2267 Date: Fri, 4 May 2001 14:05:44 -0400
2268 Content-Type: text/plain; charset=us-ascii;
2269 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2274 Do you like this message?
2277 """)
2279 def test_del_param(self):
2280 eq = self.ndiffAssertEqual
2281 msg = self._msgobj('msg_01.txt')
2282 msg.set_param('foo', 'bar', charset='us-ascii', language='en')
2283 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2284 charset='us-ascii', language='en')
2285 msg.del_param('foo', header='Content-Type')
2286 eq(msg.as_string(), """\
2287 Return-Path: <bbb@zzz.org>
2288 Delivered-To: bbb@zzz.org
2289 Received: by mail.zzz.org (Postfix, from userid 889)
2290 \tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
2291 MIME-Version: 1.0
2292 Content-Transfer-Encoding: 7bit
2293 Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2294 From: bbb@ddd.com (John X. Doe)
2295 To: bbb@zzz.org
2296 Subject: This is a test message
2297 Date: Fri, 4 May 2001 14:05:44 -0400
2298 Content-Type: text/plain; charset="us-ascii";
2299 \ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2304 Do you like this message?
2307 """)
2309 def test_rfc2231_get_content_charset(self):
2310 eq = self.assertEqual
2311 msg = self._msgobj('msg_32.txt')
2312 eq(msg.get_content_charset(), 'us-ascii')
2316 def _testclasses():
2317 mod = sys.modules[__name__]
2318 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
2321 def suite():
2322 suite = unittest.TestSuite()
2323 for testclass in _testclasses():
2324 suite.addTest(unittest.makeSuite(testclass))
2325 return suite
2328 def test_main():
2329 for testclass in _testclasses():
2330 run_unittest(testclass)
2334 if __name__ == '__main__':
2335 unittest.main(defaultTest='suite')