/Lib/email/test/test_email_renamed.py

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