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