PageRenderTime 76ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

/lib-python/2.7/email/test/test_email.py

https://bitbucket.org/pwaller/pypy
Python | 3529 lines | 3431 code | 43 blank | 55 comment | 9 complexity | 365340ec651367d3cf81fdff842ea2a4 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

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

Large files files are truncated, but you can click here to view the full file