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