cpython/Lib/test/test_email.py

798 lines
24 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Copyright (C) 2001 Python Software Foundation
# email package unit tests
import os
import time
import unittest
import base64
from cStringIO import StringIO
from types import StringType
import email
from email.Parser import Parser
from email.Generator import Generator, DecodedGenerator
from email.Message import Message
from email.Text import Text
from email.Image import Image
from email.MIMEBase import MIMEBase
from email.MessageRFC822 import MessageRFC822
from email import Utils
from email import Errors
from email import Encoders
from email import Iterators
import test.regrtest
NL = '\n'
EMPTYSTRING = ''
def openfile(filename):
path = os.path.join(os.path.dirname(test.regrtest.__file__),
'data', filename)
return open(path)
# Base test class
class TestEmailBase(unittest.TestCase):
def _msgobj(self, filename):
fp = openfile(filename)
try:
msgobj = email.message_from_file(fp)
finally:
fp.close()
return msgobj
# Test various aspects of the Message class's API
class TestMessageAPI(TestEmailBase):
def test_get_charsets(self):
eq = self.assertEqual
msgobj = self._msgobj('msg_08.txt')
charsets = msgobj.get_charsets()
eq(charsets, ['us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
msgobj = self._msgobj('msg_09.txt')
charsets = msgobj.get_charsets('dingbat')
eq(charsets, ['us-ascii', 'iso-8859-1', 'dingbat', 'koi8-r'])
msgobj = self._msgobj('msg_12.txt')
charsets = msgobj.get_charsets()
eq(charsets, ['us-ascii', 'iso-8859-1', 'iso-8859-2', 'iso-8859-3',
'us-ascii', 'koi8-r'])
def test_get_filename(self):
eq = self.assertEqual
msgobj = self._msgobj('msg_04.txt')
filenames = [p.get_filename() for p in msgobj.get_payload()]
eq(filenames, ['msg.txt', 'msg.txt'])
msgobj = self._msgobj('msg_07.txt')
subpart = msgobj.get_payload(1)
eq(subpart.get_filename(), 'dingusfish.gif')
def test_get_boundary(self):
eq = self.assertEqual
msgobj = self._msgobj('msg_07.txt')
# No quotes!
eq(msgobj.get_boundary(), 'BOUNDARY')
def test_set_boundary(self):
eq = self.assertEqual
# This one has no existing boundary parameter, but the Content-Type:
# header appears fifth.
msgobj = self._msgobj('msg_01.txt')
msgobj.set_boundary('BOUNDARY')
header, value = msgobj.items()[4]
eq(header.lower(), 'content-type')
eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
# This one has a Content-Type: header, with a boundary, stuck in the
# middle of its headers. Make sure the order is preserved; it should
# be fifth.
msgobj = self._msgobj('msg_04.txt')
msgobj.set_boundary('BOUNDARY')
header, value = msgobj.items()[4]
eq(header.lower(), 'content-type')
eq(value, 'multipart/mixed; boundary="BOUNDARY"')
# And this one has no Content-Type: header at all.
msgobj = self._msgobj('msg_03.txt')
self.assertRaises(Errors.HeaderParseError,
msgobj.set_boundary, 'BOUNDARY')
def test_get_decoded_payload(self):
eq = self.assertEqual
msgobj = self._msgobj('msg_10.txt')
# The outer message is a multipart
eq(msgobj.get_payload(decode=1), None)
# Subpart 1 is 7bit encoded
eq(msgobj.get_payload(0).get_payload(decode=1),
'This is a 7bit encoded message.\n')
# Subpart 2 is quopri
eq(msgobj.get_payload(1).get_payload(decode=1),
'\xa1This is a Quoted Printable encoded message!\n')
# Subpart 3 is base64
eq(msgobj.get_payload(2).get_payload(decode=1),
'This is a Base64 encoded message.')
# Subpart 4 has no Content-Transfer-Encoding: header.
eq(msgobj.get_payload(3).get_payload(decode=1),
'This has no Content-Transfer-Encoding: header.\n')
def test_decoded_generator(self):
eq = self.assertEqual
msgobj = self._msgobj('msg_07.txt')
s = StringIO()
g = DecodedGenerator(s)
g(msgobj)
eq(s.getvalue(), '''\
MIME-Version: 1.0
From: Barry <barry@digicool.com>
To: Dingus Lovers <cravindogs@cravindogs.com>
Subject: Here is your dingus fish
Date: Fri, 20 Apr 2001 19:35:02 -0400
Content-Type: multipart/mixed; boundary="BOUNDARY"
Hi there,
This is the dingus fish.
[Non-text (image/gif) part of message omitted, filename dingusfish.gif]
''')
def test__contains__(self):
msg = Message()
msg['From'] = 'Me'
msg['to'] = 'You'
# Check for case insensitivity
self.failUnless('from' in msg)
self.failUnless('From' in msg)
self.failUnless('FROM' in msg)
self.failUnless('to' in msg)
self.failUnless('To' in msg)
self.failUnless('TO' in msg)
def test_as_string(self):
eq = self.assertEqual
msgobj = self._msgobj('msg_01.txt')
fp = openfile('msg_01.txt')
try:
text = fp.read()
finally:
fp.close()
eq(text, msgobj.as_string())
fullrepr = str(msgobj)
lines = fullrepr.split('\n')
self.failUnless(lines[0].startswith('From '))
eq(text, NL.join(lines[1:]))
def test_bad_param(self):
msg = email.message_from_string("""\
From: foo
Content-Type: blarg; baz; boo
""")
self.assertEqual(msg.get_param('baz'), '')
def test_missing_filename(self):
msg = email.message_from_string("""\
From: foo
""")
self.assertEqual(msg.get_filename(), None)
def test_bogus_filename(self):
msg = email.message_from_string("""\
From: foo
Content-Disposition: blarg; filename
""")
self.assertEqual(msg.get_filename(), '')
def test_missing_boundary(self):
msg = email.message_from_string("""\
From: foo
""")
self.assertEqual(msg.get_boundary(), None)
# Test the email.Encoders module
class TestEncoders(unittest.TestCase):
def test_encode_noop(self):
eq = self.assertEqual
msg = Text('hello world', _encoder=Encoders.encode_noop)
eq(msg.get_payload(), 'hello world\n')
eq(msg['content-transfer-encoding'], None)
def test_encode_7bit(self):
eq = self.assertEqual
msg = Text('hello world', _encoder=Encoders.encode_7or8bit)
eq(msg.get_payload(), 'hello world\n')
eq(msg['content-transfer-encoding'], '7bit')
msg = Text('hello \x7f world', _encoder=Encoders.encode_7or8bit)
eq(msg.get_payload(), 'hello \x7f world\n')
eq(msg['content-transfer-encoding'], '7bit')
def test_encode_8bit(self):
eq = self.assertEqual
msg = Text('hello \x80 world', _encoder=Encoders.encode_7or8bit)
eq(msg.get_payload(), 'hello \x80 world\n')
eq(msg['content-transfer-encoding'], '8bit')
def test_encode_base64(self):
eq = self.assertEqual
msg = Text('hello world', _encoder=Encoders.encode_base64)
eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
eq(msg['content-transfer-encoding'], 'base64')
def test_encode_quoted_printable(self):
eq = self.assertEqual
msg = Text('hello world', _encoder=Encoders.encode_quopri)
eq(msg.get_payload(), 'hello=20world\n')
eq(msg['content-transfer-encoding'], 'quoted-printable')
class TestLongHeaders(unittest.TestCase):
def test_header_splitter(self):
msg = Text('')
# It'd be great if we could use add_header() here, but that doesn't
# guarantee an order of the parameters.
msg['X-Foobar-Spoink-Defrobnit'] = (
'wasnipoop; giraffes="very-long-necked-animals"; '
'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
sfp = StringIO()
g = Generator(sfp)
g(msg)
self.assertEqual(sfp.getvalue(), '''\
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
''')
class TestFromMangling(unittest.TestCase):
def setUp(self):
self.msg = Message()
self.msg['From'] = 'aaa@bbb.org'
self.msg.add_payload("""\
From the desk of A.A.A.:
Blah blah blah
""")
def test_mangled_from(self):
s = StringIO()
g = Generator(s, mangle_from_=1)
g(self.msg)
self.assertEqual(s.getvalue(), """\
From: aaa@bbb.org
>From the desk of A.A.A.:
Blah blah blah
""")
def test_dont_mangle_from(self):
s = StringIO()
g = Generator(s, mangle_from_=0)
g(self.msg)
self.assertEqual(s.getvalue(), """\
From: aaa@bbb.org
From the desk of A.A.A.:
Blah blah blah
""")
# Test the basic Image class
class TestImage(unittest.TestCase):
def setUp(self):
fp = openfile('PyBanner048.gif')
try:
self._imgdata = fp.read()
finally:
fp.close()
self._im = Image(self._imgdata)
def test_guess_minor_type(self):
self.assertEqual(self._im.get_type(), 'image/gif')
def test_encoding(self):
payload = self._im.get_payload()
self.assertEqual(base64.decodestring(payload), self._imgdata)
def checkSetMinor(self):
im = Image(self._imgdata, 'fish')
self.assertEqual(im.get_type(), 'image/fish')
def test_custom_encoder(self):
eq = self.assertEqual
def encoder(msg):
orig = msg.get_payload()
msg.set_payload(0)
msg['Content-Transfer-Encoding'] = 'broken64'
im = Image(self._imgdata, _encoder=encoder)
eq(im.get_payload(), 0)
eq(im['content-transfer-encoding'], 'broken64')
def test_add_header(self):
eq = self.assertEqual
unless = self.failUnless
self._im.add_header('Content-Disposition', 'attachment',
filename='dingusfish.gif')
eq(self._im['content-disposition'],
'attachment; filename="dingusfish.gif"')
eq(self._im.get_params(header='content-disposition'),
['filename="dingusfish.gif"'])
eq(self._im.get_param('filename', header='content-disposition'),
'dingusfish.gif')
missing = []
unless(self._im.get_param('attachment', missing,
header='content-disposition') is missing)
# Try some missing stuff
unless(self._im.get_param('foobar', missing) is missing)
unless(self._im.get_param('attachment', missing,
header='foobar') is missing)
# Test the basic Text class
class TestText(unittest.TestCase):
def setUp(self):
self._msg = Text('hello there')
def test_types(self):
eq = self.assertEqual
unless = self.failUnless
eq(self._msg.get_type(), 'text/plain')
eq(self._msg.get_param('charset'), 'us-ascii')
missing = []
unless(self._msg.get_param('foobar', missing) is missing)
unless(self._msg.get_param('charset', missing, header='foobar')
is missing)
def test_payload(self):
self.assertEqual(self._msg.get_payload(), 'hello there\n')
self.failUnless(not self._msg.is_multipart())
class TestMultipartMixed(unittest.TestCase):
def setUp(self):
fp = openfile('PyBanner048.gif')
try:
data = fp.read()
finally:
fp.close()
container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
image = Image(data, name='dingusfish.gif')
image.add_header('content-disposition', 'attachment',
filename='dingusfish.gif')
intro = Text('''\
Hi there,
This is the dingus fish.
''')
container.add_payload(intro)
container.add_payload(image)
container['From'] = 'Barry <barry@digicool.com>'
container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
container['Subject'] = 'Here is your dingus fish'
now = 987809702.54848599
timetuple = time.localtime(now)
if timetuple[-1] == 0:
tzsecs = time.timezone
else:
tzsecs = time.altzone
if tzsecs > 0:
sign = '-'
else:
sign = '+'
tzoffset = ' %s%04d' % (sign, tzsecs / 36)
container['Date'] = time.strftime(
'%a, %d %b %Y %H:%M:%S',
time.localtime(now)) + tzoffset
self._msg = container
self._im = image
self._txt = intro
def test_hierarchy(self):
# convenience
eq = self.assertEqual
unless = self.failUnless
raises = self.assertRaises
# tests
m = self._msg
unless(m.is_multipart())
eq(m.get_type(), 'multipart/mixed')
eq(len(m.get_payload()), 2)
raises(IndexError, m.get_payload, 2)
m0 = m.get_payload(0)
m1 = m.get_payload(1)
unless(m0 is self._txt)
unless(m1 is self._im)
eq(m.get_payload(), [m0, m1])
unless(not m0.is_multipart())
unless(not m1.is_multipart())
class TestNonConformant(TestEmailBase):
def test_parse_missing_minor_type(self):
eq = self.assertEqual
msg = self._msgobj('msg_14.txt')
eq(msg.get_type(), 'text')
eq(msg.get_main_type(), 'text')
self.failUnless(msg.get_subtype() is None)
def test_bogus_boundary(self):
fp = openfile('msg_15.txt')
try:
data = fp.read()
finally:
fp.close()
p = Parser()
# Note, under a future non-strict parsing mode, this would parse the
# message into the intended message tree.
self.assertRaises(Errors.BoundaryError, p.parsestr, data)
class TestRFC2047(unittest.TestCase):
def test_iso_8859_1(self):
eq = self.assertEqual
s = '=?iso-8859-1?q?this=20is=20some=20text?='
eq(Utils.decode(s), 'this is some text')
s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
eq(Utils.decode(s), u'Keld_J\xf8rn_Simonsen')
s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
'=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
eq(Utils.decode(s), 'If you can read this you understand the example.')
s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
eq(Utils.decode(s),
u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
eq(Utils.decode(s), u'this is some text')
def test_encode_header(self):
eq = self.assertEqual
s = 'this is some text'
eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
s = 'Keld_J\xf8rn_Simonsen'
eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
s1 = 'If you can read this yo'
s2 = 'u understand the example.'
eq(Utils.encode(s1, encoding='b'),
'=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
'=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
class TestMessageRFC822(unittest.TestCase):
def setUp(self):
fp = openfile('msg_11.txt')
self._text = fp.read()
fp.close()
def test_type_error(self):
self.assertRaises(TypeError, MessageRFC822, 'a plain string')
def test_valid_argument(self):
eq = self.assertEqual
subject = 'A sub-message'
m = Message()
m['Subject'] = subject
r = MessageRFC822(m)
eq(r.get_type(), 'message/rfc822')
self.failUnless(r.get_payload() is m)
eq(r.get_payload()['subject'], subject)
def test_generate(self):
# First craft the message to be encapsulated
m = Message()
m['Subject'] = 'An enclosed message'
m.add_payload('Here is the body of the message.\n')
r = MessageRFC822(m)
r['Subject'] = 'The enclosing message'
s = StringIO()
g = Generator(s)
g(r)
self.assertEqual(s.getvalue(), self._text)
class TestIdempotent(unittest.TestCase):
def _msgobj(self, filename):
fp = openfile(filename)
try:
data = fp.read()
finally:
fp.close()
msgobj = email.message_from_string(data)
return msgobj, data
def _idempotent(self, msg, text):
eq = self.assertEquals
s = StringIO()
g = Generator(s, maxheaderlen=0)
g(msg)
eq(text, s.getvalue())
def test_parse_text_message(self):
eq = self.assertEquals
msg, text = self._msgobj('msg_01.txt')
eq(msg.get_type(), 'text/plain')
eq(msg.get_main_type(), 'text')
eq(msg.get_subtype(), 'plain')
eq(msg.get_params(), ['charset=us-ascii'])
eq(msg.get_param('charset'), 'us-ascii')
eq(msg.preamble, None)
eq(msg.epilogue, None)
self._idempotent(msg, text)
def test_parse_untyped_message(self):
eq = self.assertEquals
msg, text = self._msgobj('msg_03.txt')
eq(msg.get_type(), None)
eq(msg.get_params(), None)
eq(msg.get_param('charset'), None)
self._idempotent(msg, text)
def test_simple_multipart(self):
msg, text = self._msgobj('msg_04.txt')
self._idempotent(msg, text)
def test_MIME_digest(self):
msg, text = self._msgobj('msg_02.txt')
self._idempotent(msg, text)
def test_mixed_with_image(self):
msg, text = self._msgobj('msg_06.txt')
self._idempotent(msg, text)
def test_multipart_report(self):
msg, text = self._msgobj('msg_05.txt')
self._idempotent(msg, text)
def test_content_type(self):
eq = self.assertEquals
# Get a message object and reset the seek pointer for other tests
msg, text = self._msgobj('msg_05.txt')
eq(msg.get_type(), 'multipart/report')
# Test the Content-Type: parameters
params = {}
for p in msg.get_params():
pk, pv = p.split('=', 1)
params[pk] = pv
eq(params['report-type'], 'delivery-status')
eq(params['boundary'], '"D1690A7AC1.996856090/mail.example.com"')
eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
eq(msg.epilogue, '\n\n')
eq(len(msg.get_payload()), 3)
# Make sure the subparts are what we expect
msg1 = msg.get_payload(0)
eq(msg1.get_type(), 'text/plain')
eq(msg1.get_payload(), 'Yadda yadda yadda\n')
msg2 = msg.get_payload(1)
eq(msg2.get_type(), None)
eq(msg2.get_payload(), 'Yadda yadda yadda\n')
msg3 = msg.get_payload(2)
eq(msg3.get_type(), 'message/rfc822')
self.failUnless(isinstance(msg3, Message))
msg4 = msg3.get_payload()
self.failUnless(isinstance(msg4, Message))
eq(msg4.get_payload(), 'Yadda yadda yadda\n')
def test_parser(self):
eq = self.assertEquals
msg, text = self._msgobj('msg_06.txt')
# Check some of the outer headers
eq(msg.get_type(), 'message/rfc822')
# Make sure there's exactly one thing in the payload and that's a
# sub-Message object of type text/plain
msg1 = msg.get_payload()
self.failUnless(isinstance(msg1, Message))
eq(msg1.get_type(), 'text/plain')
self.failUnless(isinstance(msg1.get_payload(), StringType))
eq(msg1.get_payload(), '\n')
class TestMiscellaneous(unittest.TestCase):
def test_message_from_string(self):
fp = openfile('msg_01.txt')
try:
text = fp.read()
finally:
fp.close()
msg = email.message_from_string(text)
s = StringIO()
# Don't wrap/continue long headers since we're trying to test
# idempotency.
g = Generator(s, maxheaderlen=0)
g(msg)
self.assertEqual(text, s.getvalue())
def test_message_from_file(self):
fp = openfile('msg_01.txt')
try:
text = fp.read()
fp.seek(0)
msg = email.message_from_file(fp)
s = StringIO()
# Don't wrap/continue long headers since we're trying to test
# idempotency.
g = Generator(s, maxheaderlen=0)
g(msg)
self.assertEqual(text, s.getvalue())
finally:
fp.close()
def test_message_from_string_with_class(self):
unless = self.failUnless
fp = openfile('msg_01.txt')
try:
text = fp.read()
finally:
fp.close()
# Create a subclass
class MyMessage(Message):
pass
msg = email.message_from_string(text, MyMessage)
unless(isinstance(msg, MyMessage))
# Try something more complicated
fp = openfile('msg_02.txt')
try:
text = fp.read()
finally:
fp.close()
msg = email.message_from_string(text, MyMessage)
for subpart in msg.walk():
unless(isinstance(subpart, MyMessage))
def test_message_from_file_with_class(self):
unless = self.failUnless
# Create a subclass
class MyMessage(Message):
pass
fp = openfile('msg_01.txt')
try:
msg = email.message_from_file(fp, MyMessage)
finally:
fp.close()
unless(isinstance(msg, MyMessage))
# Try something more complicated
fp = openfile('msg_02.txt')
try:
msg = email.message_from_file(fp, MyMessage)
finally:
fp.close()
for subpart in msg.walk():
unless(isinstance(subpart, MyMessage))
class TestIterators(TestEmailBase):
def test_body_line_iterator(self):
eq = self.assertEqual
# First a simple non-multipart message
msg = self._msgobj('msg_01.txt')
it = Iterators.body_line_iterator(msg)
lines = []
for line in it:
lines.append(line)
eq(len(lines), 6)
eq(EMPTYSTRING.join(lines), msg.get_payload())
# Now a more complicated multipart
msg = self._msgobj('msg_02.txt')
it = Iterators.body_line_iterator(msg)
lines = []
for line in it:
lines.append(line)
eq(len(lines), 43)
eq(EMPTYSTRING.join(lines), """\
Send Ppp mailing list submissions to
ppp@zzz.org
To subscribe or unsubscribe via the World Wide Web, visit
http://www.zzz.org/mailman/listinfo/ppp
or, via email, send a message with subject or body 'help' to
ppp-request@zzz.org
You can reach the person managing the list at
ppp-admin@zzz.org
When replying, please edit your Subject line so it is more specific
than "Re: Contents of Ppp digest..."
Today's Topics:
1. testing #1 (Barry A. Warsaw)
2. testing #2 (Barry A. Warsaw)
3. testing #3 (Barry A. Warsaw)
4. testing #4 (Barry A. Warsaw)
5. testing #5 (Barry A. Warsaw)
hello
hello
hello
hello
hello
_______________________________________________
Ppp mailing list
Ppp@zzz.org
http://www.zzz.org/mailman/listinfo/ppp
""")
def test_typed_subpart_iterator(self):
eq = self.assertEqual
msg = self._msgobj('msg_04.txt')
it = Iterators.typed_subpart_iterator(msg, 'text')
lines = []
subparts = 0
for subpart in it:
subparts += 1
lines.append(subpart.get_payload())
eq(subparts, 2)
eq(EMPTYSTRING.join(lines), """\
a simple kind of mirror
to reflect upon our own
a simple kind of mirror
to reflect upon our own
""")
def suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(TestMessageAPI))
suite.addTest(unittest.makeSuite(TestEncoders))
suite.addTest(unittest.makeSuite(TestLongHeaders))
suite.addTest(unittest.makeSuite(TestFromMangling))
suite.addTest(unittest.makeSuite(TestImage))
suite.addTest(unittest.makeSuite(TestText))
suite.addTest(unittest.makeSuite(TestMultipartMixed))
suite.addTest(unittest.makeSuite(TestNonConformant))
suite.addTest(unittest.makeSuite(TestRFC2047))
suite.addTest(unittest.makeSuite(TestMessageRFC822))
suite.addTest(unittest.makeSuite(TestIdempotent))
suite.addTest(unittest.makeSuite(TestMiscellaneous))
suite.addTest(unittest.makeSuite(TestIterators))
return suite
if __name__ == '__main__':
unittest.main(defaultTest='suite')
else:
from test_support import run_suite
run_suite(suite())