/Lib/email/test/test_email.py

http://unladen-swallow.googlecode.com/ · Python · 3289 lines · 2798 code · 313 blank · 178 comment · 94 complexity · c56bb17f941e25ea643401137ee5788d MD5 · raw file

Large files are truncated click here to view the full file

  1. # Copyright (C) 2001-2007 Python Software Foundation
  2. # Contact: email-sig@python.org
  3. # email package unit tests
  4. import os
  5. import sys
  6. import time
  7. import base64
  8. import difflib
  9. import unittest
  10. import warnings
  11. from cStringIO import StringIO
  12. import email
  13. from email.Charset import Charset
  14. from email.Header import Header, decode_header, make_header
  15. from email.Parser import Parser, HeaderParser
  16. from email.Generator import Generator, DecodedGenerator
  17. from email.Message import Message
  18. from email.MIMEAudio import MIMEAudio
  19. from email.MIMEText import MIMEText
  20. from email.MIMEImage import MIMEImage
  21. from email.MIMEBase import MIMEBase
  22. from email.MIMEMessage import MIMEMessage
  23. from email.MIMEMultipart import MIMEMultipart
  24. from email import Utils
  25. from email import Errors
  26. from email import Encoders
  27. from email import Iterators
  28. from email import base64MIME
  29. from email import quopriMIME
  30. from test.test_support import findfile, run_unittest
  31. from email.test import __file__ as landmark
  32. NL = '\n'
  33. EMPTYSTRING = ''
  34. SPACE = ' '
  35. def openfile(filename, mode='r'):
  36. path = os.path.join(os.path.dirname(landmark), 'data', filename)
  37. return open(path, mode)
  38. # Base test class
  39. class TestEmailBase(unittest.TestCase):
  40. def ndiffAssertEqual(self, first, second):
  41. """Like failUnlessEqual except use ndiff for readable output."""
  42. if first <> second:
  43. sfirst = str(first)
  44. ssecond = str(second)
  45. diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
  46. fp = StringIO()
  47. print >> fp, NL, NL.join(diff)
  48. raise self.failureException, fp.getvalue()
  49. def _msgobj(self, filename):
  50. fp = openfile(findfile(filename))
  51. try:
  52. msg = email.message_from_file(fp)
  53. finally:
  54. fp.close()
  55. return msg
  56. # Test various aspects of the Message class's API
  57. class TestMessageAPI(TestEmailBase):
  58. def test_get_all(self):
  59. eq = self.assertEqual
  60. msg = self._msgobj('msg_20.txt')
  61. eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
  62. eq(msg.get_all('xx', 'n/a'), 'n/a')
  63. def test_getset_charset(self):
  64. eq = self.assertEqual
  65. msg = Message()
  66. eq(msg.get_charset(), None)
  67. charset = Charset('iso-8859-1')
  68. msg.set_charset(charset)
  69. eq(msg['mime-version'], '1.0')
  70. eq(msg.get_content_type(), 'text/plain')
  71. eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
  72. eq(msg.get_param('charset'), 'iso-8859-1')
  73. eq(msg['content-transfer-encoding'], 'quoted-printable')
  74. eq(msg.get_charset().input_charset, 'iso-8859-1')
  75. # Remove the charset
  76. msg.set_charset(None)
  77. eq(msg.get_charset(), None)
  78. eq(msg['content-type'], 'text/plain')
  79. # Try adding a charset when there's already MIME headers present
  80. msg = Message()
  81. msg['MIME-Version'] = '2.0'
  82. msg['Content-Type'] = 'text/x-weird'
  83. msg['Content-Transfer-Encoding'] = 'quinted-puntable'
  84. msg.set_charset(charset)
  85. eq(msg['mime-version'], '2.0')
  86. eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
  87. eq(msg['content-transfer-encoding'], 'quinted-puntable')
  88. def test_set_charset_from_string(self):
  89. eq = self.assertEqual
  90. msg = Message()
  91. msg.set_charset('us-ascii')
  92. eq(msg.get_charset().input_charset, 'us-ascii')
  93. eq(msg['content-type'], 'text/plain; charset="us-ascii"')
  94. def test_set_payload_with_charset(self):
  95. msg = Message()
  96. charset = Charset('iso-8859-1')
  97. msg.set_payload('This is a string payload', charset)
  98. self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
  99. def test_get_charsets(self):
  100. eq = self.assertEqual
  101. msg = self._msgobj('msg_08.txt')
  102. charsets = msg.get_charsets()
  103. eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
  104. msg = self._msgobj('msg_09.txt')
  105. charsets = msg.get_charsets('dingbat')
  106. eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
  107. 'koi8-r'])
  108. msg = self._msgobj('msg_12.txt')
  109. charsets = msg.get_charsets()
  110. eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
  111. 'iso-8859-3', 'us-ascii', 'koi8-r'])
  112. def test_get_filename(self):
  113. eq = self.assertEqual
  114. msg = self._msgobj('msg_04.txt')
  115. filenames = [p.get_filename() for p in msg.get_payload()]
  116. eq(filenames, ['msg.txt', 'msg.txt'])
  117. msg = self._msgobj('msg_07.txt')
  118. subpart = msg.get_payload(1)
  119. eq(subpart.get_filename(), 'dingusfish.gif')
  120. def test_get_filename_with_name_parameter(self):
  121. eq = self.assertEqual
  122. msg = self._msgobj('msg_44.txt')
  123. filenames = [p.get_filename() for p in msg.get_payload()]
  124. eq(filenames, ['msg.txt', 'msg.txt'])
  125. def test_get_boundary(self):
  126. eq = self.assertEqual
  127. msg = self._msgobj('msg_07.txt')
  128. # No quotes!
  129. eq(msg.get_boundary(), 'BOUNDARY')
  130. def test_set_boundary(self):
  131. eq = self.assertEqual
  132. # This one has no existing boundary parameter, but the Content-Type:
  133. # header appears fifth.
  134. msg = self._msgobj('msg_01.txt')
  135. msg.set_boundary('BOUNDARY')
  136. header, value = msg.items()[4]
  137. eq(header.lower(), 'content-type')
  138. eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
  139. # This one has a Content-Type: header, with a boundary, stuck in the
  140. # middle of its headers. Make sure the order is preserved; it should
  141. # be fifth.
  142. msg = self._msgobj('msg_04.txt')
  143. msg.set_boundary('BOUNDARY')
  144. header, value = msg.items()[4]
  145. eq(header.lower(), 'content-type')
  146. eq(value, 'multipart/mixed; boundary="BOUNDARY"')
  147. # And this one has no Content-Type: header at all.
  148. msg = self._msgobj('msg_03.txt')
  149. self.assertRaises(Errors.HeaderParseError,
  150. msg.set_boundary, 'BOUNDARY')
  151. def test_get_decoded_payload(self):
  152. eq = self.assertEqual
  153. msg = self._msgobj('msg_10.txt')
  154. # The outer message is a multipart
  155. eq(msg.get_payload(decode=True), None)
  156. # Subpart 1 is 7bit encoded
  157. eq(msg.get_payload(0).get_payload(decode=True),
  158. 'This is a 7bit encoded message.\n')
  159. # Subpart 2 is quopri
  160. eq(msg.get_payload(1).get_payload(decode=True),
  161. '\xa1This is a Quoted Printable encoded message!\n')
  162. # Subpart 3 is base64
  163. eq(msg.get_payload(2).get_payload(decode=True),
  164. 'This is a Base64 encoded message.')
  165. # Subpart 4 has no Content-Transfer-Encoding: header.
  166. eq(msg.get_payload(3).get_payload(decode=True),
  167. 'This has no Content-Transfer-Encoding: header.\n')
  168. def test_get_decoded_uu_payload(self):
  169. eq = self.assertEqual
  170. msg = Message()
  171. msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
  172. for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
  173. msg['content-transfer-encoding'] = cte
  174. eq(msg.get_payload(decode=True), 'hello world')
  175. # Now try some bogus data
  176. msg.set_payload('foo')
  177. eq(msg.get_payload(decode=True), 'foo')
  178. def test_decode_bogus_uu_payload_quietly(self):
  179. msg = Message()
  180. msg.set_payload('begin 664 foo.txt\n%<W1F=0000H \n \nend\n')
  181. msg['Content-Transfer-Encoding'] = 'x-uuencode'
  182. old_stderr = sys.stderr
  183. try:
  184. sys.stderr = sfp = StringIO()
  185. # We don't care about the payload
  186. msg.get_payload(decode=True)
  187. finally:
  188. sys.stderr = old_stderr
  189. self.assertEqual(sfp.getvalue(), '')
  190. def test_decoded_generator(self):
  191. eq = self.assertEqual
  192. msg = self._msgobj('msg_07.txt')
  193. fp = openfile('msg_17.txt')
  194. try:
  195. text = fp.read()
  196. finally:
  197. fp.close()
  198. s = StringIO()
  199. g = DecodedGenerator(s)
  200. g.flatten(msg)
  201. eq(s.getvalue(), text)
  202. def test__contains__(self):
  203. msg = Message()
  204. msg['From'] = 'Me'
  205. msg['to'] = 'You'
  206. # Check for case insensitivity
  207. self.failUnless('from' in msg)
  208. self.failUnless('From' in msg)
  209. self.failUnless('FROM' in msg)
  210. self.failUnless('to' in msg)
  211. self.failUnless('To' in msg)
  212. self.failUnless('TO' in msg)
  213. def test_as_string(self):
  214. eq = self.assertEqual
  215. msg = self._msgobj('msg_01.txt')
  216. fp = openfile('msg_01.txt')
  217. try:
  218. text = fp.read()
  219. finally:
  220. fp.close()
  221. eq(text, msg.as_string())
  222. fullrepr = str(msg)
  223. lines = fullrepr.split('\n')
  224. self.failUnless(lines[0].startswith('From '))
  225. eq(text, NL.join(lines[1:]))
  226. def test_bad_param(self):
  227. msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
  228. self.assertEqual(msg.get_param('baz'), '')
  229. def test_missing_filename(self):
  230. msg = email.message_from_string("From: foo\n")
  231. self.assertEqual(msg.get_filename(), None)
  232. def test_bogus_filename(self):
  233. msg = email.message_from_string(
  234. "Content-Disposition: blarg; filename\n")
  235. self.assertEqual(msg.get_filename(), '')
  236. def test_missing_boundary(self):
  237. msg = email.message_from_string("From: foo\n")
  238. self.assertEqual(msg.get_boundary(), None)
  239. def test_get_params(self):
  240. eq = self.assertEqual
  241. msg = email.message_from_string(
  242. 'X-Header: foo=one; bar=two; baz=three\n')
  243. eq(msg.get_params(header='x-header'),
  244. [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
  245. msg = email.message_from_string(
  246. 'X-Header: foo; bar=one; baz=two\n')
  247. eq(msg.get_params(header='x-header'),
  248. [('foo', ''), ('bar', 'one'), ('baz', 'two')])
  249. eq(msg.get_params(), None)
  250. msg = email.message_from_string(
  251. 'X-Header: foo; bar="one"; baz=two\n')
  252. eq(msg.get_params(header='x-header'),
  253. [('foo', ''), ('bar', 'one'), ('baz', 'two')])
  254. def test_get_param_liberal(self):
  255. msg = Message()
  256. msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
  257. self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
  258. def test_get_param(self):
  259. eq = self.assertEqual
  260. msg = email.message_from_string(
  261. "X-Header: foo=one; bar=two; baz=three\n")
  262. eq(msg.get_param('bar', header='x-header'), 'two')
  263. eq(msg.get_param('quuz', header='x-header'), None)
  264. eq(msg.get_param('quuz'), None)
  265. msg = email.message_from_string(
  266. 'X-Header: foo; bar="one"; baz=two\n')
  267. eq(msg.get_param('foo', header='x-header'), '')
  268. eq(msg.get_param('bar', header='x-header'), 'one')
  269. eq(msg.get_param('baz', header='x-header'), 'two')
  270. # XXX: We are not RFC-2045 compliant! We cannot parse:
  271. # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
  272. # msg.get_param("weird")
  273. # yet.
  274. def test_get_param_funky_continuation_lines(self):
  275. msg = self._msgobj('msg_22.txt')
  276. self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
  277. def test_get_param_with_semis_in_quotes(self):
  278. msg = email.message_from_string(
  279. 'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
  280. self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
  281. self.assertEqual(msg.get_param('name', unquote=False),
  282. '"Jim&amp;&amp;Jill"')
  283. def test_has_key(self):
  284. msg = email.message_from_string('Header: exists')
  285. self.failUnless(msg.has_key('header'))
  286. self.failUnless(msg.has_key('Header'))
  287. self.failUnless(msg.has_key('HEADER'))
  288. self.failIf(msg.has_key('headeri'))
  289. def test_set_param(self):
  290. eq = self.assertEqual
  291. msg = Message()
  292. msg.set_param('charset', 'iso-2022-jp')
  293. eq(msg.get_param('charset'), 'iso-2022-jp')
  294. msg.set_param('importance', 'high value')
  295. eq(msg.get_param('importance'), 'high value')
  296. eq(msg.get_param('importance', unquote=False), '"high value"')
  297. eq(msg.get_params(), [('text/plain', ''),
  298. ('charset', 'iso-2022-jp'),
  299. ('importance', 'high value')])
  300. eq(msg.get_params(unquote=False), [('text/plain', ''),
  301. ('charset', '"iso-2022-jp"'),
  302. ('importance', '"high value"')])
  303. msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
  304. eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
  305. def test_del_param(self):
  306. eq = self.assertEqual
  307. msg = self._msgobj('msg_05.txt')
  308. eq(msg.get_params(),
  309. [('multipart/report', ''), ('report-type', 'delivery-status'),
  310. ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
  311. old_val = msg.get_param("report-type")
  312. msg.del_param("report-type")
  313. eq(msg.get_params(),
  314. [('multipart/report', ''),
  315. ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
  316. msg.set_param("report-type", old_val)
  317. eq(msg.get_params(),
  318. [('multipart/report', ''),
  319. ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
  320. ('report-type', old_val)])
  321. def test_del_param_on_other_header(self):
  322. msg = Message()
  323. msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
  324. msg.del_param('filename', 'content-disposition')
  325. self.assertEqual(msg['content-disposition'], 'attachment')
  326. def test_set_type(self):
  327. eq = self.assertEqual
  328. msg = Message()
  329. self.assertRaises(ValueError, msg.set_type, 'text')
  330. msg.set_type('text/plain')
  331. eq(msg['content-type'], 'text/plain')
  332. msg.set_param('charset', 'us-ascii')
  333. eq(msg['content-type'], 'text/plain; charset="us-ascii"')
  334. msg.set_type('text/html')
  335. eq(msg['content-type'], 'text/html; charset="us-ascii"')
  336. def test_set_type_on_other_header(self):
  337. msg = Message()
  338. msg['X-Content-Type'] = 'text/plain'
  339. msg.set_type('application/octet-stream', 'X-Content-Type')
  340. self.assertEqual(msg['x-content-type'], 'application/octet-stream')
  341. def test_get_content_type_missing(self):
  342. msg = Message()
  343. self.assertEqual(msg.get_content_type(), 'text/plain')
  344. def test_get_content_type_missing_with_default_type(self):
  345. msg = Message()
  346. msg.set_default_type('message/rfc822')
  347. self.assertEqual(msg.get_content_type(), 'message/rfc822')
  348. def test_get_content_type_from_message_implicit(self):
  349. msg = self._msgobj('msg_30.txt')
  350. self.assertEqual(msg.get_payload(0).get_content_type(),
  351. 'message/rfc822')
  352. def test_get_content_type_from_message_explicit(self):
  353. msg = self._msgobj('msg_28.txt')
  354. self.assertEqual(msg.get_payload(0).get_content_type(),
  355. 'message/rfc822')
  356. def test_get_content_type_from_message_text_plain_implicit(self):
  357. msg = self._msgobj('msg_03.txt')
  358. self.assertEqual(msg.get_content_type(), 'text/plain')
  359. def test_get_content_type_from_message_text_plain_explicit(self):
  360. msg = self._msgobj('msg_01.txt')
  361. self.assertEqual(msg.get_content_type(), 'text/plain')
  362. def test_get_content_maintype_missing(self):
  363. msg = Message()
  364. self.assertEqual(msg.get_content_maintype(), 'text')
  365. def test_get_content_maintype_missing_with_default_type(self):
  366. msg = Message()
  367. msg.set_default_type('message/rfc822')
  368. self.assertEqual(msg.get_content_maintype(), 'message')
  369. def test_get_content_maintype_from_message_implicit(self):
  370. msg = self._msgobj('msg_30.txt')
  371. self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
  372. def test_get_content_maintype_from_message_explicit(self):
  373. msg = self._msgobj('msg_28.txt')
  374. self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
  375. def test_get_content_maintype_from_message_text_plain_implicit(self):
  376. msg = self._msgobj('msg_03.txt')
  377. self.assertEqual(msg.get_content_maintype(), 'text')
  378. def test_get_content_maintype_from_message_text_plain_explicit(self):
  379. msg = self._msgobj('msg_01.txt')
  380. self.assertEqual(msg.get_content_maintype(), 'text')
  381. def test_get_content_subtype_missing(self):
  382. msg = Message()
  383. self.assertEqual(msg.get_content_subtype(), 'plain')
  384. def test_get_content_subtype_missing_with_default_type(self):
  385. msg = Message()
  386. msg.set_default_type('message/rfc822')
  387. self.assertEqual(msg.get_content_subtype(), 'rfc822')
  388. def test_get_content_subtype_from_message_implicit(self):
  389. msg = self._msgobj('msg_30.txt')
  390. self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
  391. def test_get_content_subtype_from_message_explicit(self):
  392. msg = self._msgobj('msg_28.txt')
  393. self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
  394. def test_get_content_subtype_from_message_text_plain_implicit(self):
  395. msg = self._msgobj('msg_03.txt')
  396. self.assertEqual(msg.get_content_subtype(), 'plain')
  397. def test_get_content_subtype_from_message_text_plain_explicit(self):
  398. msg = self._msgobj('msg_01.txt')
  399. self.assertEqual(msg.get_content_subtype(), 'plain')
  400. def test_get_content_maintype_error(self):
  401. msg = Message()
  402. msg['Content-Type'] = 'no-slash-in-this-string'
  403. self.assertEqual(msg.get_content_maintype(), 'text')
  404. def test_get_content_subtype_error(self):
  405. msg = Message()
  406. msg['Content-Type'] = 'no-slash-in-this-string'
  407. self.assertEqual(msg.get_content_subtype(), 'plain')
  408. def test_replace_header(self):
  409. eq = self.assertEqual
  410. msg = Message()
  411. msg.add_header('First', 'One')
  412. msg.add_header('Second', 'Two')
  413. msg.add_header('Third', 'Three')
  414. eq(msg.keys(), ['First', 'Second', 'Third'])
  415. eq(msg.values(), ['One', 'Two', 'Three'])
  416. msg.replace_header('Second', 'Twenty')
  417. eq(msg.keys(), ['First', 'Second', 'Third'])
  418. eq(msg.values(), ['One', 'Twenty', 'Three'])
  419. msg.add_header('First', 'Eleven')
  420. msg.replace_header('First', 'One Hundred')
  421. eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
  422. eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
  423. self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
  424. def test_broken_base64_payload(self):
  425. x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
  426. msg = Message()
  427. msg['content-type'] = 'audio/x-midi'
  428. msg['content-transfer-encoding'] = 'base64'
  429. msg.set_payload(x)
  430. self.assertEqual(msg.get_payload(decode=True), x)
  431. def test_get_content_charset(self):
  432. msg = Message()
  433. msg.set_charset('us-ascii')
  434. self.assertEqual('us-ascii', msg.get_content_charset())
  435. msg.set_charset(u'us-ascii')
  436. self.assertEqual('us-ascii', msg.get_content_charset())
  437. # Test the email.Encoders module
  438. class TestEncoders(unittest.TestCase):
  439. def test_encode_empty_payload(self):
  440. eq = self.assertEqual
  441. msg = Message()
  442. msg.set_charset('us-ascii')
  443. eq(msg['content-transfer-encoding'], '7bit')
  444. def test_default_cte(self):
  445. eq = self.assertEqual
  446. msg = MIMEText('hello world')
  447. eq(msg['content-transfer-encoding'], '7bit')
  448. def test_default_cte(self):
  449. eq = self.assertEqual
  450. # With no explicit _charset its us-ascii, and all are 7-bit
  451. msg = MIMEText('hello world')
  452. eq(msg['content-transfer-encoding'], '7bit')
  453. # Similar, but with 8-bit data
  454. msg = MIMEText('hello \xf8 world')
  455. eq(msg['content-transfer-encoding'], '8bit')
  456. # And now with a different charset
  457. msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
  458. eq(msg['content-transfer-encoding'], 'quoted-printable')
  459. # Test long header wrapping
  460. class TestLongHeaders(TestEmailBase):
  461. def test_split_long_continuation(self):
  462. eq = self.ndiffAssertEqual
  463. msg = email.message_from_string("""\
  464. Subject: bug demonstration
  465. \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
  466. \tmore text
  467. test
  468. """)
  469. sfp = StringIO()
  470. g = Generator(sfp)
  471. g.flatten(msg)
  472. eq(sfp.getvalue(), """\
  473. Subject: bug demonstration
  474. \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
  475. \tmore text
  476. test
  477. """)
  478. def test_another_long_almost_unsplittable_header(self):
  479. eq = self.ndiffAssertEqual
  480. hstr = """\
  481. bug demonstration
  482. \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
  483. \tmore text"""
  484. h = Header(hstr, continuation_ws='\t')
  485. eq(h.encode(), """\
  486. bug demonstration
  487. \t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
  488. \tmore text""")
  489. h = Header(hstr)
  490. eq(h.encode(), """\
  491. bug demonstration
  492. 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
  493. more text""")
  494. def test_long_nonstring(self):
  495. eq = self.ndiffAssertEqual
  496. g = Charset("iso-8859-1")
  497. cz = Charset("iso-8859-2")
  498. utf8 = Charset("utf-8")
  499. 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. "
  500. cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
  501. 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")
  502. h = Header(g_head, g, header_name='Subject')
  503. h.append(cz_head, cz)
  504. h.append(utf8_head, utf8)
  505. msg = Message()
  506. msg['Subject'] = h
  507. sfp = StringIO()
  508. g = Generator(sfp)
  509. g.flatten(msg)
  510. eq(sfp.getvalue(), """\
  511. Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
  512. =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
  513. =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
  514. =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
  515. =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
  516. =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
  517. =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
  518. =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
  519. =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
  520. =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
  521. =?utf-8?b?44Gm44GE44G+44GZ44CC?=
  522. """)
  523. eq(h.encode(), """\
  524. =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerd?=
  525. =?iso-8859-1?q?erband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndi?=
  526. =?iso-8859-1?q?schen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Kling?=
  527. =?iso-8859-1?q?en_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_met?=
  528. =?iso-8859-2?q?ropole_se_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
  529. =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE?=
  530. =?utf-8?b?44G+44Gb44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB?=
  531. =?utf-8?b?44GC44Go44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CM?=
  532. =?utf-8?q?Wenn_ist_das_Nunstuck_git_und_Slotermeyer=3F_Ja!_Beiherhund_das?=
  533. =?utf-8?b?IE9kZXIgZGllIEZsaXBwZXJ3YWxkdCBnZXJzcHV0LuOAjeOBqOiogOOBow==?=
  534. =?utf-8?b?44Gm44GE44G+44GZ44CC?=""")
  535. def test_long_header_encode(self):
  536. eq = self.ndiffAssertEqual
  537. h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
  538. 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
  539. header_name='X-Foobar-Spoink-Defrobnit')
  540. eq(h.encode(), '''\
  541. wasnipoop; giraffes="very-long-necked-animals";
  542. spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
  543. def test_long_header_encode_with_tab_continuation(self):
  544. eq = self.ndiffAssertEqual
  545. h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
  546. 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
  547. header_name='X-Foobar-Spoink-Defrobnit',
  548. continuation_ws='\t')
  549. eq(h.encode(), '''\
  550. wasnipoop; giraffes="very-long-necked-animals";
  551. \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
  552. def test_header_splitter(self):
  553. eq = self.ndiffAssertEqual
  554. msg = MIMEText('')
  555. # It'd be great if we could use add_header() here, but that doesn't
  556. # guarantee an order of the parameters.
  557. msg['X-Foobar-Spoink-Defrobnit'] = (
  558. 'wasnipoop; giraffes="very-long-necked-animals"; '
  559. 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
  560. sfp = StringIO()
  561. g = Generator(sfp)
  562. g.flatten(msg)
  563. eq(sfp.getvalue(), '''\
  564. Content-Type: text/plain; charset="us-ascii"
  565. MIME-Version: 1.0
  566. Content-Transfer-Encoding: 7bit
  567. X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
  568. \tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
  569. ''')
  570. def test_no_semis_header_splitter(self):
  571. eq = self.ndiffAssertEqual
  572. msg = Message()
  573. msg['From'] = 'test@dom.ain'
  574. msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
  575. msg.set_payload('Test')
  576. sfp = StringIO()
  577. g = Generator(sfp)
  578. g.flatten(msg)
  579. eq(sfp.getvalue(), """\
  580. From: test@dom.ain
  581. References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
  582. \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
  583. Test""")
  584. def test_no_split_long_header(self):
  585. eq = self.ndiffAssertEqual
  586. hstr = 'References: ' + 'x' * 80
  587. h = Header(hstr, continuation_ws='\t')
  588. eq(h.encode(), """\
  589. References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
  590. def test_splitting_multiple_long_lines(self):
  591. eq = self.ndiffAssertEqual
  592. hstr = """\
  593. from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
  594. \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)
  595. \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)
  596. """
  597. h = Header(hstr, continuation_ws='\t')
  598. eq(h.encode(), """\
  599. from babylon.socal-raves.org (localhost [127.0.0.1]);
  600. \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
  601. \tfor <mailman-admin@babylon.socal-raves.org>;
  602. \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
  603. \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
  604. \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
  605. \tfor <mailman-admin@babylon.socal-raves.org>;
  606. \tSat, 2 Feb 2002 17:00:06 -0800 (PST)
  607. \tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
  608. \tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
  609. \tfor <mailman-admin@babylon.socal-raves.org>;
  610. \tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
  611. def test_splitting_first_line_only_is_long(self):
  612. eq = self.ndiffAssertEqual
  613. hstr = """\
  614. from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
  615. \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
  616. \tid 17k4h5-00034i-00
  617. \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
  618. h = Header(hstr, maxlinelen=78, header_name='Received',
  619. continuation_ws='\t')
  620. eq(h.encode(), """\
  621. from modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
  622. \thelo=cthulhu.gerg.ca)
  623. \tby kronos.mems-exchange.org with esmtp (Exim 4.05)
  624. \tid 17k4h5-00034i-00
  625. \tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
  626. def test_long_8bit_header(self):
  627. eq = self.ndiffAssertEqual
  628. msg = Message()
  629. h = Header('Britische Regierung gibt', 'iso-8859-1',
  630. header_name='Subject')
  631. h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
  632. msg['Subject'] = h
  633. eq(msg.as_string(), """\
  634. Subject: =?iso-8859-1?q?Britische_Regierung_gibt?= =?iso-8859-1?q?gr=FCnes?=
  635. =?iso-8859-1?q?_Licht_f=FCr_Offshore-Windkraftprojekte?=
  636. """)
  637. def test_long_8bit_header_no_charset(self):
  638. eq = self.ndiffAssertEqual
  639. msg = Message()
  640. msg['Reply-To'] = 'Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>'
  641. eq(msg.as_string(), """\
  642. Reply-To: Britische Regierung gibt gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte <a-very-long-address@example.com>
  643. """)
  644. def test_long_to_header(self):
  645. eq = self.ndiffAssertEqual
  646. 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>'
  647. msg = Message()
  648. msg['To'] = to
  649. eq(msg.as_string(0), '''\
  650. To: "Someone Test #A" <someone@eecs.umich.edu>, <someone@eecs.umich.edu>,
  651. \t"Someone Test #B" <someone@umich.edu>,
  652. \t"Someone Test #C" <someone@eecs.umich.edu>,
  653. \t"Someone Test #D" <someone@eecs.umich.edu>
  654. ''')
  655. def test_long_line_after_append(self):
  656. eq = self.ndiffAssertEqual
  657. s = 'This is an example of string which has almost the limit of header length.'
  658. h = Header(s)
  659. h.append('Add another line.')
  660. eq(h.encode(), """\
  661. This is an example of string which has almost the limit of header length.
  662. Add another line.""")
  663. def test_shorter_line_with_append(self):
  664. eq = self.ndiffAssertEqual
  665. s = 'This is a shorter line.'
  666. h = Header(s)
  667. h.append('Add another sentence. (Surprise?)')
  668. eq(h.encode(),
  669. 'This is a shorter line. Add another sentence. (Surprise?)')
  670. def test_long_field_name(self):
  671. eq = self.ndiffAssertEqual
  672. fn = 'X-Very-Very-Very-Long-Header-Name'
  673. 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. "
  674. h = Header(gs, 'iso-8859-1', header_name=fn)
  675. # BAW: this seems broken because the first line is too long
  676. eq(h.encode(), """\
  677. =?iso-8859-1?q?Die_Mieter_treten_hier_?=
  678. =?iso-8859-1?q?ein_werden_mit_einem_Foerderband_komfortabel_den_Korridor_?=
  679. =?iso-8859-1?q?entlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_g?=
  680. =?iso-8859-1?q?egen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
  681. def test_long_received_header(self):
  682. 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'
  683. msg = Message()
  684. msg['Received-1'] = Header(h, continuation_ws='\t')
  685. msg['Received-2'] = h
  686. self.assertEqual(msg.as_string(), """\
  687. Received-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
  688. \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
  689. \tWed, 05 Mar 2003 18:10:18 -0700
  690. Received-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
  691. \throthgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
  692. \tWed, 05 Mar 2003 18:10:18 -0700
  693. """)
  694. def test_string_headerinst_eq(self):
  695. h = '<15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner\'s message of "Thu, 6 Mar 2003 13:58:21 +0100")'
  696. msg = Message()
  697. msg['Received-1'] = Header(h, header_name='Received-1',
  698. continuation_ws='\t')
  699. msg['Received-2'] = h
  700. self.assertEqual(msg.as_string(), """\
  701. Received-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
  702. \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
  703. Received-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de>
  704. \t(David Bremner's message of "Thu, 6 Mar 2003 13:58:21 +0100")
  705. """)
  706. def test_long_unbreakable_lines_with_continuation(self):
  707. eq = self.ndiffAssertEqual
  708. msg = Message()
  709. t = """\
  710. iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
  711. locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
  712. msg['Face-1'] = t
  713. msg['Face-2'] = Header(t, header_name='Face-2')
  714. eq(msg.as_string(), """\
  715. Face-1: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
  716. \tlocQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
  717. Face-2: iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
  718. locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
  719. """)
  720. def test_another_long_multiline_header(self):
  721. eq = self.ndiffAssertEqual
  722. m = '''\
  723. Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
  724. \tWed, 16 Oct 2002 07:41:11 -0700'''
  725. msg = email.message_from_string(m)
  726. eq(msg.as_string(), '''\
  727. Received: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
  728. \tMicrosoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
  729. ''')
  730. def test_long_lines_with_different_header(self):
  731. eq = self.ndiffAssertEqual
  732. h = """\
  733. List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
  734. <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>"""
  735. msg = Message()
  736. msg['List'] = h
  737. msg['List'] = Header(h, header_name='List')
  738. eq(msg.as_string(), """\
  739. List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
  740. \t<mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
  741. List: List-Unsubscribe: <https://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
  742. <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
  743. """)
  744. # Test mangling of "From " lines in the body of a message
  745. class TestFromMangling(unittest.TestCase):
  746. def setUp(self):
  747. self.msg = Message()
  748. self.msg['From'] = 'aaa@bbb.org'
  749. self.msg.set_payload("""\
  750. From the desk of A.A.A.:
  751. Blah blah blah
  752. """)
  753. def test_mangled_from(self):
  754. s = StringIO()
  755. g = Generator(s, mangle_from_=True)
  756. g.flatten(self.msg)
  757. self.assertEqual(s.getvalue(), """\
  758. From: aaa@bbb.org
  759. >From the desk of A.A.A.:
  760. Blah blah blah
  761. """)
  762. def test_dont_mangle_from(self):
  763. s = StringIO()
  764. g = Generator(s, mangle_from_=False)
  765. g.flatten(self.msg)
  766. self.assertEqual(s.getvalue(), """\
  767. From: aaa@bbb.org
  768. From the desk of A.A.A.:
  769. Blah blah blah
  770. """)
  771. # Test the basic MIMEAudio class
  772. class TestMIMEAudio(unittest.TestCase):
  773. def setUp(self):
  774. # Make sure we pick up the audiotest.au that lives in email/test/data.
  775. # In Python, there's an audiotest.au living in Lib/test but that isn't
  776. # included in some binary distros that don't include the test
  777. # package. The trailing empty string on the .join() is significant
  778. # since findfile() will do a dirname().
  779. datadir = os.path.join(os.path.dirname(landmark), 'data', '')
  780. fp = open(findfile('audiotest.au', datadir), 'rb')
  781. try:
  782. self._audiodata = fp.read()
  783. finally:
  784. fp.close()
  785. self._au = MIMEAudio(self._audiodata)
  786. def test_guess_minor_type(self):
  787. self.assertEqual(self._au.get_content_type(), 'audio/basic')
  788. def test_encoding(self):
  789. payload = self._au.get_payload()
  790. self.assertEqual(base64.decodestring(payload), self._audiodata)
  791. def test_checkSetMinor(self):
  792. au = MIMEAudio(self._audiodata, 'fish')
  793. self.assertEqual(au.get_content_type(), 'audio/fish')
  794. def test_add_header(self):
  795. eq = self.assertEqual
  796. unless = self.failUnless
  797. self._au.add_header('Content-Disposition', 'attachment',
  798. filename='audiotest.au')
  799. eq(self._au['content-disposition'],
  800. 'attachment; filename="audiotest.au"')
  801. eq(self._au.get_params(header='content-disposition'),
  802. [('attachment', ''), ('filename', 'audiotest.au')])
  803. eq(self._au.get_param('filename', header='content-disposition'),
  804. 'audiotest.au')
  805. missing = []
  806. eq(self._au.get_param('attachment', header='content-disposition'), '')
  807. unless(self._au.get_param('foo', failobj=missing,
  808. header='content-disposition') is missing)
  809. # Try some missing stuff
  810. unless(self._au.get_param('foobar', missing) is missing)
  811. unless(self._au.get_param('attachment', missing,
  812. header='foobar') is missing)
  813. # Test the basic MIMEImage class
  814. class TestMIMEImage(unittest.TestCase):
  815. def setUp(self):
  816. fp = openfile('PyBanner048.gif')
  817. try:
  818. self._imgdata = fp.read()
  819. finally:
  820. fp.close()
  821. self._im = MIMEImage(self._imgdata)
  822. def test_guess_minor_type(self):
  823. self.assertEqual(self._im.get_content_type(), 'image/gif')
  824. def test_encoding(self):
  825. payload = self._im.get_payload()
  826. self.assertEqual(base64.decodestring(payload), self._imgdata)
  827. def test_checkSetMinor(self):
  828. im = MIMEImage(self._imgdata, 'fish')
  829. self.assertEqual(im.get_content_type(), 'image/fish')
  830. def test_add_header(self):
  831. eq = self.assertEqual
  832. unless = self.failUnless
  833. self._im.add_header('Content-Disposition', 'attachment',
  834. filename='dingusfish.gif')
  835. eq(self._im['content-disposition'],
  836. 'attachment; filename="dingusfish.gif"')
  837. eq(self._im.get_params(header='content-disposition'),
  838. [('attachment', ''), ('filename', 'dingusfish.gif')])
  839. eq(self._im.get_param('filename', header='content-disposition'),
  840. 'dingusfish.gif')
  841. missing = []
  842. eq(self._im.get_param('attachment', header='content-disposition'), '')
  843. unless(self._im.get_param('foo', failobj=missing,
  844. header='content-disposition') is missing)
  845. # Try some missing stuff
  846. unless(self._im.get_param('foobar', missing) is missing)
  847. unless(self._im.get_param('attachment', missing,
  848. header='foobar') is missing)
  849. # Test the basic MIMEText class
  850. class TestMIMEText(unittest.TestCase):
  851. def setUp(self):
  852. self._msg = MIMEText('hello there')
  853. def test_types(self):
  854. eq = self.assertEqual
  855. unless = self.failUnless
  856. eq(self._msg.get_content_type(), 'text/plain')
  857. eq(self._msg.get_param('charset'), 'us-ascii')
  858. missing = []
  859. unless(self._msg.get_param('foobar', missing) is missing)
  860. unless(self._msg.get_param('charset', missing, header='foobar')
  861. is missing)
  862. def test_payload(self):
  863. self.assertEqual(self._msg.get_payload(), 'hello there')
  864. self.failUnless(not self._msg.is_multipart())
  865. def test_charset(self):
  866. eq = self.assertEqual
  867. msg = MIMEText('hello there', _charset='us-ascii')
  868. eq(msg.get_charset().input_charset, 'us-ascii')
  869. eq(msg['content-type'], 'text/plain; charset="us-ascii"')
  870. # Test complicated multipart/* messages
  871. class TestMultipart(TestEmailBase):
  872. def setUp(self):
  873. fp = openfile('PyBanner048.gif')
  874. try:
  875. data = fp.read()
  876. finally:
  877. fp.close()
  878. container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
  879. image = MIMEImage(data, name='dingusfish.gif')
  880. image.add_header('content-disposition', 'attachment',
  881. filename='dingusfish.gif')
  882. intro = MIMEText('''\
  883. Hi there,
  884. This is the dingus fish.
  885. ''')
  886. container.attach(intro)
  887. container.attach(image)
  888. container['From'] = 'Barry <barry@digicool.com>'
  889. container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
  890. container['Subject'] = 'Here is your dingus fish'
  891. now = 987809702.54848599
  892. timetuple = time.localtime(now)
  893. if timetuple[-1] == 0:
  894. tzsecs = time.timezone
  895. else:
  896. tzsecs = time.altzone
  897. if tzsecs > 0:
  898. sign = '-'
  899. else:
  900. sign = '+'
  901. tzoffset = ' %s%04d' % (sign, tzsecs / 36)
  902. container['Date'] = time.strftime(
  903. '%a, %d %b %Y %H:%M:%S',
  904. time.localtime(now)) + tzoffset
  905. self._msg = container
  906. self._im = image
  907. self._txt = intro
  908. def test_hierarchy(self):
  909. # convenience
  910. eq = self.assertEqual
  911. unless = self.failUnless
  912. raises = self.assertRaises
  913. # tests
  914. m = self._msg
  915. unless(m.is_multipart())
  916. eq(m.get_content_type(), 'multipart/mixed')
  917. eq(len(m.get_payload()), 2)
  918. raises(IndexError, m.get_payload, 2)
  919. m0 = m.get_payload(0)
  920. m1 = m.get_payload(1)
  921. unless(m0 is self._txt)
  922. unless(m1 is self._im)
  923. eq(m.get_payload(), [m0, m1])
  924. unless(not m0.is_multipart())
  925. unless(not m1.is_multipart())
  926. def test_empty_multipart_idempotent(self):
  927. text = """\
  928. Content-Type: multipart/mixed; boundary="BOUNDARY"
  929. MIME-Version: 1.0
  930. Subject: A subject
  931. To: aperson@dom.ain
  932. From: bperson@dom.ain
  933. --BOUNDARY
  934. --BOUNDARY--
  935. """
  936. msg = Parser().parsestr(text)
  937. self.ndiffAssertEqual(text, msg.as_string())
  938. def test_no_parts_in_a_multipart_with_none_epilogue(self):
  939. outer = MIMEBase('multipart', 'mixed')
  940. outer['Subject'] = 'A subject'
  941. outer['To'] = 'aperson@dom.ain'
  942. outer['From'] = 'bperson@dom.ain'
  943. outer.set_boundary('BOUNDARY')
  944. self.ndiffAssertEqual(outer.as_string(), '''\
  945. Content-Type: multipart/mixed; boundary="BOUNDARY"
  946. MIME-Version: 1.0
  947. Subject: A subject
  948. To: aperson@dom.ain
  949. From: bperson@dom.ain
  950. --BOUNDARY
  951. --BOUNDARY--''')
  952. def test_no_parts_in_a_multipart_with_empty_epilogue(self):
  953. outer = MIMEBase('multipart', 'mixed')
  954. outer['Subject'] = 'A subject'
  955. outer['To'] = 'aperson@dom.ain'
  956. outer['From'] = 'bperson@dom.ain'
  957. outer.preamble = ''
  958. outer.epilogue = ''
  959. outer.set_boundary('BOUNDARY')
  960. self.ndiffAssertEqual(outer.as_string(), '''\
  961. Content-Type: multipart/mixed; boundary="BOUNDARY"
  962. MIME-Version: 1.0
  963. Subject: A subject
  964. To: aperson@dom.ain
  965. From: bperson@dom.ain
  966. --BOUNDARY
  967. --BOUNDARY--
  968. ''')
  969. def test_one_part_in_a_multipart(self):
  970. eq = self.ndiffAssertEqual
  971. outer = MIMEBase('multipart', 'mixed')
  972. outer['Subject'] = 'A subject'
  973. outer['To'] = 'aperson@dom.ain'
  974. outer['From'] = 'bperson@dom.ain'
  975. outer.set_boundary('BOUNDARY')
  976. msg = MIMEText('hello world')
  977. outer.attach(msg)
  978. eq(outer.as_string(), '''\
  979. Content-Type: multipart/mixed; boundary="BOUNDARY"
  980. MIME-Version: 1.0
  981. Subject: A subject
  982. To: aperson@dom.ain
  983. From: bperson@dom.ain
  984. --BOUNDARY
  985. Content-Type: text/plain; charset="us-ascii"
  986. MIME-Version: 1.0
  987. Content-Transfer-Encoding: 7bit
  988. hello world
  989. --BOUNDARY--''')
  990. def test_seq_parts_in_a_multipart_with_empty_preamble(self):
  991. eq = self.ndiffAssertEqual
  992. outer = MIMEBase('multipart', 'mixed')
  993. outer['Subject'] = 'A subject'
  994. outer['To'] = 'aperson@dom.ain'
  995. outer['From'] = 'bperson@dom.ain'
  996. outer.preamble = ''
  997. msg = MIMEText('hello world')
  998. outer.attach(msg)
  999. outer.set_boundary('BOUNDARY')
  1000. eq(outer.as_string(), '''\
  1001. Content-Type: multipart/mixed; boundary="BOUNDARY"
  1002. MIME-Version: 1.0
  1003. Subject: A subject
  1004. To: aperson@dom.ain
  1005. From: bperson@dom.ain
  1006. --BOUNDARY
  1007. Content-Type: text/plain; charset="us-ascii"
  1008. MIME-Version: 1.0
  1009. Content-Transfer-Encoding: 7bit
  1010. hello world
  1011. --BOUNDARY--''')
  1012. def test_seq_parts_in_a_multipart_with_none_preamble(self):
  1013. eq = self.ndiffAssertEqual
  1014. outer = MIMEBase('multipart', 'mixed')
  1015. outer['Subject'] = 'A subject'
  1016. outer['To'] = 'aperson@dom.ain'
  1017. outer['From'] = 'bperson@dom.ain'
  1018. outer.preamble = None
  1019. msg = MIMEText('hello world')
  1020. outer.attach(msg)
  1021. outer.set_boundary('BOUNDARY')
  1022. eq(outer.as_string(), '''\
  1023. Content-Type: multipart/mixed; boundary="BOUNDARY"
  1024. MIME-Version: 1.0
  1025. Subject: A subject
  1026. To: aperson@dom.ain
  1027. From: bperson@dom.ain
  1028. --BOUNDARY
  1029. Content-Type: text/plain; charset="us-ascii"
  1030. MIME-Version: 1.0
  1031. Content-Transfer-Encoding: 7bit
  1032. hello world
  1033. --BOUNDARY--''')
  1034. def test_seq_parts_in_a_multipart_with_none_epilogue(self):
  1035. eq = self.ndiffAssertEqual
  1036. outer = MIMEBase('multipart', 'mixed')
  1037. outer['Subject'] = 'A subject'
  1038. outer['To'] = 'aperson@dom.ain'
  1039. outer['From'] = 'bperson@dom.ain'
  1040. outer.epilogue = None
  1041. msg = MIMEText('hello world')
  1042. outer.attach(msg)
  1043. outer.set_boundary('BOUNDARY')
  1044. eq(outer.as_string(), '''\
  1045. Content-Type: multipart/mixed; boundary="BOUNDARY"
  1046. MIME-Version: 1.0
  1047. Subject: A subject
  1048. To: aperson@dom.ain
  1049. From: bperson@dom.ain
  1050. --BOUNDARY
  1051. Content-Type: text/plain; charset="us-ascii"
  1052. MIME-Version: 1.0
  1053. Content-Transfer-Encoding: 7bit
  1054. hello world
  1055. --BOUNDARY--''')
  1056. def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
  1057. eq = self.ndiffAssertEqual
  1058. outer = MIMEBase('multipart', 'mixed')
  1059. outer['Subject'] = 'A subject'
  1060. outer['To'] = 'aperson@dom.ain'
  1061. outer['From'] = 'bperson@dom.ain'
  1062. outer.epilogue = ''
  1063. msg = MIMEText('hello world')
  1064. outer.attach(msg)
  1065. outer.set_boundary('BOUNDARY')
  1066. eq(outer.as_string(), '''\
  1067. Content-Type: multipart/mixed; boundary="BOUNDARY"
  1068. MIME-Version: 1.0
  1069. Subject: A subject
  1070. To: aperson@dom.ain
  1071. From: bperson@dom.ain
  1072. --BOUNDARY
  1073. Content-Type: text/plain; charset="us-ascii"
  1074. MIME-Version: 1.0
  1075. Content-Transfer-Encoding: 7bit
  1076. hello world
  1077. --BOUNDARY--
  1078. ''')
  1079. def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
  1080. eq = self.ndiffAssertEqual
  1081. outer = MIMEBase('multipart', 'mixed')
  1082. outer['Subject'] = 'A subject'
  1083. outer['To'] = 'aperson@dom.ain'
  1084. outer['From'] = 'bperson@dom.ain'
  1085. outer.epilogue = '\n'
  1086. msg = MIMEText('hello world')
  1087. outer.attach(msg)
  1088. outer.set_boundary('BOUNDARY')
  1089. eq(outer.as_string(), '''\
  1090. Content-Type: multipart/mixed; boundary="BOUNDARY"
  1091. MIME-Version: 1.0
  1092. Subject: A subject
  1093. To: aperson@dom.ain
  1094. From: bperson@dom.ain
  1095. --BOUNDARY
  1096. Content-Type: text/plain; charset="us-ascii"
  1097. MIME-Version: 1.0
  1098. Content-Transfer-Encoding: 7bit
  1099. hello world
  1100. --BOUNDARY--
  1101. ''')
  1102. def test_message_external_body(self):
  1103. eq = self.assertEqual
  1104. msg = self._msgobj('msg_36.txt')
  1105. eq(len(msg.get_payload()), 2)
  1106. msg1 = msg.get_payload(1)
  1107. eq(msg1.get_content_type(), 'multipart/alternative')
  1108. eq(len(msg1.get_payload()), 2)
  1109. for subpart in msg1.get_payload():
  1110. eq(subpart.get_content_type(), 'message/external-body')
  1111. eq(len(subpart.get_payload()), 1)
  1112. subsubpart = subpart.get_payload(0)
  1113. eq(subsubpart.get_content_type(), 'text/plain')
  1114. def test_double_boundary(self):
  1115. # msg_37.txt is a multipart that contains two dash-boundary's in a
  1116. # row. Our interpretation of RFC 2046 calls for ignoring the second
  1117. # and subsequent boundaries.
  1118. msg = self._msgobj('msg_37.txt')
  1119. self.assertEqual(len(msg.get_payload()), 3)
  1120. def test_nested_inner_contains_outer_boundary(self):
  1121. eq = self.ndiffAssertEqual
  1122. # msg_38.txt has an inner part that contains outer boundaries. My
  1123. # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
  1124. # these are illegal and should be interpreted as unterminated inner
  1125. # parts.
  1126. msg = self._msgobj('msg_38.txt')
  1127. sfp = StringIO()
  1128. Iterators._structure(msg, sfp)
  1129. eq(sfp.getvalue(), """\
  1130. multipart/mixed
  1131. multipart/mixed
  1132. multipart/alternative
  1133. text/plain
  1134. text/plain
  1135. text/plain
  1136. text/plain
  1137. """)
  1138. def test_nested_with_same_boundary(self):
  1139. eq = self.ndiffAssertEqual
  1140. # msg 39.txt is similarly evil in that it's got inner parts that use
  1141. # the same boundary as outer parts. Again, I believ…