blob: d02f255d34b91d55e0f92b20320ddb3cec404eee [file] [log] [blame]
Barry Warsaw3d597812003-01-02 22:48:36 +00001# Copyright (C) 2001,2002,2003 Python Software Foundation
Barry Warsaw190390b2002-07-19 22:31:10 +00002# email package unit tests
3
Barry Warsaw190390b2002-07-19 22:31:10 +00004import os
Barry Warsaw10627ba2003-03-06 05:41:07 +00005import sys
Barry Warsaw190390b2002-07-19 22:31:10 +00006import time
Barry Warsaw190390b2002-07-19 22:31:10 +00007import base64
8import difflib
Barry Warsaw10627ba2003-03-06 05:41:07 +00009import unittest
10import warnings
Barry Warsaw190390b2002-07-19 22:31:10 +000011from cStringIO import StringIO
12from types import StringType, ListType
Barry Warsaw190390b2002-07-19 22:31:10 +000013
14import email
15
16from email.Charset import Charset
17from email.Header import Header, decode_header, make_header
18from email.Parser import Parser, HeaderParser
19from email.Generator import Generator, DecodedGenerator
20from email.Message import Message
21from email.MIMEAudio import MIMEAudio
22from email.MIMEText import MIMEText
23from email.MIMEImage import MIMEImage
24from email.MIMEBase import MIMEBase
25from email.MIMEMessage import MIMEMessage
26from email.MIMEMultipart import MIMEMultipart
27from email import Utils
28from email import Errors
29from email import Encoders
30from email import Iterators
31from email import base64MIME
32from email import quopriMIME
33
34from test.test_support import findfile, run_unittest
Barry Warsaw04f357c2002-07-23 19:04:11 +000035from email.test import __file__ as landmark
Barry Warsaw190390b2002-07-19 22:31:10 +000036
37
38NL = '\n'
39EMPTYSTRING = ''
40SPACE = ' '
41
42# We don't care about DeprecationWarnings
43warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
44
Barry Warsawa0a00762002-11-05 21:36:17 +000045try:
46 True, False
47except NameError:
48 True = 1
49 False = 0
50
Barry Warsaw190390b2002-07-19 22:31:10 +000051
52
Barry Warsaw3d597812003-01-02 22:48:36 +000053def openfile(filename, mode='r'):
Barry Warsaw04f357c2002-07-23 19:04:11 +000054 path = os.path.join(os.path.dirname(landmark), 'data', filename)
Barry Warsaw3d597812003-01-02 22:48:36 +000055 return open(path, mode)
Barry Warsaw190390b2002-07-19 22:31:10 +000056
57
58
59# Base test class
60class TestEmailBase(unittest.TestCase):
61 if hasattr(difflib, 'ndiff'):
62 # Python 2.2 and beyond
63 def ndiffAssertEqual(self, first, second):
64 """Like failUnlessEqual except use ndiff for readable output."""
65 if first <> second:
66 sfirst = str(first)
67 ssecond = str(second)
68 diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
69 fp = StringIO()
70 print >> fp, NL, NL.join(diff)
71 raise self.failureException, fp.getvalue()
72 else:
73 # Python 2.1
74 ndiffAssertEqual = unittest.TestCase.assertEqual
75
Barry Warsawa0a00762002-11-05 21:36:17 +000076 def _msgobj(self, filename, strict=False):
Barry Warsaw190390b2002-07-19 22:31:10 +000077 fp = openfile(findfile(filename))
78 try:
Barry Warsawa0a00762002-11-05 21:36:17 +000079 msg = email.message_from_file(fp, strict=strict)
Barry Warsaw190390b2002-07-19 22:31:10 +000080 finally:
81 fp.close()
82 return msg
83
84
85
86# Test various aspects of the Message class's API
87class TestMessageAPI(TestEmailBase):
88 def test_get_all(self):
89 eq = self.assertEqual
90 msg = self._msgobj('msg_20.txt')
91 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
92 eq(msg.get_all('xx', 'n/a'), 'n/a')
93
94 def test_getset_charset(self):
95 eq = self.assertEqual
96 msg = Message()
97 eq(msg.get_charset(), None)
98 charset = Charset('iso-8859-1')
99 msg.set_charset(charset)
100 eq(msg['mime-version'], '1.0')
101 eq(msg.get_type(), 'text/plain')
102 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
103 eq(msg.get_param('charset'), 'iso-8859-1')
104 eq(msg['content-transfer-encoding'], 'quoted-printable')
105 eq(msg.get_charset().input_charset, 'iso-8859-1')
106 # Remove the charset
107 msg.set_charset(None)
108 eq(msg.get_charset(), None)
109 eq(msg['content-type'], 'text/plain')
110 # Try adding a charset when there's already MIME headers present
111 msg = Message()
112 msg['MIME-Version'] = '2.0'
113 msg['Content-Type'] = 'text/x-weird'
114 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
115 msg.set_charset(charset)
116 eq(msg['mime-version'], '2.0')
117 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
118 eq(msg['content-transfer-encoding'], 'quinted-puntable')
119
120 def test_set_charset_from_string(self):
121 eq = self.assertEqual
122 msg = Message()
123 msg.set_charset('us-ascii')
124 eq(msg.get_charset().input_charset, 'us-ascii')
125 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
126
127 def test_set_payload_with_charset(self):
128 msg = Message()
129 charset = Charset('iso-8859-1')
130 msg.set_payload('This is a string payload', charset)
131 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
132
133 def test_get_charsets(self):
134 eq = self.assertEqual
135
136 msg = self._msgobj('msg_08.txt')
137 charsets = msg.get_charsets()
138 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
139
140 msg = self._msgobj('msg_09.txt')
141 charsets = msg.get_charsets('dingbat')
142 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
143 'koi8-r'])
144
145 msg = self._msgobj('msg_12.txt')
146 charsets = msg.get_charsets()
147 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
148 'iso-8859-3', 'us-ascii', 'koi8-r'])
149
150 def test_get_filename(self):
151 eq = self.assertEqual
152
153 msg = self._msgobj('msg_04.txt')
154 filenames = [p.get_filename() for p in msg.get_payload()]
155 eq(filenames, ['msg.txt', 'msg.txt'])
156
157 msg = self._msgobj('msg_07.txt')
158 subpart = msg.get_payload(1)
159 eq(subpart.get_filename(), 'dingusfish.gif')
160
161 def test_get_boundary(self):
162 eq = self.assertEqual
163 msg = self._msgobj('msg_07.txt')
164 # No quotes!
165 eq(msg.get_boundary(), 'BOUNDARY')
166
167 def test_set_boundary(self):
168 eq = self.assertEqual
169 # This one has no existing boundary parameter, but the Content-Type:
170 # header appears fifth.
171 msg = self._msgobj('msg_01.txt')
172 msg.set_boundary('BOUNDARY')
173 header, value = msg.items()[4]
174 eq(header.lower(), 'content-type')
175 eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
176 # This one has a Content-Type: header, with a boundary, stuck in the
177 # middle of its headers. Make sure the order is preserved; it should
178 # be fifth.
179 msg = self._msgobj('msg_04.txt')
180 msg.set_boundary('BOUNDARY')
181 header, value = msg.items()[4]
182 eq(header.lower(), 'content-type')
183 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
184 # And this one has no Content-Type: header at all.
185 msg = self._msgobj('msg_03.txt')
186 self.assertRaises(Errors.HeaderParseError,
187 msg.set_boundary, 'BOUNDARY')
188
189 def test_get_decoded_payload(self):
190 eq = self.assertEqual
191 msg = self._msgobj('msg_10.txt')
192 # The outer message is a multipart
Barry Warsawa0a00762002-11-05 21:36:17 +0000193 eq(msg.get_payload(decode=True), None)
Barry Warsaw190390b2002-07-19 22:31:10 +0000194 # Subpart 1 is 7bit encoded
Barry Warsawa0a00762002-11-05 21:36:17 +0000195 eq(msg.get_payload(0).get_payload(decode=True),
Barry Warsaw190390b2002-07-19 22:31:10 +0000196 'This is a 7bit encoded message.\n')
197 # Subpart 2 is quopri
Barry Warsawa0a00762002-11-05 21:36:17 +0000198 eq(msg.get_payload(1).get_payload(decode=True),
Barry Warsaw190390b2002-07-19 22:31:10 +0000199 '\xa1This is a Quoted Printable encoded message!\n')
200 # Subpart 3 is base64
Barry Warsawa0a00762002-11-05 21:36:17 +0000201 eq(msg.get_payload(2).get_payload(decode=True),
Barry Warsaw190390b2002-07-19 22:31:10 +0000202 'This is a Base64 encoded message.')
203 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsawa0a00762002-11-05 21:36:17 +0000204 eq(msg.get_payload(3).get_payload(decode=True),
Barry Warsaw190390b2002-07-19 22:31:10 +0000205 'This has no Content-Transfer-Encoding: header.\n')
206
Barry Warsaw3840b492003-03-11 04:31:37 +0000207 def test_get_decoded_uu_payload(self):
208 eq = self.assertEqual
209 msg = Message()
210 msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
211 for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
212 msg['content-transfer-encoding'] = cte
213 eq(msg.get_payload(decode=True), 'hello world')
214 # Now try some bogus data
215 msg.set_payload('foo')
216 eq(msg.get_payload(decode=True), 'foo')
217
Barry Warsaw190390b2002-07-19 22:31:10 +0000218 def test_decoded_generator(self):
219 eq = self.assertEqual
220 msg = self._msgobj('msg_07.txt')
221 fp = openfile('msg_17.txt')
222 try:
223 text = fp.read()
224 finally:
225 fp.close()
226 s = StringIO()
227 g = DecodedGenerator(s)
228 g.flatten(msg)
229 eq(s.getvalue(), text)
230
231 def test__contains__(self):
232 msg = Message()
233 msg['From'] = 'Me'
234 msg['to'] = 'You'
235 # Check for case insensitivity
236 self.failUnless('from' in msg)
237 self.failUnless('From' in msg)
238 self.failUnless('FROM' in msg)
239 self.failUnless('to' in msg)
240 self.failUnless('To' in msg)
241 self.failUnless('TO' in msg)
242
243 def test_as_string(self):
244 eq = self.assertEqual
245 msg = self._msgobj('msg_01.txt')
246 fp = openfile('msg_01.txt')
247 try:
248 text = fp.read()
249 finally:
250 fp.close()
251 eq(text, msg.as_string())
252 fullrepr = str(msg)
253 lines = fullrepr.split('\n')
254 self.failUnless(lines[0].startswith('From '))
255 eq(text, NL.join(lines[1:]))
256
257 def test_bad_param(self):
258 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
259 self.assertEqual(msg.get_param('baz'), '')
260
261 def test_missing_filename(self):
262 msg = email.message_from_string("From: foo\n")
263 self.assertEqual(msg.get_filename(), None)
264
265 def test_bogus_filename(self):
266 msg = email.message_from_string(
267 "Content-Disposition: blarg; filename\n")
268 self.assertEqual(msg.get_filename(), '')
269
270 def test_missing_boundary(self):
271 msg = email.message_from_string("From: foo\n")
272 self.assertEqual(msg.get_boundary(), None)
273
274 def test_get_params(self):
275 eq = self.assertEqual
276 msg = email.message_from_string(
277 'X-Header: foo=one; bar=two; baz=three\n')
278 eq(msg.get_params(header='x-header'),
279 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
280 msg = email.message_from_string(
281 'X-Header: foo; bar=one; baz=two\n')
282 eq(msg.get_params(header='x-header'),
283 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
284 eq(msg.get_params(), None)
285 msg = email.message_from_string(
286 'X-Header: foo; bar="one"; baz=two\n')
287 eq(msg.get_params(header='x-header'),
288 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
289
290 def test_get_param_liberal(self):
291 msg = Message()
292 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
293 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
294
295 def test_get_param(self):
296 eq = self.assertEqual
297 msg = email.message_from_string(
298 "X-Header: foo=one; bar=two; baz=three\n")
299 eq(msg.get_param('bar', header='x-header'), 'two')
300 eq(msg.get_param('quuz', header='x-header'), None)
301 eq(msg.get_param('quuz'), None)
302 msg = email.message_from_string(
303 'X-Header: foo; bar="one"; baz=two\n')
304 eq(msg.get_param('foo', header='x-header'), '')
305 eq(msg.get_param('bar', header='x-header'), 'one')
306 eq(msg.get_param('baz', header='x-header'), 'two')
307 # XXX: We are not RFC-2045 compliant! We cannot parse:
308 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
309 # msg.get_param("weird")
310 # yet.
311
312 def test_get_param_funky_continuation_lines(self):
313 msg = self._msgobj('msg_22.txt')
314 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
315
Barry Warsaw2e31ce22003-09-03 04:10:52 +0000316 def test_get_param_with_semis_in_quotes(self):
317 msg = email.message_from_string(
318 'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
319 self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
320 self.assertEqual(msg.get_param('name', unquote=False),
321 '"Jim&amp;&amp;Jill"')
322
Barry Warsaw190390b2002-07-19 22:31:10 +0000323 def test_has_key(self):
324 msg = email.message_from_string('Header: exists')
325 self.failUnless(msg.has_key('header'))
326 self.failUnless(msg.has_key('Header'))
327 self.failUnless(msg.has_key('HEADER'))
328 self.failIf(msg.has_key('headeri'))
329
330 def test_set_param(self):
331 eq = self.assertEqual
332 msg = Message()
333 msg.set_param('charset', 'iso-2022-jp')
334 eq(msg.get_param('charset'), 'iso-2022-jp')
335 msg.set_param('importance', 'high value')
336 eq(msg.get_param('importance'), 'high value')
Barry Warsawa0a00762002-11-05 21:36:17 +0000337 eq(msg.get_param('importance', unquote=False), '"high value"')
Barry Warsaw190390b2002-07-19 22:31:10 +0000338 eq(msg.get_params(), [('text/plain', ''),
339 ('charset', 'iso-2022-jp'),
340 ('importance', 'high value')])
Barry Warsawa0a00762002-11-05 21:36:17 +0000341 eq(msg.get_params(unquote=False), [('text/plain', ''),
Barry Warsaw190390b2002-07-19 22:31:10 +0000342 ('charset', '"iso-2022-jp"'),
343 ('importance', '"high value"')])
344 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
345 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
346
347 def test_del_param(self):
348 eq = self.assertEqual
349 msg = self._msgobj('msg_05.txt')
350 eq(msg.get_params(),
351 [('multipart/report', ''), ('report-type', 'delivery-status'),
352 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
353 old_val = msg.get_param("report-type")
354 msg.del_param("report-type")
355 eq(msg.get_params(),
356 [('multipart/report', ''),
357 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
358 msg.set_param("report-type", old_val)
359 eq(msg.get_params(),
360 [('multipart/report', ''),
361 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
362 ('report-type', old_val)])
363
364 def test_set_type(self):
365 eq = self.assertEqual
366 msg = Message()
367 self.assertRaises(ValueError, msg.set_type, 'text')
368 msg.set_type('text/plain')
369 eq(msg['content-type'], 'text/plain')
370 msg.set_param('charset', 'us-ascii')
371 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
372 msg.set_type('text/html')
373 eq(msg['content-type'], 'text/html; charset="us-ascii"')
374
375 def test_get_content_type_missing(self):
376 msg = Message()
377 self.assertEqual(msg.get_content_type(), 'text/plain')
378
379 def test_get_content_type_missing_with_default_type(self):
380 msg = Message()
381 msg.set_default_type('message/rfc822')
382 self.assertEqual(msg.get_content_type(), 'message/rfc822')
383
384 def test_get_content_type_from_message_implicit(self):
385 msg = self._msgobj('msg_30.txt')
386 self.assertEqual(msg.get_payload(0).get_content_type(),
387 'message/rfc822')
388
389 def test_get_content_type_from_message_explicit(self):
390 msg = self._msgobj('msg_28.txt')
391 self.assertEqual(msg.get_payload(0).get_content_type(),
392 'message/rfc822')
393
394 def test_get_content_type_from_message_text_plain_implicit(self):
395 msg = self._msgobj('msg_03.txt')
396 self.assertEqual(msg.get_content_type(), 'text/plain')
397
398 def test_get_content_type_from_message_text_plain_explicit(self):
399 msg = self._msgobj('msg_01.txt')
400 self.assertEqual(msg.get_content_type(), 'text/plain')
401
402 def test_get_content_maintype_missing(self):
403 msg = Message()
404 self.assertEqual(msg.get_content_maintype(), 'text')
405
406 def test_get_content_maintype_missing_with_default_type(self):
407 msg = Message()
408 msg.set_default_type('message/rfc822')
409 self.assertEqual(msg.get_content_maintype(), 'message')
410
411 def test_get_content_maintype_from_message_implicit(self):
412 msg = self._msgobj('msg_30.txt')
413 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
414
415 def test_get_content_maintype_from_message_explicit(self):
416 msg = self._msgobj('msg_28.txt')
417 self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
418
419 def test_get_content_maintype_from_message_text_plain_implicit(self):
420 msg = self._msgobj('msg_03.txt')
421 self.assertEqual(msg.get_content_maintype(), 'text')
422
423 def test_get_content_maintype_from_message_text_plain_explicit(self):
424 msg = self._msgobj('msg_01.txt')
425 self.assertEqual(msg.get_content_maintype(), 'text')
426
427 def test_get_content_subtype_missing(self):
428 msg = Message()
429 self.assertEqual(msg.get_content_subtype(), 'plain')
430
431 def test_get_content_subtype_missing_with_default_type(self):
432 msg = Message()
433 msg.set_default_type('message/rfc822')
434 self.assertEqual(msg.get_content_subtype(), 'rfc822')
435
436 def test_get_content_subtype_from_message_implicit(self):
437 msg = self._msgobj('msg_30.txt')
438 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
439
440 def test_get_content_subtype_from_message_explicit(self):
441 msg = self._msgobj('msg_28.txt')
442 self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
443
444 def test_get_content_subtype_from_message_text_plain_implicit(self):
445 msg = self._msgobj('msg_03.txt')
446 self.assertEqual(msg.get_content_subtype(), 'plain')
447
448 def test_get_content_subtype_from_message_text_plain_explicit(self):
449 msg = self._msgobj('msg_01.txt')
450 self.assertEqual(msg.get_content_subtype(), 'plain')
451
452 def test_get_content_maintype_error(self):
453 msg = Message()
454 msg['Content-Type'] = 'no-slash-in-this-string'
Barry Warsaw33281362002-08-20 14:51:10 +0000455 self.assertEqual(msg.get_content_maintype(), 'text')
Barry Warsaw190390b2002-07-19 22:31:10 +0000456
457 def test_get_content_subtype_error(self):
458 msg = Message()
459 msg['Content-Type'] = 'no-slash-in-this-string'
Barry Warsaw33281362002-08-20 14:51:10 +0000460 self.assertEqual(msg.get_content_subtype(), 'plain')
Barry Warsaw190390b2002-07-19 22:31:10 +0000461
Barry Warsaw58fb61c2002-09-06 03:39:59 +0000462 def test_replace_header(self):
463 eq = self.assertEqual
464 msg = Message()
465 msg.add_header('First', 'One')
466 msg.add_header('Second', 'Two')
467 msg.add_header('Third', 'Three')
468 eq(msg.keys(), ['First', 'Second', 'Third'])
469 eq(msg.values(), ['One', 'Two', 'Three'])
470 msg.replace_header('Second', 'Twenty')
471 eq(msg.keys(), ['First', 'Second', 'Third'])
472 eq(msg.values(), ['One', 'Twenty', 'Three'])
473 msg.add_header('First', 'Eleven')
474 msg.replace_header('First', 'One Hundred')
475 eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
476 eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
477 self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
478
Barry Warsaw3efb6512003-03-10 16:09:51 +0000479 def test_broken_base64_payload(self):
480 x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
481 msg = Message()
482 msg['content-type'] = 'audio/x-midi'
483 msg['content-transfer-encoding'] = 'base64'
484 msg.set_payload(x)
485 self.assertEqual(msg.get_payload(decode=True), x)
486
Barry Warsaw190390b2002-07-19 22:31:10 +0000487
488
489# Test the email.Encoders module
490class TestEncoders(unittest.TestCase):
491 def test_encode_noop(self):
492 eq = self.assertEqual
493 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsawbd757ba2003-03-11 05:04:54 +0000494 eq(msg.get_payload(), 'hello world')
Barry Warsaw190390b2002-07-19 22:31:10 +0000495
496 def test_encode_7bit(self):
497 eq = self.assertEqual
498 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsawbd757ba2003-03-11 05:04:54 +0000499 eq(msg.get_payload(), 'hello world')
Barry Warsaw190390b2002-07-19 22:31:10 +0000500 eq(msg['content-transfer-encoding'], '7bit')
501 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsawbd757ba2003-03-11 05:04:54 +0000502 eq(msg.get_payload(), 'hello \x7f world')
Barry Warsaw190390b2002-07-19 22:31:10 +0000503 eq(msg['content-transfer-encoding'], '7bit')
504
505 def test_encode_8bit(self):
506 eq = self.assertEqual
507 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsawbd757ba2003-03-11 05:04:54 +0000508 eq(msg.get_payload(), 'hello \x80 world')
Barry Warsaw190390b2002-07-19 22:31:10 +0000509 eq(msg['content-transfer-encoding'], '8bit')
510
511 def test_encode_empty_payload(self):
512 eq = self.assertEqual
513 msg = Message()
514 msg.set_charset('us-ascii')
515 eq(msg['content-transfer-encoding'], '7bit')
516
517 def test_encode_base64(self):
518 eq = self.assertEqual
519 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsawbd757ba2003-03-11 05:04:54 +0000520 eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=')
Barry Warsaw190390b2002-07-19 22:31:10 +0000521 eq(msg['content-transfer-encoding'], 'base64')
522
523 def test_encode_quoted_printable(self):
524 eq = self.assertEqual
525 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsawbd757ba2003-03-11 05:04:54 +0000526 eq(msg.get_payload(), 'hello=20world')
Barry Warsaw190390b2002-07-19 22:31:10 +0000527 eq(msg['content-transfer-encoding'], 'quoted-printable')
528
529 def test_default_cte(self):
530 eq = self.assertEqual
531 msg = MIMEText('hello world')
532 eq(msg['content-transfer-encoding'], '7bit')
533
534 def test_default_cte(self):
535 eq = self.assertEqual
536 # With no explicit _charset its us-ascii, and all are 7-bit
537 msg = MIMEText('hello world')
538 eq(msg['content-transfer-encoding'], '7bit')
539 # Similar, but with 8-bit data
540 msg = MIMEText('hello \xf8 world')
541 eq(msg['content-transfer-encoding'], '8bit')
542 # And now with a different charset
543 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
544 eq(msg['content-transfer-encoding'], 'quoted-printable')
545
546
547
548# Test long header wrapping
549class TestLongHeaders(TestEmailBase):
550 def test_split_long_continuation(self):
551 eq = self.ndiffAssertEqual
552 msg = email.message_from_string("""\
553Subject: bug demonstration
554\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
555\tmore text
556
557test
558""")
559 sfp = StringIO()
560 g = Generator(sfp)
561 g.flatten(msg)
562 eq(sfp.getvalue(), """\
563Subject: bug demonstration
564\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
565\tmore text
566
567test
568""")
569
570 def test_another_long_almost_unsplittable_header(self):
571 eq = self.ndiffAssertEqual
572 hstr = """\
573bug demonstration
574\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
575\tmore text"""
576 h = Header(hstr, continuation_ws='\t')
577 eq(h.encode(), """\
578bug demonstration
579\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
580\tmore text""")
581 h = Header(hstr)
582 eq(h.encode(), """\
583bug demonstration
584 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
585 more text""")
586
587 def test_long_nonstring(self):
588 eq = self.ndiffAssertEqual
589 g = Charset("iso-8859-1")
590 cz = Charset("iso-8859-2")
591 utf8 = Charset("utf-8")
592 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. "
593 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
594 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")
Barry Warsaw10627ba2003-03-06 05:41:07 +0000595 h = Header(g_head, g, header_name='Subject')
Barry Warsaw190390b2002-07-19 22:31:10 +0000596 h.append(cz_head, cz)
597 h.append(utf8_head, utf8)
598 msg = Message()
599 msg['Subject'] = h
600 sfp = StringIO()
601 g = Generator(sfp)
602 g.flatten(msg)
Barry Warsaw10627ba2003-03-06 05:41:07 +0000603 eq(sfp.getvalue(), """\
604Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
605 =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
606 =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
607 =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
608 =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
609 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
610 =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
611 =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
612 =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
613 =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
614 =?utf-8?b?44Gm44GE44G+44GZ44CC?=
Barry Warsaw190390b2002-07-19 22:31:10 +0000615
Barry Warsaw10627ba2003-03-06 05:41:07 +0000616""")
617 eq(h.encode(), """\
618=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
619 =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
620 =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
621 =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
622 =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
623 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
624 =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
625 =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
626 =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
627 =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
628 =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
Barry Warsaw190390b2002-07-19 22:31:10 +0000629
630 def test_long_header_encode(self):
631 eq = self.ndiffAssertEqual
632 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
633 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
634 header_name='X-Foobar-Spoink-Defrobnit')
635 eq(h.encode(), '''\
636wasnipoop; giraffes="very-long-necked-animals";
637 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
638
639 def test_long_header_encode_with_tab_continuation(self):
640 eq = self.ndiffAssertEqual
641 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
642 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
643 header_name='X-Foobar-Spoink-Defrobnit',
644 continuation_ws='\t')
645 eq(h.encode(), '''\
646wasnipoop; giraffes="very-long-necked-animals";
647\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
648
649 def test_header_splitter(self):
650 eq = self.ndiffAssertEqual
651 msg = MIMEText('')
652 # It'd be great if we could use add_header() here, but that doesn't
653 # guarantee an order of the parameters.
654 msg['X-Foobar-Spoink-Defrobnit'] = (
655 'wasnipoop; giraffes="very-long-necked-animals"; '
656 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
657 sfp = StringIO()
658 g = Generator(sfp)
659 g.flatten(msg)
660 eq(sfp.getvalue(), '''\
661Content-Type: text/plain; charset="us-ascii"
662MIME-Version: 1.0
663Content-Transfer-Encoding: 7bit
664X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
665\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
666
667''')
668
669 def test_no_semis_header_splitter(self):
670 eq = self.ndiffAssertEqual
671 msg = Message()
672 msg['From'] = 'test@dom.ain'
673 msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
674 msg.set_payload('Test')
675 sfp = StringIO()
676 g = Generator(sfp)
677 g.flatten(msg)
678 eq(sfp.getvalue(), """\
679From: test@dom.ain
680References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
681\t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
682
683Test""")
684
685 def test_no_split_long_header(self):
686 eq = self.ndiffAssertEqual
687 hstr = 'References: ' + 'x' * 80
688 h = Header(hstr, continuation_ws='\t')
689 eq(h.encode(), """\
690References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
691
692 def test_splitting_multiple_long_lines(self):
693 eq = self.ndiffAssertEqual
694 hstr = """\
695from 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)
696\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)
697\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)
698"""
699 h = Header(hstr, continuation_ws='\t')
700 eq(h.encode(), """\
701from babylon.socal-raves.org (localhost [127.0.0.1]);
702\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
703\tfor <mailman-admin@babylon.socal-raves.org>;
704\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
705\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
706\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
707\tfor <mailman-admin@babylon.socal-raves.org>;
708\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
709\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
710\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
711\tfor <mailman-admin@babylon.socal-raves.org>;
712\tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
713
Barry Warsaw24d45df2002-09-10 15:46:44 +0000714 def test_splitting_first_line_only_is_long(self):
715 eq = self.ndiffAssertEqual
716 hstr = """\
717from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
718\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
719\tid 17k4h5-00034i-00
720\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
721 h = Header(hstr, maxlinelen=78, header_name='Received',
722 continuation_ws='\t')
723 eq(h.encode(), """\
724from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
725\thelo=cthulhu.gerg.ca)
726\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
727\tid 17k4h5-00034i-00
728\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
729
Barry Warsawa74771c2002-10-14 15:26:17 +0000730 def test_long_8bit_header(self):
731 eq = self.ndiffAssertEqual
732 msg = Message()
Barry Warsaw10627ba2003-03-06 05:41:07 +0000733 h = Header('Britische Regierung gibt', 'iso-8859-1',
734 header_name='Subject')
Barry Warsawa74771c2002-10-14 15:26:17 +0000735 h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
736 msg['Subject'] = h
737 eq(msg.as_string(), """\
Barry Warsaw10627ba2003-03-06 05:41:07 +0000738Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
739 =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
Barry Warsawa74771c2002-10-14 15:26:17 +0000740
741""")
742
743 def test_long_8bit_header_no_charset(self):
744 eq = self.ndiffAssertEqual
745 msg = Message()
746 msg['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
747 eq(msg.as_string(), """\
748Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
749
750""")
751
Barry Warsaw10627ba2003-03-06 05:41:07 +0000752 def test_long_to_header(self):
753 eq = self.ndiffAssertEqual
754 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>'
755 msg = Message()
756 msg['To'] = to
757 eq(msg.as_string(0), '''\
758To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
759\t"Someone Test #B" <someone@umich.edu>,
760\t"Someone Test #C" <someone@eecs.umich.edu>,
761\t"Someone Test #D" <someone@eecs.umich.edu>
762
763''')
764
765 def test_long_line_after_append(self):
766 eq = self.ndiffAssertEqual
767 s = 'This is an example of string which has almost the limit of header length.'
768 h = Header(s)
769 h.append('Add another line.')
770 eq(h.encode(), """\
771This is an example of string which has almost the limit of header length.
772 Add another line.""")
773
774 def test_shorter_line_with_append(self):
775 eq = self.ndiffAssertEqual
776 s = 'This is a shorter line.'
777 h = Header(s)
778 h.append('Add another sentence. (Surprise?)')
779 eq(h.encode(),
780 'This is a shorter line. Add another sentence. (Surprise?)')
781
782 def test_long_field_name(self):
783 eq = self.ndiffAssertEqual
784 fn = 'X-Very-Very-Very-Long-Header-Name'
785 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. "
786 h = Header(gs, 'iso-8859-1', header_name=fn)
787 # BAW: this seems broken because the first line is too long
788 eq(h.encode(), """\
789=?iso-8859-1?q?Die_Mieter_treten_hier_?=
790 =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
791 =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
792 =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
793
Barry Warsawf0d35852003-03-06 20:31:02 +0000794 def test_long_received_header(self):
795 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'
796 msg = Message()
797 msg['Received-1'] = Header(h, continuation_ws='\t')
798 msg['Received-2'] = h
799 self.assertEqual(msg.as_string(), """\
800Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
Barry Warsawbf7e2412003-03-07 15:58:51 +0000801\throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
802\tWed, 05 Mar 2003 18:10:18 -0700
Barry Warsawf0d35852003-03-06 20:31:02 +0000803Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
Barry Warsawbf7e2412003-03-07 15:58:51 +0000804\throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
805\tWed, 05 Mar 2003 18:10:18 -0700
Barry Warsawf0d35852003-03-06 20:31:02 +0000806
807""")
808
Barry Warsaw82783e62003-03-07 15:35:47 +0000809 def test_string_headerinst_eq(self):
810 h = '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
811 msg = Message()
812 msg['Received-1'] = Header(h, header_name='Received-1',
813 continuation_ws='\t')
814 msg['Received-2'] = h
815 self.assertEqual(msg.as_string(), """\
816Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
Barry Warsawbf7e2412003-03-07 15:58:51 +0000817\t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
Barry Warsaw82783e62003-03-07 15:35:47 +0000818Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
Barry Warsawbf7e2412003-03-07 15:58:51 +0000819\t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
Barry Warsaw82783e62003-03-07 15:35:47 +0000820
821""")
822
Barry Warsaw28ffcef2003-03-07 23:23:04 +0000823 def test_long_unbreakable_lines_with_continuation(self):
824 eq = self.ndiffAssertEqual
825 msg = Message()
826 t = """\
827 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
828 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
829 msg['Face-1'] = t
830 msg['Face-2'] = Header(t, header_name='Face-2')
831 eq(msg.as_string(), """\
832Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
833\tlocQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
834Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
835 locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
836
837""")
838
Barry Warsaw79637632003-03-10 15:11:29 +0000839 def test_another_long_multiline_header(self):
840 eq = self.ndiffAssertEqual
841 m = '''\
842Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
Barry Warsaw0822ff72003-04-24 15:58:47 +0000843\tWed, 16 Oct 2002 07:41:11 -0700'''
Barry Warsaw79637632003-03-10 15:11:29 +0000844 msg = email.message_from_string(m)
845 eq(msg.as_string(), '''\
846Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
Barry Warsaw0822ff72003-04-24 15:58:47 +0000847\tMicrosoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
Barry Warsaw79637632003-03-10 15:11:29 +0000848
849''')
850
Barry Warsaw24075492003-03-17 20:35:14 +0000851 def test_long_lines_with_different_header(self):
852 eq = self.ndiffAssertEqual
853 h = """\
854List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
855 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
856 msg = Message()
857 msg['List'] = h
858 msg['List'] = Header(h, header_name='List')
859 eq(msg.as_string(), """\
860List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
Barry Warsaw0822ff72003-04-24 15:58:47 +0000861\t<mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
Barry Warsaw24075492003-03-17 20:35:14 +0000862List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
863 <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
864
865""")
866
Barry Warsaw190390b2002-07-19 22:31:10 +0000867
868
869# Test mangling of "From " lines in the body of a message
870class TestFromMangling(unittest.TestCase):
871 def setUp(self):
872 self.msg = Message()
873 self.msg['From'] = 'aaa@bbb.org'
874 self.msg.set_payload("""\
875From the desk of A.A.A.:
876Blah blah blah
877""")
878
879 def test_mangled_from(self):
880 s = StringIO()
Barry Warsawa0a00762002-11-05 21:36:17 +0000881 g = Generator(s, mangle_from_=True)
Barry Warsaw190390b2002-07-19 22:31:10 +0000882 g.flatten(self.msg)
883 self.assertEqual(s.getvalue(), """\
884From: aaa@bbb.org
885
886>From the desk of A.A.A.:
887Blah blah blah
888""")
889
890 def test_dont_mangle_from(self):
891 s = StringIO()
Barry Warsawa0a00762002-11-05 21:36:17 +0000892 g = Generator(s, mangle_from_=False)
Barry Warsaw190390b2002-07-19 22:31:10 +0000893 g.flatten(self.msg)
894 self.assertEqual(s.getvalue(), """\
895From: aaa@bbb.org
896
897From the desk of A.A.A.:
898Blah blah blah
899""")
900
901
902
903# Test the basic MIMEAudio class
904class TestMIMEAudio(unittest.TestCase):
905 def setUp(self):
Barry Warsawf29ffbd2002-12-30 17:45:02 +0000906 # Make sure we pick up the audiotest.au that lives in email/test/data.
907 # In Python, there's an audiotest.au living in Lib/test but that isn't
908 # included in some binary distros that don't include the test
909 # package. The trailing empty string on the .join() is significant
910 # since findfile() will do a dirname().
911 datadir = os.path.join(os.path.dirname(landmark), 'data', '')
912 fp = open(findfile('audiotest.au', datadir), 'rb')
Barry Warsaw190390b2002-07-19 22:31:10 +0000913 try:
914 self._audiodata = fp.read()
915 finally:
916 fp.close()
917 self._au = MIMEAudio(self._audiodata)
918
919 def test_guess_minor_type(self):
920 self.assertEqual(self._au.get_type(), 'audio/basic')
921
922 def test_encoding(self):
923 payload = self._au.get_payload()
924 self.assertEqual(base64.decodestring(payload), self._audiodata)
925
926 def checkSetMinor(self):
927 au = MIMEAudio(self._audiodata, 'fish')
928 self.assertEqual(im.get_type(), 'audio/fish')
929
930 def test_custom_encoder(self):
931 eq = self.assertEqual
932 def encoder(msg):
933 orig = msg.get_payload()
934 msg.set_payload(0)
935 msg['Content-Transfer-Encoding'] = 'broken64'
936 au = MIMEAudio(self._audiodata, _encoder=encoder)
937 eq(au.get_payload(), 0)
938 eq(au['content-transfer-encoding'], 'broken64')
939
940 def test_add_header(self):
941 eq = self.assertEqual
942 unless = self.failUnless
943 self._au.add_header('Content-Disposition', 'attachment',
944 filename='audiotest.au')
945 eq(self._au['content-disposition'],
946 'attachment; filename="audiotest.au"')
947 eq(self._au.get_params(header='content-disposition'),
948 [('attachment', ''), ('filename', 'audiotest.au')])
949 eq(self._au.get_param('filename', header='content-disposition'),
950 'audiotest.au')
951 missing = []
952 eq(self._au.get_param('attachment', header='content-disposition'), '')
953 unless(self._au.get_param('foo', failobj=missing,
954 header='content-disposition') is missing)
955 # Try some missing stuff
956 unless(self._au.get_param('foobar', missing) is missing)
957 unless(self._au.get_param('attachment', missing,
958 header='foobar') is missing)
959
960
961
962# Test the basic MIMEImage class
963class TestMIMEImage(unittest.TestCase):
964 def setUp(self):
965 fp = openfile('PyBanner048.gif')
966 try:
967 self._imgdata = fp.read()
968 finally:
969 fp.close()
970 self._im = MIMEImage(self._imgdata)
971
972 def test_guess_minor_type(self):
973 self.assertEqual(self._im.get_type(), 'image/gif')
974
975 def test_encoding(self):
976 payload = self._im.get_payload()
977 self.assertEqual(base64.decodestring(payload), self._imgdata)
978
979 def checkSetMinor(self):
980 im = MIMEImage(self._imgdata, 'fish')
981 self.assertEqual(im.get_type(), 'image/fish')
982
983 def test_custom_encoder(self):
984 eq = self.assertEqual
985 def encoder(msg):
986 orig = msg.get_payload()
987 msg.set_payload(0)
988 msg['Content-Transfer-Encoding'] = 'broken64'
989 im = MIMEImage(self._imgdata, _encoder=encoder)
990 eq(im.get_payload(), 0)
991 eq(im['content-transfer-encoding'], 'broken64')
992
993 def test_add_header(self):
994 eq = self.assertEqual
995 unless = self.failUnless
996 self._im.add_header('Content-Disposition', 'attachment',
997 filename='dingusfish.gif')
998 eq(self._im['content-disposition'],
999 'attachment; filename="dingusfish.gif"')
1000 eq(self._im.get_params(header='content-disposition'),
1001 [('attachment', ''), ('filename', 'dingusfish.gif')])
1002 eq(self._im.get_param('filename', header='content-disposition'),
1003 'dingusfish.gif')
1004 missing = []
1005 eq(self._im.get_param('attachment', header='content-disposition'), '')
1006 unless(self._im.get_param('foo', failobj=missing,
1007 header='content-disposition') is missing)
1008 # Try some missing stuff
1009 unless(self._im.get_param('foobar', missing) is missing)
1010 unless(self._im.get_param('attachment', missing,
1011 header='foobar') is missing)
1012
1013
1014
1015# Test the basic MIMEText class
1016class TestMIMEText(unittest.TestCase):
1017 def setUp(self):
1018 self._msg = MIMEText('hello there')
1019
1020 def test_types(self):
1021 eq = self.assertEqual
1022 unless = self.failUnless
1023 eq(self._msg.get_type(), 'text/plain')
1024 eq(self._msg.get_param('charset'), 'us-ascii')
1025 missing = []
1026 unless(self._msg.get_param('foobar', missing) is missing)
1027 unless(self._msg.get_param('charset', missing, header='foobar')
1028 is missing)
1029
1030 def test_payload(self):
Barry Warsawbd757ba2003-03-11 05:04:54 +00001031 self.assertEqual(self._msg.get_payload(), 'hello there')
Barry Warsaw190390b2002-07-19 22:31:10 +00001032 self.failUnless(not self._msg.is_multipart())
1033
1034 def test_charset(self):
1035 eq = self.assertEqual
1036 msg = MIMEText('hello there', _charset='us-ascii')
1037 eq(msg.get_charset().input_charset, 'us-ascii')
1038 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1039
1040
1041
1042# Test a more complicated multipart/mixed type message
Barry Warsaw513af772003-03-10 17:00:43 +00001043class TestMultipartMixed(TestEmailBase):
Barry Warsaw190390b2002-07-19 22:31:10 +00001044 def setUp(self):
1045 fp = openfile('PyBanner048.gif')
1046 try:
1047 data = fp.read()
1048 finally:
1049 fp.close()
1050
1051 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
1052 image = MIMEImage(data, name='dingusfish.gif')
1053 image.add_header('content-disposition', 'attachment',
1054 filename='dingusfish.gif')
1055 intro = MIMEText('''\
1056Hi there,
1057
1058This is the dingus fish.
1059''')
1060 container.attach(intro)
1061 container.attach(image)
1062 container['From'] = 'Barry <barry@digicool.com>'
1063 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1064 container['Subject'] = 'Here is your dingus fish'
1065
1066 now = 987809702.54848599
1067 timetuple = time.localtime(now)
1068 if timetuple[-1] == 0:
1069 tzsecs = time.timezone
1070 else:
1071 tzsecs = time.altzone
1072 if tzsecs > 0:
1073 sign = '-'
1074 else:
1075 sign = '+'
1076 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
1077 container['Date'] = time.strftime(
1078 '%a, %d %b %Y %H:%M:%S',
1079 time.localtime(now)) + tzoffset
1080 self._msg = container
1081 self._im = image
1082 self._txt = intro
1083
1084 def test_hierarchy(self):
1085 # convenience
1086 eq = self.assertEqual
1087 unless = self.failUnless
1088 raises = self.assertRaises
1089 # tests
1090 m = self._msg
1091 unless(m.is_multipart())
1092 eq(m.get_type(), 'multipart/mixed')
1093 eq(len(m.get_payload()), 2)
1094 raises(IndexError, m.get_payload, 2)
1095 m0 = m.get_payload(0)
1096 m1 = m.get_payload(1)
1097 unless(m0 is self._txt)
1098 unless(m1 is self._im)
1099 eq(m.get_payload(), [m0, m1])
1100 unless(not m0.is_multipart())
1101 unless(not m1.is_multipart())
1102
1103 def test_no_parts_in_a_multipart(self):
1104 outer = MIMEBase('multipart', 'mixed')
1105 outer['Subject'] = 'A subject'
1106 outer['To'] = 'aperson@dom.ain'
1107 outer['From'] = 'bperson@dom.ain'
1108 outer.preamble = ''
1109 outer.epilogue = ''
1110 outer.set_boundary('BOUNDARY')
1111 msg = MIMEText('hello world')
1112 self.assertEqual(outer.as_string(), '''\
1113Content-Type: multipart/mixed; boundary="BOUNDARY"
1114MIME-Version: 1.0
1115Subject: A subject
1116To: aperson@dom.ain
1117From: bperson@dom.ain
1118
1119--BOUNDARY
1120
1121
1122--BOUNDARY--
1123''')
1124
1125 def test_one_part_in_a_multipart(self):
Barry Warsawe1ff4bb2003-03-10 16:59:34 +00001126 eq = self.ndiffAssertEqual
Barry Warsaw190390b2002-07-19 22:31:10 +00001127 outer = MIMEBase('multipart', 'mixed')
1128 outer['Subject'] = 'A subject'
1129 outer['To'] = 'aperson@dom.ain'
1130 outer['From'] = 'bperson@dom.ain'
1131 outer.preamble = ''
1132 outer.epilogue = ''
1133 outer.set_boundary('BOUNDARY')
1134 msg = MIMEText('hello world')
1135 outer.attach(msg)
Barry Warsawe1ff4bb2003-03-10 16:59:34 +00001136 eq(outer.as_string(), '''\
Barry Warsaw190390b2002-07-19 22:31:10 +00001137Content-Type: multipart/mixed; boundary="BOUNDARY"
1138MIME-Version: 1.0
1139Subject: A subject
1140To: aperson@dom.ain
1141From: bperson@dom.ain
1142
1143--BOUNDARY
1144Content-Type: text/plain; charset="us-ascii"
1145MIME-Version: 1.0
1146Content-Transfer-Encoding: 7bit
1147
1148hello world
Barry Warsaw190390b2002-07-19 22:31:10 +00001149--BOUNDARY--
1150''')
1151
1152 def test_seq_parts_in_a_multipart(self):
Barry Warsawe1ff4bb2003-03-10 16:59:34 +00001153 eq = self.ndiffAssertEqual
Barry Warsaw190390b2002-07-19 22:31:10 +00001154 outer = MIMEBase('multipart', 'mixed')
1155 outer['Subject'] = 'A subject'
1156 outer['To'] = 'aperson@dom.ain'
1157 outer['From'] = 'bperson@dom.ain'
1158 outer.preamble = ''
1159 outer.epilogue = ''
1160 msg = MIMEText('hello world')
1161 outer.attach(msg)
1162 outer.set_boundary('BOUNDARY')
Barry Warsawe1ff4bb2003-03-10 16:59:34 +00001163 eq(outer.as_string(), '''\
Barry Warsaw190390b2002-07-19 22:31:10 +00001164Content-Type: multipart/mixed; boundary="BOUNDARY"
1165MIME-Version: 1.0
1166Subject: A subject
1167To: aperson@dom.ain
1168From: bperson@dom.ain
1169
1170--BOUNDARY
1171Content-Type: text/plain; charset="us-ascii"
1172MIME-Version: 1.0
1173Content-Transfer-Encoding: 7bit
1174
1175hello world
Barry Warsaw190390b2002-07-19 22:31:10 +00001176--BOUNDARY--
1177''')
1178
1179
1180
1181# Test some badly formatted messages
1182class TestNonConformant(TestEmailBase):
1183 def test_parse_missing_minor_type(self):
1184 eq = self.assertEqual
1185 msg = self._msgobj('msg_14.txt')
1186 eq(msg.get_type(), 'text')
1187 eq(msg.get_main_type(), None)
1188 eq(msg.get_subtype(), None)
1189
Thomas Wouters1e130542004-03-20 20:29:50 +00001190## XXX: No longer fails with the new parser. Should it ?
1191## def test_bogus_boundary(self):
1192## fp = openfile(findfile('msg_15.txt'))
1193## try:
1194## data = fp.read()
1195## finally:
1196## fp.close()
1197## p = Parser(strict=True)
1198## # Note, under a future non-strict parsing mode, this would parse the
1199## # message into the intended message tree.
1200## self.assertRaises(Errors.BoundaryError, p.parsestr, data)
Barry Warsaw190390b2002-07-19 22:31:10 +00001201
1202 def test_multipart_no_boundary(self):
1203 fp = openfile(findfile('msg_25.txt'))
1204 try:
1205 self.assertRaises(Errors.BoundaryError,
1206 email.message_from_file, fp)
1207 finally:
1208 fp.close()
1209
Barry Warsaw33281362002-08-20 14:51:10 +00001210 def test_invalid_content_type(self):
1211 eq = self.assertEqual
1212 neq = self.ndiffAssertEqual
1213 msg = Message()
1214 # RFC 2045, $5.2 says invalid yields text/plain
1215 msg['Content-Type'] = 'text'
1216 eq(msg.get_content_maintype(), 'text')
1217 eq(msg.get_content_subtype(), 'plain')
1218 eq(msg.get_content_type(), 'text/plain')
1219 # Clear the old value and try something /really/ invalid
1220 del msg['content-type']
1221 msg['Content-Type'] = 'foo'
1222 eq(msg.get_content_maintype(), 'text')
1223 eq(msg.get_content_subtype(), 'plain')
1224 eq(msg.get_content_type(), 'text/plain')
1225 # Still, make sure that the message is idempotently generated
1226 s = StringIO()
1227 g = Generator(s)
1228 g.flatten(msg)
1229 neq(s.getvalue(), 'Content-Type: foo\n\n')
1230
Barry Warsaw24d45df2002-09-10 15:46:44 +00001231 def test_no_start_boundary(self):
1232 eq = self.ndiffAssertEqual
1233 msg = self._msgobj('msg_31.txt')
1234 eq(msg.get_payload(), """\
1235--BOUNDARY
1236Content-Type: text/plain
1237
1238message 1
1239
1240--BOUNDARY
1241Content-Type: text/plain
1242
1243message 2
1244
1245--BOUNDARY--
1246""")
1247
Thomas Wouters1e130542004-03-20 20:29:50 +00001248## XXX: No longer fails with the new parser. Should it ?
1249## def test_no_separating_blank_line(self):
1250## eq = self.ndiffAssertEqual
1251## msg = self._msgobj('msg_35.txt')
1252## eq(msg.as_string(), """\
1253## From: aperson@dom.ain
1254## To: bperson@dom.ain
1255## Subject: here's something interesting
1256##
1257## counter to RFC 2822, there's no separating newline here
1258## """)
1259## # strict=True should raise an exception
1260## self.assertRaises(Errors.HeaderParseError,
1261## self._msgobj, 'msg_35.txt', True)
1262##
1263##
Barry Warsaw190390b2002-07-19 22:31:10 +00001264
1265# Test RFC 2047 header encoding and decoding
1266class TestRFC2047(unittest.TestCase):
1267 def test_iso_8859_1(self):
1268 eq = self.assertEqual
1269 s = '=?iso-8859-1?q?this=20is=20some=20text?='
1270 eq(Utils.decode(s), 'this is some text')
1271 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
1272 eq(Utils.decode(s), u'Keld J\xf8rn Simonsen')
1273 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
1274 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
1275 eq(Utils.decode(s), 'If you can read this you understand the example.')
1276 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
1277 eq(Utils.decode(s),
1278 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
1279 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
1280 eq(Utils.decode(s), u'this issome text')
1281 s = '=?iso-8859-1?q?this=20is_?= =?iso-8859-1?q?some=20text?='
1282 eq(Utils.decode(s), u'this is some text')
1283
1284 def test_encode_header(self):
1285 eq = self.assertEqual
1286 s = 'this is some text'
1287 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
1288 s = 'Keld_J\xf8rn_Simonsen'
1289 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
1290 s1 = 'If you can read this yo'
1291 s2 = 'u understand the example.'
1292 eq(Utils.encode(s1, encoding='b'),
1293 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
1294 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
1295 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
1296
Barry Warsaw28ffcb62003-03-06 06:38:29 +00001297 def test_rfc2047_multiline(self):
1298 eq = self.assertEqual
1299 s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1300 foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1301 dh = decode_header(s)
1302 eq(dh, [
1303 ('Re:', None),
1304 ('r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1305 ('baz foo bar', None),
1306 ('r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1307 eq(str(make_header(dh)),
1308 """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar
1309 =?mac-iceland?q?r=8Aksm=9Arg=8Cs?=""")
1310
Barry Warsawc79ffb02003-03-06 16:11:14 +00001311 def test_whitespace_eater_unicode(self):
1312 eq = self.assertEqual
1313 s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1314 dh = decode_header(s)
1315 eq(dh, [('Andr\xe9', 'iso-8859-1'), ('Pirard <pirard@dom.ain>', None)])
Barry Warsawea8f6fa2003-03-12 03:14:11 +00001316 # Python 2.1's unicode() builtin doesn't call the object's
1317 # __unicode__() method. Use the following alternative instead.
1318 #hu = unicode(make_header(dh)).encode('latin-1')
1319 hu = make_header(dh).__unicode__().encode('latin-1')
Barry Warsawc79ffb02003-03-06 16:11:14 +00001320 eq(hu, 'Andr\xe9 Pirard <pirard@dom.ain>')
1321
Barry Warsaw9c505ae2003-03-30 20:47:22 +00001322 def test_whitespace_eater_unicode_2(self):
1323 eq = self.assertEqual
1324 s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1325 dh = decode_header(s)
1326 eq(dh, [('The', None), ('quick brown fox', 'iso-8859-1'),
1327 ('jumped over the', None), ('lazy dog', 'iso-8859-1')])
1328 hu = make_header(dh).__unicode__()
1329 eq(hu, u'The quick brown fox jumped over the lazy dog')
1330
Barry Warsaw190390b2002-07-19 22:31:10 +00001331
1332
1333# Test the MIMEMessage class
1334class TestMIMEMessage(TestEmailBase):
1335 def setUp(self):
1336 fp = openfile('msg_11.txt')
1337 try:
1338 self._text = fp.read()
1339 finally:
1340 fp.close()
1341
1342 def test_type_error(self):
1343 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
1344
1345 def test_valid_argument(self):
1346 eq = self.assertEqual
1347 unless = self.failUnless
1348 subject = 'A sub-message'
1349 m = Message()
1350 m['Subject'] = subject
1351 r = MIMEMessage(m)
1352 eq(r.get_type(), 'message/rfc822')
1353 payload = r.get_payload()
1354 unless(type(payload), ListType)
1355 eq(len(payload), 1)
1356 subpart = payload[0]
1357 unless(subpart is m)
1358 eq(subpart['subject'], subject)
1359
1360 def test_bad_multipart(self):
1361 eq = self.assertEqual
1362 msg1 = Message()
1363 msg1['Subject'] = 'subpart 1'
1364 msg2 = Message()
1365 msg2['Subject'] = 'subpart 2'
1366 r = MIMEMessage(msg1)
1367 self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
1368
1369 def test_generate(self):
1370 # First craft the message to be encapsulated
1371 m = Message()
1372 m['Subject'] = 'An enclosed message'
1373 m.set_payload('Here is the body of the message.\n')
1374 r = MIMEMessage(m)
1375 r['Subject'] = 'The enclosing message'
1376 s = StringIO()
1377 g = Generator(s)
1378 g.flatten(r)
1379 self.assertEqual(s.getvalue(), """\
1380Content-Type: message/rfc822
1381MIME-Version: 1.0
1382Subject: The enclosing message
1383
1384Subject: An enclosed message
1385
1386Here is the body of the message.
1387""")
1388
1389 def test_parse_message_rfc822(self):
1390 eq = self.assertEqual
1391 unless = self.failUnless
1392 msg = self._msgobj('msg_11.txt')
1393 eq(msg.get_type(), 'message/rfc822')
1394 payload = msg.get_payload()
1395 unless(isinstance(payload, ListType))
1396 eq(len(payload), 1)
1397 submsg = payload[0]
1398 self.failUnless(isinstance(submsg, Message))
1399 eq(submsg['subject'], 'An enclosed message')
1400 eq(submsg.get_payload(), 'Here is the body of the message.\n')
1401
1402 def test_dsn(self):
1403 eq = self.assertEqual
1404 unless = self.failUnless
1405 # msg 16 is a Delivery Status Notification, see RFC 1894
1406 msg = self._msgobj('msg_16.txt')
1407 eq(msg.get_type(), 'multipart/report')
1408 unless(msg.is_multipart())
1409 eq(len(msg.get_payload()), 3)
1410 # Subpart 1 is a text/plain, human readable section
1411 subpart = msg.get_payload(0)
1412 eq(subpart.get_type(), 'text/plain')
1413 eq(subpart.get_payload(), """\
1414This report relates to a message you sent with the following header fields:
1415
1416 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1417 Date: Sun, 23 Sep 2001 20:10:55 -0700
1418 From: "Ian T. Henry" <henryi@oxy.edu>
1419 To: SoCal Raves <scr@socal-raves.org>
1420 Subject: [scr] yeah for Ians!!
1421
1422Your message cannot be delivered to the following recipients:
1423
1424 Recipient address: jangel1@cougar.noc.ucla.edu
1425 Reason: recipient reached disk quota
1426
1427""")
1428 # Subpart 2 contains the machine parsable DSN information. It
1429 # consists of two blocks of headers, represented by two nested Message
1430 # objects.
1431 subpart = msg.get_payload(1)
1432 eq(subpart.get_type(), 'message/delivery-status')
1433 eq(len(subpart.get_payload()), 2)
1434 # message/delivery-status should treat each block as a bunch of
1435 # headers, i.e. a bunch of Message objects.
1436 dsn1 = subpart.get_payload(0)
1437 unless(isinstance(dsn1, Message))
1438 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1439 eq(dsn1.get_param('dns', header='reporting-mta'), '')
1440 # Try a missing one <wink>
1441 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1442 dsn2 = subpart.get_payload(1)
1443 unless(isinstance(dsn2, Message))
1444 eq(dsn2['action'], 'failed')
1445 eq(dsn2.get_params(header='original-recipient'),
1446 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1447 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1448 # Subpart 3 is the original message
1449 subpart = msg.get_payload(2)
1450 eq(subpart.get_type(), 'message/rfc822')
1451 payload = subpart.get_payload()
1452 unless(isinstance(payload, ListType))
1453 eq(len(payload), 1)
1454 subsubpart = payload[0]
1455 unless(isinstance(subsubpart, Message))
1456 eq(subsubpart.get_type(), 'text/plain')
1457 eq(subsubpart['message-id'],
1458 '<002001c144a6$8752e060$56104586@oxy.edu>')
1459
1460 def test_epilogue(self):
Barry Warsawe1ff4bb2003-03-10 16:59:34 +00001461 eq = self.ndiffAssertEqual
Barry Warsaw190390b2002-07-19 22:31:10 +00001462 fp = openfile('msg_21.txt')
1463 try:
1464 text = fp.read()
1465 finally:
1466 fp.close()
1467 msg = Message()
1468 msg['From'] = 'aperson@dom.ain'
1469 msg['To'] = 'bperson@dom.ain'
1470 msg['Subject'] = 'Test'
1471 msg.preamble = 'MIME message\n'
1472 msg.epilogue = 'End of MIME message\n'
1473 msg1 = MIMEText('One')
1474 msg2 = MIMEText('Two')
1475 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1476 msg.attach(msg1)
1477 msg.attach(msg2)
1478 sfp = StringIO()
1479 g = Generator(sfp)
1480 g.flatten(msg)
Barry Warsawe1ff4bb2003-03-10 16:59:34 +00001481 eq(sfp.getvalue(), text)
Barry Warsaw190390b2002-07-19 22:31:10 +00001482
Barry Warsaw10627ba2003-03-06 05:41:07 +00001483 def test_no_nl_preamble(self):
1484 eq = self.ndiffAssertEqual
1485 msg = Message()
1486 msg['From'] = 'aperson@dom.ain'
1487 msg['To'] = 'bperson@dom.ain'
1488 msg['Subject'] = 'Test'
1489 msg.preamble = 'MIME message'
1490 msg.epilogue = ''
1491 msg1 = MIMEText('One')
1492 msg2 = MIMEText('Two')
1493 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1494 msg.attach(msg1)
1495 msg.attach(msg2)
1496 eq(msg.as_string(), """\
1497From: aperson@dom.ain
1498To: bperson@dom.ain
1499Subject: Test
1500Content-Type: multipart/mixed; boundary="BOUNDARY"
1501
1502MIME message
1503--BOUNDARY
1504Content-Type: text/plain; charset="us-ascii"
1505MIME-Version: 1.0
1506Content-Transfer-Encoding: 7bit
1507
1508One
Barry Warsaw10627ba2003-03-06 05:41:07 +00001509--BOUNDARY
1510Content-Type: text/plain; charset="us-ascii"
1511MIME-Version: 1.0
1512Content-Transfer-Encoding: 7bit
1513
1514Two
Barry Warsaw10627ba2003-03-06 05:41:07 +00001515--BOUNDARY--
1516""")
1517
Barry Warsaw190390b2002-07-19 22:31:10 +00001518 def test_default_type(self):
1519 eq = self.assertEqual
1520 fp = openfile('msg_30.txt')
1521 try:
1522 msg = email.message_from_file(fp)
1523 finally:
1524 fp.close()
1525 container1 = msg.get_payload(0)
1526 eq(container1.get_default_type(), 'message/rfc822')
1527 eq(container1.get_type(), None)
1528 container2 = msg.get_payload(1)
1529 eq(container2.get_default_type(), 'message/rfc822')
1530 eq(container2.get_type(), None)
1531 container1a = container1.get_payload(0)
1532 eq(container1a.get_default_type(), 'text/plain')
1533 eq(container1a.get_type(), 'text/plain')
1534 container2a = container2.get_payload(0)
1535 eq(container2a.get_default_type(), 'text/plain')
1536 eq(container2a.get_type(), 'text/plain')
1537
1538 def test_default_type_with_explicit_container_type(self):
1539 eq = self.assertEqual
1540 fp = openfile('msg_28.txt')
1541 try:
1542 msg = email.message_from_file(fp)
1543 finally:
1544 fp.close()
1545 container1 = msg.get_payload(0)
1546 eq(container1.get_default_type(), 'message/rfc822')
1547 eq(container1.get_type(), 'message/rfc822')
1548 container2 = msg.get_payload(1)
1549 eq(container2.get_default_type(), 'message/rfc822')
1550 eq(container2.get_type(), 'message/rfc822')
1551 container1a = container1.get_payload(0)
1552 eq(container1a.get_default_type(), 'text/plain')
1553 eq(container1a.get_type(), 'text/plain')
1554 container2a = container2.get_payload(0)
1555 eq(container2a.get_default_type(), 'text/plain')
1556 eq(container2a.get_type(), 'text/plain')
1557
1558 def test_default_type_non_parsed(self):
1559 eq = self.assertEqual
1560 neq = self.ndiffAssertEqual
1561 # Set up container
1562 container = MIMEMultipart('digest', 'BOUNDARY')
1563 container.epilogue = '\n'
1564 # Set up subparts
1565 subpart1a = MIMEText('message 1\n')
1566 subpart2a = MIMEText('message 2\n')
1567 subpart1 = MIMEMessage(subpart1a)
1568 subpart2 = MIMEMessage(subpart2a)
1569 container.attach(subpart1)
1570 container.attach(subpart2)
1571 eq(subpart1.get_type(), 'message/rfc822')
1572 eq(subpart1.get_default_type(), 'message/rfc822')
1573 eq(subpart2.get_type(), 'message/rfc822')
1574 eq(subpart2.get_default_type(), 'message/rfc822')
1575 neq(container.as_string(0), '''\
1576Content-Type: multipart/digest; boundary="BOUNDARY"
1577MIME-Version: 1.0
1578
1579--BOUNDARY
1580Content-Type: message/rfc822
1581MIME-Version: 1.0
1582
1583Content-Type: text/plain; charset="us-ascii"
1584MIME-Version: 1.0
1585Content-Transfer-Encoding: 7bit
1586
1587message 1
1588
1589--BOUNDARY
1590Content-Type: message/rfc822
1591MIME-Version: 1.0
1592
1593Content-Type: text/plain; charset="us-ascii"
1594MIME-Version: 1.0
1595Content-Transfer-Encoding: 7bit
1596
1597message 2
1598
1599--BOUNDARY--
1600''')
1601 del subpart1['content-type']
1602 del subpart1['mime-version']
1603 del subpart2['content-type']
1604 del subpart2['mime-version']
1605 eq(subpart1.get_type(), None)
1606 eq(subpart1.get_default_type(), 'message/rfc822')
1607 eq(subpart2.get_type(), None)
1608 eq(subpart2.get_default_type(), 'message/rfc822')
1609 neq(container.as_string(0), '''\
1610Content-Type: multipart/digest; boundary="BOUNDARY"
1611MIME-Version: 1.0
1612
1613--BOUNDARY
1614
1615Content-Type: text/plain; charset="us-ascii"
1616MIME-Version: 1.0
1617Content-Transfer-Encoding: 7bit
1618
1619message 1
1620
1621--BOUNDARY
1622
1623Content-Type: text/plain; charset="us-ascii"
1624MIME-Version: 1.0
1625Content-Transfer-Encoding: 7bit
1626
1627message 2
1628
1629--BOUNDARY--
1630''')
1631
1632
1633
1634# A general test of parser->model->generator idempotency. IOW, read a message
1635# in, parse it into a message object tree, then without touching the tree,
1636# regenerate the plain text. The original text and the transformed text
1637# should be identical. Note: that we ignore the Unix-From since that may
1638# contain a changed date.
1639class TestIdempotent(TestEmailBase):
1640 def _msgobj(self, filename):
1641 fp = openfile(filename)
1642 try:
1643 data = fp.read()
1644 finally:
1645 fp.close()
1646 msg = email.message_from_string(data)
1647 return msg, data
1648
1649 def _idempotent(self, msg, text):
1650 eq = self.ndiffAssertEqual
1651 s = StringIO()
1652 g = Generator(s, maxheaderlen=0)
1653 g.flatten(msg)
1654 eq(text, s.getvalue())
1655
1656 def test_parse_text_message(self):
1657 eq = self.assertEquals
1658 msg, text = self._msgobj('msg_01.txt')
1659 eq(msg.get_type(), 'text/plain')
1660 eq(msg.get_main_type(), 'text')
1661 eq(msg.get_subtype(), 'plain')
1662 eq(msg.get_params()[1], ('charset', 'us-ascii'))
1663 eq(msg.get_param('charset'), 'us-ascii')
1664 eq(msg.preamble, None)
1665 eq(msg.epilogue, None)
1666 self._idempotent(msg, text)
1667
1668 def test_parse_untyped_message(self):
1669 eq = self.assertEquals
1670 msg, text = self._msgobj('msg_03.txt')
1671 eq(msg.get_type(), None)
1672 eq(msg.get_params(), None)
1673 eq(msg.get_param('charset'), None)
1674 self._idempotent(msg, text)
1675
1676 def test_simple_multipart(self):
1677 msg, text = self._msgobj('msg_04.txt')
1678 self._idempotent(msg, text)
1679
1680 def test_MIME_digest(self):
1681 msg, text = self._msgobj('msg_02.txt')
1682 self._idempotent(msg, text)
1683
1684 def test_long_header(self):
1685 msg, text = self._msgobj('msg_27.txt')
1686 self._idempotent(msg, text)
1687
1688 def test_MIME_digest_with_part_headers(self):
1689 msg, text = self._msgobj('msg_28.txt')
1690 self._idempotent(msg, text)
1691
1692 def test_mixed_with_image(self):
1693 msg, text = self._msgobj('msg_06.txt')
1694 self._idempotent(msg, text)
1695
1696 def test_multipart_report(self):
1697 msg, text = self._msgobj('msg_05.txt')
1698 self._idempotent(msg, text)
1699
1700 def test_dsn(self):
1701 msg, text = self._msgobj('msg_16.txt')
1702 self._idempotent(msg, text)
1703
1704 def test_preamble_epilogue(self):
1705 msg, text = self._msgobj('msg_21.txt')
1706 self._idempotent(msg, text)
1707
1708 def test_multipart_one_part(self):
1709 msg, text = self._msgobj('msg_23.txt')
1710 self._idempotent(msg, text)
1711
1712 def test_multipart_no_parts(self):
1713 msg, text = self._msgobj('msg_24.txt')
1714 self._idempotent(msg, text)
1715
Barry Warsaw24d45df2002-09-10 15:46:44 +00001716 def test_no_start_boundary(self):
1717 msg, text = self._msgobj('msg_31.txt')
1718 self._idempotent(msg, text)
1719
Barry Warsaw9c745692002-09-26 17:21:02 +00001720 def test_rfc2231_charset(self):
1721 msg, text = self._msgobj('msg_32.txt')
1722 self._idempotent(msg, text)
1723
1724 def test_more_rfc2231_parameters(self):
1725 msg, text = self._msgobj('msg_33.txt')
1726 self._idempotent(msg, text)
1727
Barry Warsaw48b0a1c2002-11-05 21:04:52 +00001728 def test_text_plain_in_a_multipart_digest(self):
1729 msg, text = self._msgobj('msg_34.txt')
1730 self._idempotent(msg, text)
1731
Barry Warsaw190390b2002-07-19 22:31:10 +00001732 def test_content_type(self):
1733 eq = self.assertEquals
1734 unless = self.failUnless
1735 # Get a message object and reset the seek pointer for other tests
1736 msg, text = self._msgobj('msg_05.txt')
1737 eq(msg.get_type(), 'multipart/report')
1738 # Test the Content-Type: parameters
1739 params = {}
1740 for pk, pv in msg.get_params():
1741 params[pk] = pv
1742 eq(params['report-type'], 'delivery-status')
1743 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
1744 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
1745 eq(msg.epilogue, '\n\n')
1746 eq(len(msg.get_payload()), 3)
1747 # Make sure the subparts are what we expect
1748 msg1 = msg.get_payload(0)
1749 eq(msg1.get_type(), 'text/plain')
1750 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1751 msg2 = msg.get_payload(1)
1752 eq(msg2.get_type(), None)
1753 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1754 msg3 = msg.get_payload(2)
1755 eq(msg3.get_type(), 'message/rfc822')
1756 self.failUnless(isinstance(msg3, Message))
1757 payload = msg3.get_payload()
1758 unless(isinstance(payload, ListType))
1759 eq(len(payload), 1)
1760 msg4 = payload[0]
1761 unless(isinstance(msg4, Message))
1762 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1763
1764 def test_parser(self):
1765 eq = self.assertEquals
1766 unless = self.failUnless
1767 msg, text = self._msgobj('msg_06.txt')
1768 # Check some of the outer headers
1769 eq(msg.get_type(), 'message/rfc822')
1770 # Make sure the payload is a list of exactly one sub-Message, and that
1771 # that submessage has a type of text/plain
1772 payload = msg.get_payload()
1773 unless(isinstance(payload, ListType))
1774 eq(len(payload), 1)
1775 msg1 = payload[0]
1776 self.failUnless(isinstance(msg1, Message))
1777 eq(msg1.get_type(), 'text/plain')
1778 self.failUnless(isinstance(msg1.get_payload(), StringType))
1779 eq(msg1.get_payload(), '\n')
1780
Barry Warsaw9c745692002-09-26 17:21:02 +00001781
Barry Warsaw190390b2002-07-19 22:31:10 +00001782
1783# Test various other bits of the package's functionality
1784class TestMiscellaneous(unittest.TestCase):
1785 def test_message_from_string(self):
1786 fp = openfile('msg_01.txt')
1787 try:
1788 text = fp.read()
1789 finally:
1790 fp.close()
1791 msg = email.message_from_string(text)
1792 s = StringIO()
1793 # Don't wrap/continue long headers since we're trying to test
1794 # idempotency.
1795 g = Generator(s, maxheaderlen=0)
1796 g.flatten(msg)
1797 self.assertEqual(text, s.getvalue())
1798
1799 def test_message_from_file(self):
1800 fp = openfile('msg_01.txt')
1801 try:
1802 text = fp.read()
1803 fp.seek(0)
1804 msg = email.message_from_file(fp)
1805 s = StringIO()
1806 # Don't wrap/continue long headers since we're trying to test
1807 # idempotency.
1808 g = Generator(s, maxheaderlen=0)
1809 g.flatten(msg)
1810 self.assertEqual(text, s.getvalue())
1811 finally:
1812 fp.close()
1813
1814 def test_message_from_string_with_class(self):
1815 unless = self.failUnless
1816 fp = openfile('msg_01.txt')
1817 try:
1818 text = fp.read()
1819 finally:
1820 fp.close()
1821 # Create a subclass
1822 class MyMessage(Message):
1823 pass
1824
1825 msg = email.message_from_string(text, MyMessage)
1826 unless(isinstance(msg, MyMessage))
1827 # Try something more complicated
1828 fp = openfile('msg_02.txt')
1829 try:
1830 text = fp.read()
1831 finally:
1832 fp.close()
1833 msg = email.message_from_string(text, MyMessage)
1834 for subpart in msg.walk():
1835 unless(isinstance(subpart, MyMessage))
1836
1837 def test_message_from_file_with_class(self):
1838 unless = self.failUnless
1839 # Create a subclass
1840 class MyMessage(Message):
1841 pass
1842
1843 fp = openfile('msg_01.txt')
1844 try:
1845 msg = email.message_from_file(fp, MyMessage)
1846 finally:
1847 fp.close()
1848 unless(isinstance(msg, MyMessage))
1849 # Try something more complicated
1850 fp = openfile('msg_02.txt')
1851 try:
1852 msg = email.message_from_file(fp, MyMessage)
1853 finally:
1854 fp.close()
1855 for subpart in msg.walk():
1856 unless(isinstance(subpart, MyMessage))
1857
1858 def test__all__(self):
1859 module = __import__('email')
1860 all = module.__all__
1861 all.sort()
1862 self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
Barry Warsaw0ac885e2002-10-01 17:57:06 +00001863 'Header', 'Iterators', 'MIMEAudio', 'MIMEBase',
1864 'MIMEImage', 'MIMEMessage', 'MIMEMultipart',
1865 'MIMENonMultipart', 'MIMEText', 'Message',
1866 'Parser', 'Utils', 'base64MIME',
Barry Warsaw190390b2002-07-19 22:31:10 +00001867 'message_from_file', 'message_from_string',
1868 'quopriMIME'])
1869
1870 def test_formatdate(self):
1871 now = time.time()
1872 self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
1873 time.gmtime(now)[:6])
1874
1875 def test_formatdate_localtime(self):
1876 now = time.time()
1877 self.assertEqual(
Barry Warsawa0a00762002-11-05 21:36:17 +00001878 Utils.parsedate(Utils.formatdate(now, localtime=True))[:6],
Barry Warsaw190390b2002-07-19 22:31:10 +00001879 time.localtime(now)[:6])
1880
1881 def test_parsedate_none(self):
1882 self.assertEqual(Utils.parsedate(''), None)
1883
Barry Warsaw795833f2002-12-30 17:20:53 +00001884 def test_parsedate_compact(self):
1885 # The FWS after the comma is optional
1886 self.assertEqual(Utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
1887 Utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
1888
Barry Warsawf8b3e1f2003-05-08 03:34:01 +00001889 def test_parsedate_no_dayofweek(self):
1890 eq = self.assertEqual
1891 eq(Utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
1892 (2003, 2, 25, 13, 47, 26, 0, 0, 0, -28800))
1893
1894 def test_parsedate_compact_no_dayofweek(self):
1895 eq = self.assertEqual
1896 eq(Utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
1897 (2003, 2, 5, 13, 47, 26, 0, 0, 0, -28800))
1898
Barry Warsaw190390b2002-07-19 22:31:10 +00001899 def test_parseaddr_empty(self):
1900 self.assertEqual(Utils.parseaddr('<>'), ('', ''))
1901 self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
1902
1903 def test_noquote_dump(self):
1904 self.assertEqual(
1905 Utils.formataddr(('A Silly Person', 'person@dom.ain')),
1906 'A Silly Person <person@dom.ain>')
1907
1908 def test_escape_dump(self):
1909 self.assertEqual(
1910 Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
1911 r'"A \(Very\) Silly Person" <person@dom.ain>')
1912 a = r'A \(Special\) Person'
1913 b = 'person@dom.ain'
1914 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1915
Barry Warsawa2e64702003-03-10 19:18:34 +00001916 def test_escape_backslashes(self):
1917 self.assertEqual(
1918 Utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
1919 r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
1920 a = r'Arthur \Backslash\ Foobar'
1921 b = 'person@dom.ain'
1922 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1923
Barry Warsawedb59c12002-12-30 16:19:52 +00001924 def test_name_with_dot(self):
1925 x = 'John X. Doe <jxd@example.com>'
1926 y = '"John X. Doe" <jxd@example.com>'
1927 a, b = ('John X. Doe', 'jxd@example.com')
1928 self.assertEqual(Utils.parseaddr(x), (a, b))
1929 self.assertEqual(Utils.parseaddr(y), (a, b))
1930 # formataddr() quotes the name if there's a dot in it
1931 self.assertEqual(Utils.formataddr((a, b)), y)
1932
Barry Warsaw190390b2002-07-19 22:31:10 +00001933 def test_quote_dump(self):
1934 self.assertEqual(
1935 Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
1936 r'"A Silly; Person" <person@dom.ain>')
1937
1938 def test_fix_eols(self):
1939 eq = self.assertEqual
1940 eq(Utils.fix_eols('hello'), 'hello')
1941 eq(Utils.fix_eols('hello\n'), 'hello\r\n')
1942 eq(Utils.fix_eols('hello\r'), 'hello\r\n')
1943 eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
1944 eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
1945
1946 def test_charset_richcomparisons(self):
1947 eq = self.assertEqual
1948 ne = self.failIfEqual
1949 cset1 = Charset()
1950 cset2 = Charset()
1951 eq(cset1, 'us-ascii')
1952 eq(cset1, 'US-ASCII')
1953 eq(cset1, 'Us-AsCiI')
1954 eq('us-ascii', cset1)
1955 eq('US-ASCII', cset1)
1956 eq('Us-AsCiI', cset1)
1957 ne(cset1, 'usascii')
1958 ne(cset1, 'USASCII')
1959 ne(cset1, 'UsAsCiI')
1960 ne('usascii', cset1)
1961 ne('USASCII', cset1)
1962 ne('UsAsCiI', cset1)
1963 eq(cset1, cset2)
1964 eq(cset2, cset1)
1965
1966 def test_getaddresses(self):
1967 eq = self.assertEqual
1968 eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
1969 'Bud Person <bperson@dom.ain>']),
1970 [('Al Person', 'aperson@dom.ain'),
1971 ('Bud Person', 'bperson@dom.ain')])
1972
Barry Warsawab758402003-03-17 18:36:37 +00001973 def test_getaddresses_nasty(self):
1974 eq = self.assertEqual
1975 eq(Utils.getaddresses(['foo: ;']), [('', '')])
1976 eq(Utils.getaddresses(
1977 ['[]*-- =~$']),
1978 [('', ''), ('', ''), ('', '*--')])
1979 eq(Utils.getaddresses(
1980 ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
1981 [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
1982
Barry Warsawbc6edac2002-09-11 02:31:24 +00001983 def test_utils_quote_unquote(self):
1984 eq = self.assertEqual
1985 msg = Message()
1986 msg.add_header('content-disposition', 'attachment',
1987 filename='foo\\wacky"name')
1988 eq(msg.get_filename(), 'foo\\wacky"name')
1989
Barry Warsawdc8087b2002-10-10 15:14:22 +00001990 def test_get_body_encoding_with_bogus_charset(self):
1991 charset = Charset('not a charset')
1992 self.assertEqual(charset.get_body_encoding(), 'base64')
1993
1994 def test_get_body_encoding_with_uppercase_charset(self):
1995 eq = self.assertEqual
1996 msg = Message()
1997 msg['Content-Type'] = 'text/plain; charset=UTF-8'
1998 eq(msg['content-type'], 'text/plain; charset=UTF-8')
1999 charsets = msg.get_charsets()
2000 eq(len(charsets), 1)
2001 eq(charsets[0], 'utf-8')
2002 charset = Charset(charsets[0])
2003 eq(charset.get_body_encoding(), 'base64')
2004 msg.set_payload('hello world', charset=charset)
2005 eq(msg.get_payload(), 'hello world')
2006 eq(msg['content-transfer-encoding'], 'base64')
2007 # Try another one
2008 msg = Message()
2009 msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
2010 charsets = msg.get_charsets()
2011 eq(len(charsets), 1)
2012 eq(charsets[0], 'us-ascii')
2013 charset = Charset(charsets[0])
2014 eq(charset.get_body_encoding(), Encoders.encode_7or8bit)
2015 msg.set_payload('hello world', charset=charset)
2016 eq(msg.get_payload(), 'hello world')
2017 eq(msg['content-transfer-encoding'], '7bit')
2018
2019 def test_charsets_case_insensitive(self):
2020 lc = Charset('us-ascii')
2021 uc = Charset('US-ASCII')
2022 self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
2023
Barry Warsaw190390b2002-07-19 22:31:10 +00002024
2025
2026# Test the iterator/generators
2027class TestIterators(TestEmailBase):
2028 def test_body_line_iterator(self):
2029 eq = self.assertEqual
2030 # First a simple non-multipart message
2031 msg = self._msgobj('msg_01.txt')
2032 it = Iterators.body_line_iterator(msg)
2033 lines = list(it)
2034 eq(len(lines), 6)
2035 eq(EMPTYSTRING.join(lines), msg.get_payload())
2036 # Now a more complicated multipart
2037 msg = self._msgobj('msg_02.txt')
2038 it = Iterators.body_line_iterator(msg)
2039 lines = list(it)
2040 eq(len(lines), 43)
2041 fp = openfile('msg_19.txt')
2042 try:
2043 eq(EMPTYSTRING.join(lines), fp.read())
2044 finally:
2045 fp.close()
2046
2047 def test_typed_subpart_iterator(self):
2048 eq = self.assertEqual
2049 msg = self._msgobj('msg_04.txt')
2050 it = Iterators.typed_subpart_iterator(msg, 'text')
2051 lines = []
2052 subparts = 0
2053 for subpart in it:
2054 subparts += 1
2055 lines.append(subpart.get_payload())
2056 eq(subparts, 2)
2057 eq(EMPTYSTRING.join(lines), """\
2058a simple kind of mirror
2059to reflect upon our own
2060a simple kind of mirror
2061to reflect upon our own
2062""")
2063
2064 def test_typed_subpart_iterator_default_type(self):
2065 eq = self.assertEqual
2066 msg = self._msgobj('msg_03.txt')
2067 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
2068 lines = []
2069 subparts = 0
2070 for subpart in it:
2071 subparts += 1
2072 lines.append(subpart.get_payload())
2073 eq(subparts, 1)
2074 eq(EMPTYSTRING.join(lines), """\
2075
2076Hi,
2077
2078Do you like this message?
2079
2080-Me
2081""")
2082
2083
2084
2085class TestParsers(TestEmailBase):
2086 def test_header_parser(self):
2087 eq = self.assertEqual
2088 # Parse only the headers of a complex multipart MIME document
2089 fp = openfile('msg_02.txt')
2090 try:
2091 msg = HeaderParser().parse(fp)
2092 finally:
2093 fp.close()
2094 eq(msg['from'], 'ppp-request@zzz.org')
2095 eq(msg['to'], 'ppp@zzz.org')
2096 eq(msg.get_type(), 'multipart/mixed')
2097 eq(msg.is_multipart(), 0)
2098 self.failUnless(isinstance(msg.get_payload(), StringType))
2099
2100 def test_whitespace_continuaton(self):
2101 eq = self.assertEqual
2102 # This message contains a line after the Subject: header that has only
2103 # whitespace, but it is not empty!
2104 msg = email.message_from_string("""\
2105From: aperson@dom.ain
2106To: bperson@dom.ain
2107Subject: the next line has a space on it
2108\x20
2109Date: Mon, 8 Apr 2002 15:09:19 -0400
2110Message-ID: spam
2111
2112Here's the message body
2113""")
2114 eq(msg['subject'], 'the next line has a space on it\n ')
2115 eq(msg['message-id'], 'spam')
2116 eq(msg.get_payload(), "Here's the message body\n")
2117
2118 def test_crlf_separation(self):
2119 eq = self.assertEqual
Barry Warsaw3d597812003-01-02 22:48:36 +00002120 fp = openfile('msg_26.txt', mode='rb')
Barry Warsaw190390b2002-07-19 22:31:10 +00002121 try:
2122 msg = Parser().parse(fp)
2123 finally:
2124 fp.close()
2125 eq(len(msg.get_payload()), 2)
2126 part1 = msg.get_payload(0)
2127 eq(part1.get_type(), 'text/plain')
2128 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
2129 part2 = msg.get_payload(1)
2130 eq(part2.get_type(), 'application/riscos')
2131
2132 def test_multipart_digest_with_extra_mime_headers(self):
2133 eq = self.assertEqual
2134 neq = self.ndiffAssertEqual
2135 fp = openfile('msg_28.txt')
2136 try:
2137 msg = email.message_from_file(fp)
2138 finally:
2139 fp.close()
2140 # Structure is:
2141 # multipart/digest
2142 # message/rfc822
2143 # text/plain
2144 # message/rfc822
2145 # text/plain
2146 eq(msg.is_multipart(), 1)
2147 eq(len(msg.get_payload()), 2)
2148 part1 = msg.get_payload(0)
2149 eq(part1.get_type(), 'message/rfc822')
2150 eq(part1.is_multipart(), 1)
2151 eq(len(part1.get_payload()), 1)
2152 part1a = part1.get_payload(0)
2153 eq(part1a.is_multipart(), 0)
2154 eq(part1a.get_type(), 'text/plain')
2155 neq(part1a.get_payload(), 'message 1\n')
2156 # next message/rfc822
2157 part2 = msg.get_payload(1)
2158 eq(part2.get_type(), 'message/rfc822')
2159 eq(part2.is_multipart(), 1)
2160 eq(len(part2.get_payload()), 1)
2161 part2a = part2.get_payload(0)
2162 eq(part2a.is_multipart(), 0)
2163 eq(part2a.get_type(), 'text/plain')
2164 neq(part2a.get_payload(), 'message 2\n')
2165
Barry Warsawb404bb72002-08-20 12:54:07 +00002166 def test_three_lines(self):
2167 # A bug report by Andrew McNamara
Barry Warsaw1a160752002-08-27 22:38:50 +00002168 lines = ['From: Andrew Person <aperson@dom.ain',
Barry Warsawb404bb72002-08-20 12:54:07 +00002169 'Subject: Test',
2170 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2171 msg = email.message_from_string(NL.join(lines))
2172 self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2173
Barry Warsaw190390b2002-07-19 22:31:10 +00002174
2175
2176class TestBase64(unittest.TestCase):
2177 def test_len(self):
2178 eq = self.assertEqual
2179 eq(base64MIME.base64_len('hello'),
2180 len(base64MIME.encode('hello', eol='')))
2181 for size in range(15):
2182 if size == 0 : bsize = 0
2183 elif size <= 3 : bsize = 4
2184 elif size <= 6 : bsize = 8
2185 elif size <= 9 : bsize = 12
2186 elif size <= 12: bsize = 16
2187 else : bsize = 20
2188 eq(base64MIME.base64_len('x'*size), bsize)
2189
2190 def test_decode(self):
2191 eq = self.assertEqual
2192 eq(base64MIME.decode(''), '')
2193 eq(base64MIME.decode('aGVsbG8='), 'hello')
2194 eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
2195 eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
2196
2197 def test_encode(self):
2198 eq = self.assertEqual
2199 eq(base64MIME.encode(''), '')
2200 eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
2201 # Test the binary flag
2202 eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
2203 eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
2204 # Test the maxlinelen arg
2205 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
2206eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2207eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2208eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
2209eHh4eCB4eHh4IA==
2210""")
2211 # Test the eol argument
2212 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2213eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2214eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2215eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
2216eHh4eCB4eHh4IA==\r
2217""")
2218
2219 def test_header_encode(self):
2220 eq = self.assertEqual
2221 he = base64MIME.header_encode
2222 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
2223 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
2224 # Test the charset option
2225 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
2226 # Test the keep_eols flag
Barry Warsawa0a00762002-11-05 21:36:17 +00002227 eq(he('hello\nworld', keep_eols=True),
Barry Warsaw190390b2002-07-19 22:31:10 +00002228 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
2229 # Test the maxlinelen argument
2230 eq(he('xxxx ' * 20, maxlinelen=40), """\
2231=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
2232 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
2233 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
2234 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
2235 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
2236 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2237 # Test the eol argument
2238 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2239=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
2240 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
2241 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
2242 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
2243 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
2244 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
2245
2246
2247
2248class TestQuopri(unittest.TestCase):
2249 def setUp(self):
2250 self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
2251 [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
2252 [chr(x) for x in range(ord('0'), ord('9')+1)] + \
2253 ['!', '*', '+', '-', '/', ' ']
2254 self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
2255 assert len(self.hlit) + len(self.hnon) == 256
2256 self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
2257 self.blit.remove('=')
2258 self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
2259 assert len(self.blit) + len(self.bnon) == 256
2260
2261 def test_header_quopri_check(self):
2262 for c in self.hlit:
2263 self.failIf(quopriMIME.header_quopri_check(c))
2264 for c in self.hnon:
2265 self.failUnless(quopriMIME.header_quopri_check(c))
2266
2267 def test_body_quopri_check(self):
2268 for c in self.blit:
2269 self.failIf(quopriMIME.body_quopri_check(c))
2270 for c in self.bnon:
2271 self.failUnless(quopriMIME.body_quopri_check(c))
2272
2273 def test_header_quopri_len(self):
2274 eq = self.assertEqual
2275 hql = quopriMIME.header_quopri_len
2276 enc = quopriMIME.header_encode
2277 for s in ('hello', 'h@e@l@l@o@'):
2278 # Empty charset and no line-endings. 7 == RFC chrome
2279 eq(hql(s), len(enc(s, charset='', eol=''))-7)
2280 for c in self.hlit:
2281 eq(hql(c), 1)
2282 for c in self.hnon:
2283 eq(hql(c), 3)
2284
2285 def test_body_quopri_len(self):
2286 eq = self.assertEqual
2287 bql = quopriMIME.body_quopri_len
2288 for c in self.blit:
2289 eq(bql(c), 1)
2290 for c in self.bnon:
2291 eq(bql(c), 3)
2292
2293 def test_quote_unquote_idempotent(self):
2294 for x in range(256):
2295 c = chr(x)
2296 self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
2297
2298 def test_header_encode(self):
2299 eq = self.assertEqual
2300 he = quopriMIME.header_encode
2301 eq(he('hello'), '=?iso-8859-1?q?hello?=')
2302 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
2303 # Test the charset option
2304 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
2305 # Test the keep_eols flag
Barry Warsawa0a00762002-11-05 21:36:17 +00002306 eq(he('hello\nworld', keep_eols=True), '=?iso-8859-1?q?hello=0Aworld?=')
Barry Warsaw190390b2002-07-19 22:31:10 +00002307 # Test a non-ASCII character
2308 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
2309 # Test the maxlinelen argument
2310 eq(he('xxxx ' * 20, maxlinelen=40), """\
2311=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
2312 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
2313 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
2314 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
2315 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2316 # Test the eol argument
2317 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2318=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
2319 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
2320 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
2321 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
2322 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
2323
2324 def test_decode(self):
2325 eq = self.assertEqual
2326 eq(quopriMIME.decode(''), '')
2327 eq(quopriMIME.decode('hello'), 'hello')
2328 eq(quopriMIME.decode('hello', 'X'), 'hello')
2329 eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
2330
2331 def test_encode(self):
2332 eq = self.assertEqual
2333 eq(quopriMIME.encode(''), '')
2334 eq(quopriMIME.encode('hello'), 'hello')
2335 # Test the binary flag
2336 eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
2337 eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
2338 # Test the maxlinelen arg
2339 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
2340xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
2341 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
2342x xxxx xxxx xxxx xxxx=20""")
2343 # Test the eol argument
2344 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
2345xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
2346 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
2347x xxxx xxxx xxxx xxxx=20""")
2348 eq(quopriMIME.encode("""\
2349one line
2350
2351two line"""), """\
2352one line
2353
2354two line""")
2355
2356
2357
2358# Test the Charset class
2359class TestCharset(unittest.TestCase):
Barry Warsaw41118042002-10-21 05:43:58 +00002360 def tearDown(self):
2361 from email import Charset as CharsetModule
2362 try:
2363 del CharsetModule.CHARSETS['fake']
2364 except KeyError:
2365 pass
2366
Barry Warsaw190390b2002-07-19 22:31:10 +00002367 def test_idempotent(self):
2368 eq = self.assertEqual
2369 # Make sure us-ascii = no Unicode conversion
2370 c = Charset('us-ascii')
2371 s = 'Hello World!'
2372 sp = c.to_splittable(s)
2373 eq(s, c.from_splittable(sp))
2374 # test 8-bit idempotency with us-ascii
2375 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
2376 sp = c.to_splittable(s)
2377 eq(s, c.from_splittable(sp))
2378
Barry Warsaw34aa4452002-10-21 05:31:08 +00002379 def test_body_encode(self):
2380 eq = self.assertEqual
2381 # Try a charset with QP body encoding
2382 c = Charset('iso-8859-1')
2383 eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
2384 # Try a charset with Base64 body encoding
2385 c = Charset('utf-8')
2386 eq('aGVsbG8gd29ybGQ=\n', c.body_encode('hello world'))
2387 # Try a charset with None body encoding
2388 c = Charset('us-ascii')
2389 eq('hello world', c.body_encode('hello world'))
2390 # Try the convert argument, where input codec <> output codec
2391 c = Charset('euc-jp')
2392 # With apologies to Tokio Kikuchi ;)
2393 try:
2394 eq('\x1b$B5FCO;~IW\x1b(B',
2395 c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
2396 eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
2397 c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
2398 except LookupError:
2399 # We probably don't have the Japanese codecs installed
2400 pass
Barry Warsaw41118042002-10-21 05:43:58 +00002401 # Testing SF bug #625509, which we have to fake, since there are no
2402 # built-in encodings where the header encoding is QP but the body
2403 # encoding is not.
2404 from email import Charset as CharsetModule
2405 CharsetModule.add_charset('fake', CharsetModule.QP, None)
2406 c = Charset('fake')
2407 eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
Barry Warsaw34aa4452002-10-21 05:31:08 +00002408
Barry Warsaw190390b2002-07-19 22:31:10 +00002409
2410
2411# Test multilingual MIME headers.
2412class TestHeader(TestEmailBase):
2413 def test_simple(self):
2414 eq = self.ndiffAssertEqual
2415 h = Header('Hello World!')
2416 eq(h.encode(), 'Hello World!')
2417 h.append(' Goodbye World!')
Barry Warsaw10627ba2003-03-06 05:41:07 +00002418 eq(h.encode(), 'Hello World! Goodbye World!')
Barry Warsaw190390b2002-07-19 22:31:10 +00002419
2420 def test_simple_surprise(self):
2421 eq = self.ndiffAssertEqual
2422 h = Header('Hello World!')
2423 eq(h.encode(), 'Hello World!')
2424 h.append('Goodbye World!')
Barry Warsaw10627ba2003-03-06 05:41:07 +00002425 eq(h.encode(), 'Hello World! Goodbye World!')
Barry Warsaw190390b2002-07-19 22:31:10 +00002426
2427 def test_header_needs_no_decoding(self):
2428 h = 'no decoding needed'
2429 self.assertEqual(decode_header(h), [(h, None)])
2430
2431 def test_long(self):
2432 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.",
2433 maxlinelen=76)
Barry Warsaw10627ba2003-03-06 05:41:07 +00002434 for l in h.encode(splitchars=' ').split('\n '):
Barry Warsaw190390b2002-07-19 22:31:10 +00002435 self.failUnless(len(l) <= 76)
2436
2437 def test_multilingual(self):
2438 eq = self.ndiffAssertEqual
2439 g = Charset("iso-8859-1")
2440 cz = Charset("iso-8859-2")
2441 utf8 = Charset("utf-8")
2442 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. "
2443 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
2444 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")
2445 h = Header(g_head, g)
2446 h.append(cz_head, cz)
2447 h.append(utf8_head, utf8)
2448 enc = h.encode()
Barry Warsaw10627ba2003-03-06 05:41:07 +00002449 eq(enc, """\
2450=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_ko?=
2451 =?iso-8859-1?q?mfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wan?=
2452 =?iso-8859-1?q?dgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6?=
2453 =?iso-8859-1?q?rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
2454 =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
2455 =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
2456 =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
2457 =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
2458 =?utf-8?q?_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das_Oder_die_Fl?=
2459 =?utf-8?b?aXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBo+OBpuOBhOOBvuOBmQ==?=
2460 =?utf-8?b?44CC?=""")
Barry Warsaw190390b2002-07-19 22:31:10 +00002461 eq(decode_header(enc),
2462 [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
2463 (utf8_head, "utf-8")])
2464 # Test for conversion to unicode. BAW: Python 2.1 doesn't support the
2465 # __unicode__() protocol, so do things this way for compatibility.
2466 ustr = h.__unicode__()
2467 # For Python 2.2 and beyond
2468 #ustr = unicode(h)
2469 eq(ustr.encode('utf-8'),
2470 'Die Mieter treten hier ein werden mit einem Foerderband '
2471 'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
2472 'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
2473 'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
2474 'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
2475 '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
2476 '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
2477 '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
2478 '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
2479 '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
2480 '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
2481 '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
2482 '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
2483 'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
2484 'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
2485 '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
2486 # Test make_header()
2487 newh = make_header(decode_header(enc))
2488 eq(newh, enc)
2489
2490 def test_header_ctor_default_args(self):
2491 eq = self.ndiffAssertEqual
2492 h = Header()
2493 eq(h, '')
2494 h.append('foo', Charset('iso-8859-1'))
2495 eq(h, '=?iso-8859-1?q?foo?=')
2496
2497 def test_explicit_maxlinelen(self):
2498 eq = self.ndiffAssertEqual
2499 hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
2500 h = Header(hstr)
2501 eq(h.encode(), '''\
2502A very long line that must get split to something other than at the 76th
2503 character boundary to test the non-default behavior''')
2504 h = Header(hstr, header_name='Subject')
2505 eq(h.encode(), '''\
2506A very long line that must get split to something other than at the
2507 76th character boundary to test the non-default behavior''')
2508 h = Header(hstr, maxlinelen=1024, header_name='Subject')
2509 eq(h.encode(), hstr)
2510
Barry Warsaw10d0d592002-07-23 19:46:35 +00002511 def test_us_ascii_header(self):
2512 eq = self.assertEqual
2513 s = 'hello'
2514 x = decode_header(s)
2515 eq(x, [('hello', None)])
2516 h = make_header(x)
2517 eq(s, h.encode())
Barry Warsaw190390b2002-07-19 22:31:10 +00002518
Barry Warsaw10d0d592002-07-23 19:46:35 +00002519 def test_string_charset(self):
2520 eq = self.assertEqual
2521 h = Header()
2522 h.append('hello', 'iso-8859-1')
2523 eq(h, '=?iso-8859-1?q?hello?=')
2524
Barry Warsaw09f74242002-09-26 17:21:53 +00002525## def test_unicode_error(self):
2526## raises = self.assertRaises
2527## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
2528## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
2529## h = Header()
2530## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
2531## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
2532## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
Barry Warsaw9c745692002-09-26 17:21:02 +00002533
Barry Warsaweecdc742002-09-28 21:04:19 +00002534 def test_utf8_shortest(self):
2535 eq = self.assertEqual
2536 h = Header(u'p\xf6stal', 'utf-8')
2537 eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
2538 h = Header(u'\u83ca\u5730\u6642\u592b', 'utf-8')
2539 eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
2540
Barry Warsaw10ee7a72002-12-30 19:14:38 +00002541 def test_bad_8bit_header(self):
2542 raises = self.assertRaises
2543 eq = self.assertEqual
2544 x = 'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
2545 raises(UnicodeError, Header, x)
2546 h = Header()
2547 raises(UnicodeError, h.append, x)
2548 eq(str(Header(x, errors='replace')), x)
2549 h.append(x, errors='replace')
2550 eq(str(h), x)
2551
Barry Warsaw10627ba2003-03-06 05:41:07 +00002552 def test_encoded_adjacent_nonencoded(self):
2553 eq = self.assertEqual
2554 h = Header()
2555 h.append('hello', 'iso-8859-1')
2556 h.append('world')
2557 s = h.encode()
2558 eq(s, '=?iso-8859-1?q?hello?= world')
2559 h = make_header(decode_header(s))
2560 eq(h.encode(), s)
2561
2562 def test_whitespace_eater(self):
2563 eq = self.assertEqual
2564 s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
2565 parts = decode_header(s)
2566 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)])
2567 hdr = make_header(parts)
2568 eq(hdr.encode(),
2569 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
2570
2571 def test_broken_base64_header(self):
2572 raises = self.assertRaises
2573 s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3IQ?='
2574 raises(Errors.HeaderParseError, decode_header, s)
2575
Barry Warsaw10d0d592002-07-23 19:46:35 +00002576
2577
Barry Warsawe99e2f52002-09-06 03:56:26 +00002578# Test RFC 2231 header parameters (en/de)coding
Barry Warsaw190390b2002-07-19 22:31:10 +00002579class TestRFC2231(TestEmailBase):
2580 def test_get_param(self):
2581 eq = self.assertEqual
2582 msg = self._msgobj('msg_29.txt')
2583 eq(msg.get_param('title'),
2584 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
Barry Warsawa0a00762002-11-05 21:36:17 +00002585 eq(msg.get_param('title', unquote=False),
Barry Warsaw190390b2002-07-19 22:31:10 +00002586 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
2587
Barry Warsawe99e2f52002-09-06 03:56:26 +00002588 def test_set_param(self):
2589 eq = self.assertEqual
2590 msg = Message()
2591 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2592 charset='us-ascii')
2593 eq(msg.get_param('title'),
2594 ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
2595 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2596 charset='us-ascii', language='en')
2597 eq(msg.get_param('title'),
2598 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
2599 msg = self._msgobj('msg_01.txt')
2600 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2601 charset='us-ascii', language='en')
2602 eq(msg.as_string(), """\
2603Return-Path: <bbb@zzz.org>
2604Delivered-To: bbb@zzz.org
2605Received: by mail.zzz.org (Postfix, from userid 889)
2606\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
2607MIME-Version: 1.0
2608Content-Transfer-Encoding: 7bit
2609Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2610From: bbb@ddd.com (John X. Doe)
2611To: bbb@zzz.org
2612Subject: This is a test message
2613Date: Fri, 4 May 2001 14:05:44 -0400
2614Content-Type: text/plain; charset=us-ascii;
2615\ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2616
2617
2618Hi,
2619
2620Do you like this message?
2621
2622-Me
2623""")
2624
2625 def test_del_param(self):
2626 eq = self.ndiffAssertEqual
2627 msg = self._msgobj('msg_01.txt')
2628 msg.set_param('foo', 'bar', charset='us-ascii', language='en')
2629 msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
2630 charset='us-ascii', language='en')
2631 msg.del_param('foo', header='Content-Type')
2632 eq(msg.as_string(), """\
2633Return-Path: <bbb@zzz.org>
2634Delivered-To: bbb@zzz.org
2635Received: by mail.zzz.org (Postfix, from userid 889)
2636\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
2637MIME-Version: 1.0
2638Content-Transfer-Encoding: 7bit
2639Message-ID: <15090.61304.110929.45684@aaa.zzz.org>
2640From: bbb@ddd.com (John X. Doe)
2641To: bbb@zzz.org
2642Subject: This is a test message
2643Date: Fri, 4 May 2001 14:05:44 -0400
2644Content-Type: text/plain; charset="us-ascii";
2645\ttitle*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
2646
2647
2648Hi,
2649
2650Do you like this message?
2651
2652-Me
2653""")
2654
Barry Warsaw9c745692002-09-26 17:21:02 +00002655 def test_rfc2231_get_content_charset(self):
2656 eq = self.assertEqual
2657 msg = self._msgobj('msg_32.txt')
2658 eq(msg.get_content_charset(), 'us-ascii')
2659
Barry Warsaw21fcc4e2003-03-07 22:45:55 +00002660 def test_rfc2231_no_language_or_charset(self):
2661 m = '''\
2662Content-Transfer-Encoding: 8bit
2663Content-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
2664Content-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
2665
2666'''
2667 msg = email.message_from_string(m)
2668 self.assertEqual(msg.get_param('NAME'),
2669 (None, None, 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm'))
2670
Barry Warsaw622d60b2003-08-19 03:54:24 +00002671 def test_rfc2231_no_language_or_charset_in_filename(self):
2672 m = '''\
2673Content-Disposition: inline;
2674\tfilename*0="This%20is%20even%20more%20";
2675\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
2676\tfilename*2="is it not.pdf"
2677
2678'''
2679 msg = email.message_from_string(m)
2680 self.assertEqual(msg.get_filename(),
2681 'This is even more ***fun*** is it not.pdf')
2682
2683 def test_rfc2231_no_language_or_charset_in_boundary(self):
2684 m = '''\
2685Content-Type: multipart/alternative;
2686\tboundary*0="This%20is%20even%20more%20";
2687\tboundary*1="%2A%2A%2Afun%2A%2A%2A%20";
2688\tboundary*2="is it not.pdf"
2689
2690'''
2691 msg = email.message_from_string(m)
2692 self.assertEqual(msg.get_boundary(),
2693 'This is even more ***fun*** is it not.pdf')
2694
2695 def test_rfc2231_no_language_or_charset_in_charset(self):
2696 # This is a nonsensical charset value, but tests the code anyway
2697 m = '''\
2698Content-Type: text/plain;
2699\tcharset*0="This%20is%20even%20more%20";
2700\tcharset*1="%2A%2A%2Afun%2A%2A%2A%20";
2701\tcharset*2="is it not.pdf"
2702
2703'''
2704 msg = email.message_from_string(m)
2705 self.assertEqual(msg.get_content_charset(),
2706 'this is even more ***fun*** is it not.pdf')
2707
Barry Warsaw190390b2002-07-19 22:31:10 +00002708
2709
2710def _testclasses():
2711 mod = sys.modules[__name__]
2712 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
2713
2714
2715def suite():
2716 suite = unittest.TestSuite()
2717 for testclass in _testclasses():
2718 suite.addTest(unittest.makeSuite(testclass))
2719 return suite
2720
2721
2722def test_main():
2723 for testclass in _testclasses():
2724 run_unittest(testclass)
2725
2726
2727
2728if __name__ == '__main__':
2729 unittest.main(defaultTest='suite')