»Core Development>Code coverage>Lib/email/test/test_email.py

Python code coverage for Lib/email/test/test_email.py

#countcontent
1n/a# Copyright (C) 2001-2010 Python Software Foundation
2n/a# Contact: email-sig@python.org
3n/a# email package unit tests
4n/a
5n/aimport os
6n/aimport re
7n/aimport sys
8n/aimport time
9n/aimport base64
10n/aimport difflib
11n/aimport unittest
12n/aimport warnings
13n/aimport textwrap
14n/a
15n/afrom io import StringIO, BytesIO
16n/afrom itertools import chain
17n/a
18n/aimport email
19n/a
20n/afrom email.charset import Charset
21n/afrom email.header import Header, decode_header, make_header
22n/afrom email.parser import Parser, HeaderParser
23n/afrom email.generator import Generator, DecodedGenerator
24n/afrom email.message import Message
25n/afrom email.mime.application import MIMEApplication
26n/afrom email.mime.audio import MIMEAudio
27n/afrom email.mime.text import MIMEText
28n/afrom email.mime.image import MIMEImage
29n/afrom email.mime.base import MIMEBase
30n/afrom email.mime.message import MIMEMessage
31n/afrom email.mime.multipart import MIMEMultipart
32n/afrom email import utils
33n/afrom email import errors
34n/afrom email import encoders
35n/afrom email import iterators
36n/afrom email import base64mime
37n/afrom email import quoprimime
38n/a
39n/afrom test.support import findfile, run_unittest, unlink
40n/afrom email.test import __file__ as landmark
41n/a
42n/a
43n/aNL = '\n'
44n/aEMPTYSTRING = ''
45n/aSPACE = ' '
46n/a
47n/a
48n/a
49n/adef openfile(filename, *args, **kws):
50n/a path = os.path.join(os.path.dirname(landmark), 'data', filename)
51n/a return open(path, *args, **kws)
52n/a
53n/a
54n/a
55n/a# Base test class
56n/aclass TestEmailBase(unittest.TestCase):
57n/a def ndiffAssertEqual(self, first, second):
58n/a """Like assertEqual except use ndiff for readable output."""
59n/a if first != second:
60n/a sfirst = str(first)
61n/a ssecond = str(second)
62n/a rfirst = [repr(line) for line in sfirst.splitlines()]
63n/a rsecond = [repr(line) for line in ssecond.splitlines()]
64n/a diff = difflib.ndiff(rfirst, rsecond)
65n/a raise self.failureException(NL + NL.join(diff))
66n/a
67n/a def _msgobj(self, filename):
68n/a with openfile(findfile(filename)) as fp:
69n/a return email.message_from_file(fp)
70n/a
71n/a
72n/a
73n/a# Test various aspects of the Message class's API
74n/aclass TestMessageAPI(TestEmailBase):
75n/a def test_get_all(self):
76n/a eq = self.assertEqual
77n/a msg = self._msgobj('msg_20.txt')
78n/a eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
79n/a eq(msg.get_all('xx', 'n/a'), 'n/a')
80n/a
81n/a def test_getset_charset(self):
82n/a eq = self.assertEqual
83n/a msg = Message()
84n/a eq(msg.get_charset(), None)
85n/a charset = Charset('iso-8859-1')
86n/a msg.set_charset(charset)
87n/a eq(msg['mime-version'], '1.0')
88n/a eq(msg.get_content_type(), 'text/plain')
89n/a eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
90n/a eq(msg.get_param('charset'), 'iso-8859-1')
91n/a eq(msg['content-transfer-encoding'], 'quoted-printable')
92n/a eq(msg.get_charset().input_charset, 'iso-8859-1')
93n/a # Remove the charset
94n/a msg.set_charset(None)
95n/a eq(msg.get_charset(), None)
96n/a eq(msg['content-type'], 'text/plain')
97n/a # Try adding a charset when there's already MIME headers present
98n/a msg = Message()
99n/a msg['MIME-Version'] = '2.0'
100n/a msg['Content-Type'] = 'text/x-weird'
101n/a msg['Content-Transfer-Encoding'] = 'quinted-puntable'
102n/a msg.set_charset(charset)
103n/a eq(msg['mime-version'], '2.0')
104n/a eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
105n/a eq(msg['content-transfer-encoding'], 'quinted-puntable')
106n/a
107n/a def test_set_charset_from_string(self):
108n/a eq = self.assertEqual
109n/a msg = Message()
110n/a msg.set_charset('us-ascii')
111n/a eq(msg.get_charset().input_charset, 'us-ascii')
112n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
113n/a
114n/a def test_set_payload_with_charset(self):
115n/a msg = Message()
116n/a charset = Charset('iso-8859-1')
117n/a msg.set_payload('This is a string payload', charset)
118n/a self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
119n/a
120n/a def test_get_charsets(self):
121n/a eq = self.assertEqual
122n/a
123n/a msg = self._msgobj('msg_08.txt')
124n/a charsets = msg.get_charsets()
125n/a eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
126n/a
127n/a msg = self._msgobj('msg_09.txt')
128n/a charsets = msg.get_charsets('dingbat')
129n/a eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
130n/a 'koi8-r'])
131n/a
132n/a msg = self._msgobj('msg_12.txt')
133n/a charsets = msg.get_charsets()
134n/a eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
135n/a 'iso-8859-3', 'us-ascii', 'koi8-r'])
136n/a
137n/a def test_get_filename(self):
138n/a eq = self.assertEqual
139n/a
140n/a msg = self._msgobj('msg_04.txt')
141n/a filenames = [p.get_filename() for p in msg.get_payload()]
142n/a eq(filenames, ['msg.txt', 'msg.txt'])
143n/a
144n/a msg = self._msgobj('msg_07.txt')
145n/a subpart = msg.get_payload(1)
146n/a eq(subpart.get_filename(), 'dingusfish.gif')
147n/a
148n/a def test_get_filename_with_name_parameter(self):
149n/a eq = self.assertEqual
150n/a
151n/a msg = self._msgobj('msg_44.txt')
152n/a filenames = [p.get_filename() for p in msg.get_payload()]
153n/a eq(filenames, ['msg.txt', 'msg.txt'])
154n/a
155n/a def test_get_boundary(self):
156n/a eq = self.assertEqual
157n/a msg = self._msgobj('msg_07.txt')
158n/a # No quotes!
159n/a eq(msg.get_boundary(), 'BOUNDARY')
160n/a
161n/a def test_set_boundary(self):
162n/a eq = self.assertEqual
163n/a # This one has no existing boundary parameter, but the Content-Type:
164n/a # header appears fifth.
165n/a msg = self._msgobj('msg_01.txt')
166n/a msg.set_boundary('BOUNDARY')
167n/a header, value = msg.items()[4]
168n/a eq(header.lower(), 'content-type')
169n/a eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
170n/a # This one has a Content-Type: header, with a boundary, stuck in the
171n/a # middle of its headers. Make sure the order is preserved; it should
172n/a # be fifth.
173n/a msg = self._msgobj('msg_04.txt')
174n/a msg.set_boundary('BOUNDARY')
175n/a header, value = msg.items()[4]
176n/a eq(header.lower(), 'content-type')
177n/a eq(value, 'multipart/mixed; boundary="BOUNDARY"')
178n/a # And this one has no Content-Type: header at all.
179n/a msg = self._msgobj('msg_03.txt')
180n/a self.assertRaises(errors.HeaderParseError,
181n/a msg.set_boundary, 'BOUNDARY')
182n/a
183n/a def test_make_boundary(self):
184n/a msg = MIMEMultipart('form-data')
185n/a # Note that when the boundary gets created is an implementation
186n/a # detail and might change.
187n/a self.assertEqual(msg.items()[0][1], 'multipart/form-data')
188n/a # Trigger creation of boundary
189n/a msg.as_string()
190n/a self.assertEqual(msg.items()[0][1][:33],
191n/a 'multipart/form-data; boundary="==')
192n/a # XXX: there ought to be tests of the uniqueness of the boundary, too.
193n/a
194n/a def test_message_rfc822_only(self):
195n/a # Issue 7970: message/rfc822 not in multipart parsed by
196n/a # HeaderParser caused an exception when flattened.
197n/a with openfile(findfile('msg_46.txt')) as fp:
198n/a msgdata = fp.read()
199n/a parser = HeaderParser()
200n/a msg = parser.parsestr(msgdata)
201n/a out = StringIO()
202n/a gen = Generator(out, True, 0)
203n/a gen.flatten(msg, False)
204n/a self.assertEqual(out.getvalue(), msgdata)
205n/a
206n/a def test_get_decoded_payload(self):
207n/a eq = self.assertEqual
208n/a msg = self._msgobj('msg_10.txt')
209n/a # The outer message is a multipart
210n/a eq(msg.get_payload(decode=True), None)
211n/a # Subpart 1 is 7bit encoded
212n/a eq(msg.get_payload(0).get_payload(decode=True),
213n/a b'This is a 7bit encoded message.\n')
214n/a # Subpart 2 is quopri
215n/a eq(msg.get_payload(1).get_payload(decode=True),
216n/a b'\xa1This is a Quoted Printable encoded message!\n')
217n/a # Subpart 3 is base64
218n/a eq(msg.get_payload(2).get_payload(decode=True),
219n/a b'This is a Base64 encoded message.')
220n/a # Subpart 4 is base64 with a trailing newline, which
221n/a # used to be stripped (issue 7143).
222n/a eq(msg.get_payload(3).get_payload(decode=True),
223n/a b'This is a Base64 encoded message.\n')
224n/a # Subpart 5 has no Content-Transfer-Encoding: header.
225n/a eq(msg.get_payload(4).get_payload(decode=True),
226n/a b'This has no Content-Transfer-Encoding: header.\n')
227n/a
228n/a def test_get_decoded_uu_payload(self):
229n/a eq = self.assertEqual
230n/a msg = Message()
231n/a msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
232n/a for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
233n/a msg['content-transfer-encoding'] = cte
234n/a eq(msg.get_payload(decode=True), b'hello world')
235n/a # Now try some bogus data
236n/a msg.set_payload('foo')
237n/a eq(msg.get_payload(decode=True), b'foo')
238n/a
239n/a def test_decoded_generator(self):
240n/a eq = self.assertEqual
241n/a msg = self._msgobj('msg_07.txt')
242n/a with openfile('msg_17.txt') as fp:
243n/a text = fp.read()
244n/a s = StringIO()
245n/a g = DecodedGenerator(s)
246n/a g.flatten(msg)
247n/a eq(s.getvalue(), text)
248n/a
249n/a def test__contains__(self):
250n/a msg = Message()
251n/a msg['From'] = 'Me'
252n/a msg['to'] = 'You'
253n/a # Check for case insensitivity
254n/a self.assertTrue('from' in msg)
255n/a self.assertTrue('From' in msg)
256n/a self.assertTrue('FROM' in msg)
257n/a self.assertTrue('to' in msg)
258n/a self.assertTrue('To' in msg)
259n/a self.assertTrue('TO' in msg)
260n/a
261n/a def test_as_string(self):
262n/a eq = self.ndiffAssertEqual
263n/a msg = self._msgobj('msg_01.txt')
264n/a with openfile('msg_01.txt') as fp:
265n/a text = fp.read()
266n/a eq(text, str(msg))
267n/a fullrepr = msg.as_string(unixfrom=True)
268n/a lines = fullrepr.split('\n')
269n/a self.assertTrue(lines[0].startswith('From '))
270n/a eq(text, NL.join(lines[1:]))
271n/a
272n/a def test_bad_param(self):
273n/a msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
274n/a self.assertEqual(msg.get_param('baz'), '')
275n/a
276n/a def test_missing_filename(self):
277n/a msg = email.message_from_string("From: foo\n")
278n/a self.assertEqual(msg.get_filename(), None)
279n/a
280n/a def test_bogus_filename(self):
281n/a msg = email.message_from_string(
282n/a "Content-Disposition: blarg; filename\n")
283n/a self.assertEqual(msg.get_filename(), '')
284n/a
285n/a def test_missing_boundary(self):
286n/a msg = email.message_from_string("From: foo\n")
287n/a self.assertEqual(msg.get_boundary(), None)
288n/a
289n/a def test_get_params(self):
290n/a eq = self.assertEqual
291n/a msg = email.message_from_string(
292n/a 'X-Header: foo=one; bar=two; baz=three\n')
293n/a eq(msg.get_params(header='x-header'),
294n/a [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
295n/a msg = email.message_from_string(
296n/a 'X-Header: foo; bar=one; baz=two\n')
297n/a eq(msg.get_params(header='x-header'),
298n/a [('foo', ''), ('bar', 'one'), ('baz', 'two')])
299n/a eq(msg.get_params(), None)
300n/a msg = email.message_from_string(
301n/a 'X-Header: foo; bar="one"; baz=two\n')
302n/a eq(msg.get_params(header='x-header'),
303n/a [('foo', ''), ('bar', 'one'), ('baz', 'two')])
304n/a
305n/a def test_get_param_liberal(self):
306n/a msg = Message()
307n/a msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
308n/a self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
309n/a
310n/a def test_get_param(self):
311n/a eq = self.assertEqual
312n/a msg = email.message_from_string(
313n/a "X-Header: foo=one; bar=two; baz=three\n")
314n/a eq(msg.get_param('bar', header='x-header'), 'two')
315n/a eq(msg.get_param('quuz', header='x-header'), None)
316n/a eq(msg.get_param('quuz'), None)
317n/a msg = email.message_from_string(
318n/a 'X-Header: foo; bar="one"; baz=two\n')
319n/a eq(msg.get_param('foo', header='x-header'), '')
320n/a eq(msg.get_param('bar', header='x-header'), 'one')
321n/a eq(msg.get_param('baz', header='x-header'), 'two')
322n/a # XXX: We are not RFC-2045 compliant! We cannot parse:
323n/a # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
324n/a # msg.get_param("weird")
325n/a # yet.
326n/a
327n/a def test_get_param_funky_continuation_lines(self):
328n/a msg = self._msgobj('msg_22.txt')
329n/a self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
330n/a
331n/a def test_get_param_with_semis_in_quotes(self):
332n/a msg = email.message_from_string(
333n/a 'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
334n/a self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
335n/a self.assertEqual(msg.get_param('name', unquote=False),
336n/a '"Jim&amp;&amp;Jill"')
337n/a
338n/a def test_get_param_with_quotes(self):
339n/a msg = email.message_from_string(
340n/a 'Content-Type: foo; bar*0="baz\\"foobar"; bar*1="\\"baz"')
341n/a self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
342n/a msg = email.message_from_string(
343n/a "Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"")
344n/a self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
345n/a
346n/a def test_field_containment(self):
347n/a unless = self.assertTrue
348n/a msg = email.message_from_string('Header: exists')
349n/a unless('header' in msg)
350n/a unless('Header' in msg)
351n/a unless('HEADER' in msg)
352n/a self.assertFalse('headerx' in msg)
353n/a
354n/a def test_set_param(self):
355n/a eq = self.assertEqual
356n/a msg = Message()
357n/a msg.set_param('charset', 'iso-2022-jp')
358n/a eq(msg.get_param('charset'), 'iso-2022-jp')
359n/a msg.set_param('importance', 'high value')
360n/a eq(msg.get_param('importance'), 'high value')
361n/a eq(msg.get_param('importance', unquote=False), '"high value"')
362n/a eq(msg.get_params(), [('text/plain', ''),
363n/a ('charset', 'iso-2022-jp'),
364n/a ('importance', 'high value')])
365n/a eq(msg.get_params(unquote=False), [('text/plain', ''),
366n/a ('charset', '"iso-2022-jp"'),
367n/a ('importance', '"high value"')])
368n/a msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
369n/a eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
370n/a
371n/a def test_del_param(self):
372n/a eq = self.assertEqual
373n/a msg = self._msgobj('msg_05.txt')
374n/a eq(msg.get_params(),
375n/a [('multipart/report', ''), ('report-type', 'delivery-status'),
376n/a ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
377n/a old_val = msg.get_param("report-type")
378n/a msg.del_param("report-type")
379n/a eq(msg.get_params(),
380n/a [('multipart/report', ''),
381n/a ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
382n/a msg.set_param("report-type", old_val)
383n/a eq(msg.get_params(),
384n/a [('multipart/report', ''),
385n/a ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
386n/a ('report-type', old_val)])
387n/a
388n/a def test_del_param_on_other_header(self):
389n/a msg = Message()
390n/a msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
391n/a msg.del_param('filename', 'content-disposition')
392n/a self.assertEqual(msg['content-disposition'], 'attachment')
393n/a
394n/a def test_set_type(self):
395n/a eq = self.assertEqual
396n/a msg = Message()
397n/a self.assertRaises(ValueError, msg.set_type, 'text')
398n/a msg.set_type('text/plain')
399n/a eq(msg['content-type'], 'text/plain')
400n/a msg.set_param('charset', 'us-ascii')
401n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
402n/a msg.set_type('text/html')
403n/a eq(msg['content-type'], 'text/html; charset="us-ascii"')
404n/a
405n/a def test_set_type_on_other_header(self):
406n/a msg = Message()
407n/a msg['X-Content-Type'] = 'text/plain'
408n/a msg.set_type('application/octet-stream', 'X-Content-Type')
409n/a self.assertEqual(msg['x-content-type'], 'application/octet-stream')
410n/a
411n/a def test_get_content_type_missing(self):
412n/a msg = Message()
413n/a self.assertEqual(msg.get_content_type(), 'text/plain')
414n/a
415n/a def test_get_content_type_missing_with_default_type(self):
416n/a msg = Message()
417n/a msg.set_default_type('message/rfc822')
418n/a self.assertEqual(msg.get_content_type(), 'message/rfc822')
419n/a
420n/a def test_get_content_type_from_message_implicit(self):
421n/a msg = self._msgobj('msg_30.txt')
422n/a self.assertEqual(msg.get_payload(0).get_content_type(),
423n/a 'message/rfc822')
424n/a
425n/a def test_get_content_type_from_message_explicit(self):
426n/a msg = self._msgobj('msg_28.txt')
427n/a self.assertEqual(msg.get_payload(0).get_content_type(),
428n/a 'message/rfc822')
429n/a
430n/a def test_get_content_type_from_message_text_plain_implicit(self):
431n/a msg = self._msgobj('msg_03.txt')
432n/a self.assertEqual(msg.get_content_type(), 'text/plain')
433n/a
434n/a def test_get_content_type_from_message_text_plain_explicit(self):
435n/a msg = self._msgobj('msg_01.txt')
436n/a self.assertEqual(msg.get_content_type(), 'text/plain')
437n/a
438n/a def test_get_content_maintype_missing(self):
439n/a msg = Message()
440n/a self.assertEqual(msg.get_content_maintype(), 'text')
441n/a
442n/a def test_get_content_maintype_missing_with_default_type(self):
443n/a msg = Message()
444n/a msg.set_default_type('message/rfc822')
445n/a self.assertEqual(msg.get_content_maintype(), 'message')
446n/a
447n/a def test_get_content_maintype_from_message_implicit(self):
448n/a msg = self._msgobj('msg_30.txt')
449n/a self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
450n/a
451n/a def test_get_content_maintype_from_message_explicit(self):
452n/a msg = self._msgobj('msg_28.txt')
453n/a self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
454n/a
455n/a def test_get_content_maintype_from_message_text_plain_implicit(self):
456n/a msg = self._msgobj('msg_03.txt')
457n/a self.assertEqual(msg.get_content_maintype(), 'text')
458n/a
459n/a def test_get_content_maintype_from_message_text_plain_explicit(self):
460n/a msg = self._msgobj('msg_01.txt')
461n/a self.assertEqual(msg.get_content_maintype(), 'text')
462n/a
463n/a def test_get_content_subtype_missing(self):
464n/a msg = Message()
465n/a self.assertEqual(msg.get_content_subtype(), 'plain')
466n/a
467n/a def test_get_content_subtype_missing_with_default_type(self):
468n/a msg = Message()
469n/a msg.set_default_type('message/rfc822')
470n/a self.assertEqual(msg.get_content_subtype(), 'rfc822')
471n/a
472n/a def test_get_content_subtype_from_message_implicit(self):
473n/a msg = self._msgobj('msg_30.txt')
474n/a self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
475n/a
476n/a def test_get_content_subtype_from_message_explicit(self):
477n/a msg = self._msgobj('msg_28.txt')
478n/a self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
479n/a
480n/a def test_get_content_subtype_from_message_text_plain_implicit(self):
481n/a msg = self._msgobj('msg_03.txt')
482n/a self.assertEqual(msg.get_content_subtype(), 'plain')
483n/a
484n/a def test_get_content_subtype_from_message_text_plain_explicit(self):
485n/a msg = self._msgobj('msg_01.txt')
486n/a self.assertEqual(msg.get_content_subtype(), 'plain')
487n/a
488n/a def test_get_content_maintype_error(self):
489n/a msg = Message()
490n/a msg['Content-Type'] = 'no-slash-in-this-string'
491n/a self.assertEqual(msg.get_content_maintype(), 'text')
492n/a
493n/a def test_get_content_subtype_error(self):
494n/a msg = Message()
495n/a msg['Content-Type'] = 'no-slash-in-this-string'
496n/a self.assertEqual(msg.get_content_subtype(), 'plain')
497n/a
498n/a def test_replace_header(self):
499n/a eq = self.assertEqual
500n/a msg = Message()
501n/a msg.add_header('First', 'One')
502n/a msg.add_header('Second', 'Two')
503n/a msg.add_header('Third', 'Three')
504n/a eq(msg.keys(), ['First', 'Second', 'Third'])
505n/a eq(msg.values(), ['One', 'Two', 'Three'])
506n/a msg.replace_header('Second', 'Twenty')
507n/a eq(msg.keys(), ['First', 'Second', 'Third'])
508n/a eq(msg.values(), ['One', 'Twenty', 'Three'])
509n/a msg.add_header('First', 'Eleven')
510n/a msg.replace_header('First', 'One Hundred')
511n/a eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
512n/a eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
513n/a self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
514n/a
515n/a def test_broken_base64_payload(self):
516n/a x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
517n/a msg = Message()
518n/a msg['content-type'] = 'audio/x-midi'
519n/a msg['content-transfer-encoding'] = 'base64'
520n/a msg.set_payload(x)
521n/a self.assertEqual(msg.get_payload(decode=True),
522n/a bytes(x, 'raw-unicode-escape'))
523n/a
524n/a # Issue 1078919
525n/a def test_ascii_add_header(self):
526n/a msg = Message()
527n/a msg.add_header('Content-Disposition', 'attachment',
528n/a filename='bud.gif')
529n/a self.assertEqual('attachment; filename="bud.gif"',
530n/a msg['Content-Disposition'])
531n/a
532n/a def test_noascii_add_header(self):
533n/a msg = Message()
534n/a msg.add_header('Content-Disposition', 'attachment',
535n/a filename="Fußballer.ppt")
536n/a self.assertEqual(
537n/a 'attachment; filename*=utf-8\'\'Fu%C3%9Fballer.ppt',
538n/a msg['Content-Disposition'])
539n/a
540n/a def test_nonascii_add_header_via_triple(self):
541n/a msg = Message()
542n/a msg.add_header('Content-Disposition', 'attachment',
543n/a filename=('iso-8859-1', '', 'Fußballer.ppt'))
544n/a self.assertEqual(
545n/a 'attachment; filename*=iso-8859-1\'\'Fu%DFballer.ppt',
546n/a msg['Content-Disposition'])
547n/a
548n/a def test_ascii_add_header_with_tspecial(self):
549n/a msg = Message()
550n/a msg.add_header('Content-Disposition', 'attachment',
551n/a filename="windows [filename].ppt")
552n/a self.assertEqual(
553n/a 'attachment; filename="windows [filename].ppt"',
554n/a msg['Content-Disposition'])
555n/a
556n/a def test_nonascii_add_header_with_tspecial(self):
557n/a msg = Message()
558n/a msg.add_header('Content-Disposition', 'attachment',
559n/a filename="Fußballer [filename].ppt")
560n/a self.assertEqual(
561n/a "attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt",
562n/a msg['Content-Disposition'])
563n/a
564n/a # Issue 5871: reject an attempt to embed a header inside a header value
565n/a # (header injection attack).
566n/a def test_embeded_header_via_Header_rejected(self):
567n/a msg = Message()
568n/a msg['Dummy'] = Header('dummy\nX-Injected-Header: test')
569n/a self.assertRaises(errors.HeaderParseError, msg.as_string)
570n/a
571n/a def test_embeded_header_via_string_rejected(self):
572n/a msg = Message()
573n/a msg['Dummy'] = 'dummy\nX-Injected-Header: test'
574n/a self.assertRaises(errors.HeaderParseError, msg.as_string)
575n/a
576n/a
577n/a# Test the email.encoders module
578n/aclass TestEncoders(unittest.TestCase):
579n/a def test_encode_empty_payload(self):
580n/a eq = self.assertEqual
581n/a msg = Message()
582n/a msg.set_charset('us-ascii')
583n/a eq(msg['content-transfer-encoding'], '7bit')
584n/a
585n/a def test_default_cte(self):
586n/a eq = self.assertEqual
587n/a # 7bit data and the default us-ascii _charset
588n/a msg = MIMEText('hello world')
589n/a eq(msg['content-transfer-encoding'], '7bit')
590n/a # Similar, but with 8bit data
591n/a msg = MIMEText('hello \xf8 world')
592n/a eq(msg['content-transfer-encoding'], '8bit')
593n/a # And now with a different charset
594n/a msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
595n/a eq(msg['content-transfer-encoding'], 'quoted-printable')
596n/a
597n/a def test_encode7or8bit(self):
598n/a # Make sure a charset whose input character set is 8bit but
599n/a # whose output character set is 7bit gets a transfer-encoding
600n/a # of 7bit.
601n/a eq = self.assertEqual
602n/a msg = MIMEText('æ–‡', _charset='euc-jp')
603n/a eq(msg['content-transfer-encoding'], '7bit')
604n/a
605n/a
606n/a# Test long header wrapping
607n/aclass TestLongHeaders(TestEmailBase):
608n/a def test_split_long_continuation(self):
609n/a eq = self.ndiffAssertEqual
610n/a msg = email.message_from_string("""\
611n/aSubject: bug demonstration
612n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
613n/a\tmore text
614n/a
615n/atest
616n/a""")
617n/a sfp = StringIO()
618n/a g = Generator(sfp)
619n/a g.flatten(msg)
620n/a eq(sfp.getvalue(), """\
621n/aSubject: bug demonstration
622n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
623n/a\tmore text
624n/a
625n/atest
626n/a""")
627n/a
628n/a def test_another_long_almost_unsplittable_header(self):
629n/a eq = self.ndiffAssertEqual
630n/a hstr = """\
631n/abug demonstration
632n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
633n/a\tmore text"""
634n/a h = Header(hstr, continuation_ws='\t')
635n/a eq(h.encode(), """\
636n/abug demonstration
637n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
638n/a\tmore text""")
639n/a h = Header(hstr.replace('\t', ' '))
640n/a eq(h.encode(), """\
641n/abug demonstration
642n/a 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
643n/a more text""")
644n/a
645n/a def test_long_nonstring(self):
646n/a eq = self.ndiffAssertEqual
647n/a g = Charset("iso-8859-1")
648n/a cz = Charset("iso-8859-2")
649n/a utf8 = Charset("utf-8")
650n/a g_head = (b'Die Mieter treten hier ein werden mit einem Foerderband '
651n/a b'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
652n/a b'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
653n/a b'bef\xf6rdert. ')
654n/a cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
655n/a b'd\xf9vtipu.. ')
656n/a utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
657n/a '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
658n/a '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
659n/a '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
660n/a '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
661n/a 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
662n/a 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
663n/a '\u3044\u307e\u3059\u3002')
664n/a h = Header(g_head, g, header_name='Subject')
665n/a h.append(cz_head, cz)
666n/a h.append(utf8_head, utf8)
667n/a msg = Message()
668n/a msg['Subject'] = h
669n/a sfp = StringIO()
670n/a g = Generator(sfp)
671n/a g.flatten(msg)
672n/a eq(sfp.getvalue(), """\
673n/aSubject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderb?=
674n/a =?iso-8859-1?q?and_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen?=
675n/a =?iso-8859-1?q?_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef?=
676n/a =?iso-8859-1?q?=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hrouti?=
677n/a =?iso-8859-2?q?ly_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
678n/a =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
679n/a =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn44Gf44KJ?=
680n/a =?utf-8?b?44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFzIE51bnN0dWNr?=
681n/a =?utf-8?b?IGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5kIGRhcyBPZGVyIGRp?=
682n/a =?utf-8?b?ZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIDjgaPjgabjgYTjgb7jgZk=?=
683n/a =?utf-8?b?44CC?=
684n/a
685n/a""")
686n/a eq(h.encode(maxlinelen=76), """\
687n/a=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerde?=
688n/a =?iso-8859-1?q?rband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndis?=
689n/a =?iso-8859-1?q?chen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klinge?=
690n/a =?iso-8859-1?q?n_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se?=
691n/a =?iso-8859-2?q?_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
692n/a =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb?=
693n/a =?utf-8?b?44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go?=
694n/a =?utf-8?b?44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBp?=
695n/a =?utf-8?b?c3QgZGFzIE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWlo?=
696n/a =?utf-8?b?ZXJodW5kIGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI0=?=
697n/a =?utf-8?b?44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
698n/a
699n/a def test_long_header_encode(self):
700n/a eq = self.ndiffAssertEqual
701n/a h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
702n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
703n/a header_name='X-Foobar-Spoink-Defrobnit')
704n/a eq(h.encode(), '''\
705n/awasnipoop; giraffes="very-long-necked-animals";
706n/a spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
707n/a
708n/a def test_long_header_encode_with_tab_continuation_is_just_a_hint(self):
709n/a eq = self.ndiffAssertEqual
710n/a h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
711n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
712n/a header_name='X-Foobar-Spoink-Defrobnit',
713n/a continuation_ws='\t')
714n/a eq(h.encode(), '''\
715n/awasnipoop; giraffes="very-long-necked-animals";
716n/a spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
717n/a
718n/a def test_long_header_encode_with_tab_continuation(self):
719n/a eq = self.ndiffAssertEqual
720n/a h = Header('wasnipoop; giraffes="very-long-necked-animals";\t'
721n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
722n/a header_name='X-Foobar-Spoink-Defrobnit',
723n/a continuation_ws='\t')
724n/a eq(h.encode(), '''\
725n/awasnipoop; giraffes="very-long-necked-animals";
726n/a\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
727n/a
728n/a def test_header_splitter(self):
729n/a eq = self.ndiffAssertEqual
730n/a msg = MIMEText('')
731n/a # It'd be great if we could use add_header() here, but that doesn't
732n/a # guarantee an order of the parameters.
733n/a msg['X-Foobar-Spoink-Defrobnit'] = (
734n/a 'wasnipoop; giraffes="very-long-necked-animals"; '
735n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
736n/a sfp = StringIO()
737n/a g = Generator(sfp)
738n/a g.flatten(msg)
739n/a eq(sfp.getvalue(), '''\
740n/aContent-Type: text/plain; charset="us-ascii"
741n/aMIME-Version: 1.0
742n/aContent-Transfer-Encoding: 7bit
743n/aX-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
744n/a spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
745n/a
746n/a''')
747n/a
748n/a def test_no_semis_header_splitter(self):
749n/a eq = self.ndiffAssertEqual
750n/a msg = Message()
751n/a msg['From'] = 'test@dom.ain'
752n/a msg['References'] = SPACE.join('<%d@dom.ain>' % i for i in range(10))
753n/a msg.set_payload('Test')
754n/a sfp = StringIO()
755n/a g = Generator(sfp)
756n/a g.flatten(msg)
757n/a eq(sfp.getvalue(), """\
758n/aFrom: test@dom.ain
759n/aReferences: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
760n/a <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
761n/a
762n/aTest""")
763n/a
764n/a def test_no_split_long_header(self):
765n/a eq = self.ndiffAssertEqual
766n/a hstr = 'References: ' + 'x' * 80
767n/a h = Header(hstr)
768n/a # These come on two lines because Headers are really field value
769n/a # classes and don't really know about their field names.
770n/a eq(h.encode(), """\
771n/aReferences:
772n/a xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
773n/a h = Header('x' * 80)
774n/a eq(h.encode(), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
775n/a
776n/a def test_splitting_multiple_long_lines(self):
777n/a eq = self.ndiffAssertEqual
778n/a hstr = """\
779n/afrom 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)
780n/a\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)
781n/a\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)
782n/a"""
783n/a h = Header(hstr, continuation_ws='\t')
784n/a eq(h.encode(), """\
785n/afrom babylon.socal-raves.org (localhost [127.0.0.1]);
786n/a by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
787n/a for <mailman-admin@babylon.socal-raves.org>;
788n/a Sat, 2 Feb 2002 17:00:06 -0800 (PST)
789n/a\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
790n/a by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
791n/a for <mailman-admin@babylon.socal-raves.org>;
792n/a Sat, 2 Feb 2002 17:00:06 -0800 (PST)
793n/a\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
794n/a by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
795n/a for <mailman-admin@babylon.socal-raves.org>;
796n/a Sat, 2 Feb 2002 17:00:06 -0800 (PST)""")
797n/a
798n/a def test_splitting_first_line_only_is_long(self):
799n/a eq = self.ndiffAssertEqual
800n/a hstr = """\
801n/afrom modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
802n/a\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
803n/a\tid 17k4h5-00034i-00
804n/a\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
805n/a h = Header(hstr, maxlinelen=78, header_name='Received',
806n/a continuation_ws='\t')
807n/a eq(h.encode(), """\
808n/afrom modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
809n/a helo=cthulhu.gerg.ca)
810n/a\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
811n/a\tid 17k4h5-00034i-00
812n/a\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
813n/a
814n/a def test_long_8bit_header(self):
815n/a eq = self.ndiffAssertEqual
816n/a msg = Message()
817n/a h = Header('Britische Regierung gibt', 'iso-8859-1',
818n/a header_name='Subject')
819n/a h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
820n/a eq(h.encode(maxlinelen=76), """\
821n/a=?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
822n/a =?iso-8859-1?q?hore-Windkraftprojekte?=""")
823n/a msg['Subject'] = h
824n/a eq(msg.as_string(maxheaderlen=76), """\
825n/aSubject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
826n/a =?iso-8859-1?q?hore-Windkraftprojekte?=
827n/a
828n/a""")
829n/a eq(msg.as_string(maxheaderlen=0), """\
830n/aSubject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offshore-Windkraftprojekte?=
831n/a
832n/a""")
833n/a
834n/a def test_long_8bit_header_no_charset(self):
835n/a eq = self.ndiffAssertEqual
836n/a msg = Message()
837n/a header_string = ('Britische Regierung gibt gr\xfcnes Licht '
838n/a 'f\xfcr Offshore-Windkraftprojekte '
839n/a '<a-very-long-address@example.com>')
840n/a msg['Reply-To'] = header_string
841n/a self.assertRaises(UnicodeEncodeError, msg.as_string)
842n/a msg = Message()
843n/a msg['Reply-To'] = Header(header_string, 'utf-8',
844n/a header_name='Reply-To')
845n/a eq(msg.as_string(maxheaderlen=78), """\
846n/aReply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?=
847n/a =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?=
848n/a
849n/a""")
850n/a
851n/a def test_long_to_header(self):
852n/a eq = self.ndiffAssertEqual
853n/a to = ('"Someone Test #A" <someone@eecs.umich.edu>,'
854n/a '<someone@eecs.umich.edu>,'
855n/a '"Someone Test #B" <someone@umich.edu>, '
856n/a '"Someone Test #C" <someone@eecs.umich.edu>, '
857n/a '"Someone Test #D" <someone@eecs.umich.edu>')
858n/a msg = Message()
859n/a msg['To'] = to
860n/a eq(msg.as_string(maxheaderlen=78), '''\
861n/aTo: "Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,
862n/a "Someone Test #B" <someone@umich.edu>,
863n/a "Someone Test #C" <someone@eecs.umich.edu>,
864n/a "Someone Test #D" <someone@eecs.umich.edu>
865n/a
866n/a''')
867n/a
868n/a def test_long_line_after_append(self):
869n/a eq = self.ndiffAssertEqual
870n/a s = 'This is an example of string which has almost the limit of header length.'
871n/a h = Header(s)
872n/a h.append('Add another line.')
873n/a eq(h.encode(maxlinelen=76), """\
874n/aThis is an example of string which has almost the limit of header length.
875n/a Add another line.""")
876n/a
877n/a def test_shorter_line_with_append(self):
878n/a eq = self.ndiffAssertEqual
879n/a s = 'This is a shorter line.'
880n/a h = Header(s)
881n/a h.append('Add another sentence. (Surprise?)')
882n/a eq(h.encode(),
883n/a 'This is a shorter line. Add another sentence. (Surprise?)')
884n/a
885n/a def test_long_field_name(self):
886n/a eq = self.ndiffAssertEqual
887n/a fn = 'X-Very-Very-Very-Long-Header-Name'
888n/a gs = ('Die Mieter treten hier ein werden mit einem Foerderband '
889n/a 'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
890n/a 'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
891n/a 'bef\xf6rdert. ')
892n/a h = Header(gs, 'iso-8859-1', header_name=fn)
893n/a # BAW: this seems broken because the first line is too long
894n/a eq(h.encode(maxlinelen=76), """\
895n/a=?iso-8859-1?q?Die_Mieter_treten_hier_e?=
896n/a =?iso-8859-1?q?in_werden_mit_einem_Foerderband_komfortabel_den_Korridor_e?=
897n/a =?iso-8859-1?q?ntlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_ge?=
898n/a =?iso-8859-1?q?gen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
899n/a
900n/a def test_long_received_header(self):
901n/a h = ('from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) '
902n/a 'by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; '
903n/a 'Wed, 05 Mar 2003 18:10:18 -0700')
904n/a msg = Message()
905n/a msg['Received-1'] = Header(h, continuation_ws='\t')
906n/a msg['Received-2'] = h
907n/a # This should be splitting on spaces not semicolons.
908n/a self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
909n/aReceived-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
910n/a Wed, 05 Mar 2003 18:10:18 -0700
911n/aReceived-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
912n/a Wed, 05 Mar 2003 18:10:18 -0700
913n/a
914n/a""")
915n/a
916n/a def test_string_headerinst_eq(self):
917n/a h = ('<15975.17901.207240.414604@sgigritzmann1.mathematik.'
918n/a 'tu-muenchen.de> (David Bremner\'s message of '
919n/a '"Thu, 6 Mar 2003 13:58:21 +0100")')
920n/a msg = Message()
921n/a msg['Received-1'] = Header(h, header_name='Received-1',
922n/a continuation_ws='\t')
923n/a msg['Received-2'] = h
924n/a # XXX This should be splitting on spaces not commas.
925n/a self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
926n/aReceived-1: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu,
927n/a 6 Mar 2003 13:58:21 +0100\")
928n/aReceived-2: <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David Bremner's message of \"Thu,
929n/a 6 Mar 2003 13:58:21 +0100\")
930n/a
931n/a""")
932n/a
933n/a def test_long_unbreakable_lines_with_continuation(self):
934n/a eq = self.ndiffAssertEqual
935n/a msg = Message()
936n/a t = """\
937n/aiVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
938n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
939n/a msg['Face-1'] = t
940n/a msg['Face-2'] = Header(t, header_name='Face-2')
941n/a # XXX This splitting is all wrong. It the first value line should be
942n/a # snug against the field name.
943n/a eq(msg.as_string(maxheaderlen=78), """\
944n/aFace-1:\x20
945n/a iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
946n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
947n/aFace-2:\x20
948n/a iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
949n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
950n/a
951n/a""")
952n/a
953n/a def test_another_long_multiline_header(self):
954n/a eq = self.ndiffAssertEqual
955n/a m = ('Received: from siimage.com '
956n/a '([172.25.1.3]) by zima.siliconimage.com with '
957n/a 'Microsoft SMTPSVC(5.0.2195.4905); '
958n/a 'Wed, 16 Oct 2002 07:41:11 -0700')
959n/a msg = email.message_from_string(m)
960n/a eq(msg.as_string(maxheaderlen=78), '''\
961n/aReceived: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with Microsoft SMTPSVC(5.0.2195.4905);
962n/a Wed, 16 Oct 2002 07:41:11 -0700
963n/a
964n/a''')
965n/a
966n/a def test_long_lines_with_different_header(self):
967n/a eq = self.ndiffAssertEqual
968n/a h = ('List-Unsubscribe: '
969n/a '<http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,'
970n/a ' <mailto:spamassassin-talk-request@lists.sourceforge.net'
971n/a '?subject=unsubscribe>')
972n/a msg = Message()
973n/a msg['List'] = h
974n/a msg['List'] = Header(h, header_name='List')
975n/a eq(msg.as_string(maxheaderlen=78), """\
976n/aList: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
977n/a <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
978n/aList: List-Unsubscribe: <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
979n/a <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
980n/a
981n/a""")
982n/a
983n/a def test_long_rfc2047_header_with_embedded_fws(self):
984n/a h = Header(textwrap.dedent("""\
985n/a We're going to pretend this header is in a non-ascii character set
986n/a \tto see if line wrapping with encoded words and embedded
987n/a folding white space works"""),
988n/a charset='utf-8',
989n/a header_name='Test')
990n/a self.assertEqual(h.encode()+'\n', textwrap.dedent("""\
991n/a =?utf-8?q?We=27re_going_to_pretend_this_header_is_in_a_non-ascii_chara?=
992n/a =?utf-8?q?cter_set?=
993n/a =?utf-8?q?_to_see_if_line_wrapping_with_encoded_words_and_embedded?=
994n/a =?utf-8?q?_folding_white_space_works?=""")+'\n')
995n/a
996n/a
997n/a
998n/a# Test mangling of "From " lines in the body of a message
999n/aclass TestFromMangling(unittest.TestCase):
1000n/a def setUp(self):
1001n/a self.msg = Message()
1002n/a self.msg['From'] = 'aaa@bbb.org'
1003n/a self.msg.set_payload("""\
1004n/aFrom the desk of A.A.A.:
1005n/aBlah blah blah
1006n/a""")
1007n/a
1008n/a def test_mangled_from(self):
1009n/a s = StringIO()
1010n/a g = Generator(s, mangle_from_=True)
1011n/a g.flatten(self.msg)
1012n/a self.assertEqual(s.getvalue(), """\
1013n/aFrom: aaa@bbb.org
1014n/a
1015n/a>From the desk of A.A.A.:
1016n/aBlah blah blah
1017n/a""")
1018n/a
1019n/a def test_dont_mangle_from(self):
1020n/a s = StringIO()
1021n/a g = Generator(s, mangle_from_=False)
1022n/a g.flatten(self.msg)
1023n/a self.assertEqual(s.getvalue(), """\
1024n/aFrom: aaa@bbb.org
1025n/a
1026n/aFrom the desk of A.A.A.:
1027n/aBlah blah blah
1028n/a""")
1029n/a
1030n/a
1031n/a
1032n/a# Test the basic MIMEAudio class
1033n/aclass TestMIMEAudio(unittest.TestCase):
1034n/a def setUp(self):
1035n/a # Make sure we pick up the audiotest.au that lives in email/test/data.
1036n/a # In Python, there's an audiotest.au living in Lib/test but that isn't
1037n/a # included in some binary distros that don't include the test
1038n/a # package. The trailing empty string on the .join() is significant
1039n/a # since findfile() will do a dirname().
1040n/a datadir = os.path.join(os.path.dirname(landmark), 'data', '')
1041n/a with open(findfile('audiotest.au', datadir), 'rb') as fp:
1042n/a self._audiodata = fp.read()
1043n/a self._au = MIMEAudio(self._audiodata)
1044n/a
1045n/a def test_guess_minor_type(self):
1046n/a self.assertEqual(self._au.get_content_type(), 'audio/basic')
1047n/a
1048n/a def test_encoding(self):
1049n/a payload = self._au.get_payload()
1050n/a self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
1051n/a self._audiodata)
1052n/a
1053n/a def test_checkSetMinor(self):
1054n/a au = MIMEAudio(self._audiodata, 'fish')
1055n/a self.assertEqual(au.get_content_type(), 'audio/fish')
1056n/a
1057n/a def test_add_header(self):
1058n/a eq = self.assertEqual
1059n/a unless = self.assertTrue
1060n/a self._au.add_header('Content-Disposition', 'attachment',
1061n/a filename='audiotest.au')
1062n/a eq(self._au['content-disposition'],
1063n/a 'attachment; filename="audiotest.au"')
1064n/a eq(self._au.get_params(header='content-disposition'),
1065n/a [('attachment', ''), ('filename', 'audiotest.au')])
1066n/a eq(self._au.get_param('filename', header='content-disposition'),
1067n/a 'audiotest.au')
1068n/a missing = []
1069n/a eq(self._au.get_param('attachment', header='content-disposition'), '')
1070n/a unless(self._au.get_param('foo', failobj=missing,
1071n/a header='content-disposition') is missing)
1072n/a # Try some missing stuff
1073n/a unless(self._au.get_param('foobar', missing) is missing)
1074n/a unless(self._au.get_param('attachment', missing,
1075n/a header='foobar') is missing)
1076n/a
1077n/a
1078n/a
1079n/a# Test the basic MIMEImage class
1080n/aclass TestMIMEImage(unittest.TestCase):
1081n/a def setUp(self):
1082n/a with openfile('PyBanner048.gif', 'rb') as fp:
1083n/a self._imgdata = fp.read()
1084n/a self._im = MIMEImage(self._imgdata)
1085n/a
1086n/a def test_guess_minor_type(self):
1087n/a self.assertEqual(self._im.get_content_type(), 'image/gif')
1088n/a
1089n/a def test_encoding(self):
1090n/a payload = self._im.get_payload()
1091n/a self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
1092n/a self._imgdata)
1093n/a
1094n/a def test_checkSetMinor(self):
1095n/a im = MIMEImage(self._imgdata, 'fish')
1096n/a self.assertEqual(im.get_content_type(), 'image/fish')
1097n/a
1098n/a def test_add_header(self):
1099n/a eq = self.assertEqual
1100n/a unless = self.assertTrue
1101n/a self._im.add_header('Content-Disposition', 'attachment',
1102n/a filename='dingusfish.gif')
1103n/a eq(self._im['content-disposition'],
1104n/a 'attachment; filename="dingusfish.gif"')
1105n/a eq(self._im.get_params(header='content-disposition'),
1106n/a [('attachment', ''), ('filename', 'dingusfish.gif')])
1107n/a eq(self._im.get_param('filename', header='content-disposition'),
1108n/a 'dingusfish.gif')
1109n/a missing = []
1110n/a eq(self._im.get_param('attachment', header='content-disposition'), '')
1111n/a unless(self._im.get_param('foo', failobj=missing,
1112n/a header='content-disposition') is missing)
1113n/a # Try some missing stuff
1114n/a unless(self._im.get_param('foobar', missing) is missing)
1115n/a unless(self._im.get_param('attachment', missing,
1116n/a header='foobar') is missing)
1117n/a
1118n/a
1119n/a
1120n/a# Test the basic MIMEApplication class
1121n/aclass TestMIMEApplication(unittest.TestCase):
1122n/a def test_headers(self):
1123n/a eq = self.assertEqual
1124n/a msg = MIMEApplication(b'\xfa\xfb\xfc\xfd\xfe\xff')
1125n/a eq(msg.get_content_type(), 'application/octet-stream')
1126n/a eq(msg['content-transfer-encoding'], 'base64')
1127n/a
1128n/a def test_body(self):
1129n/a eq = self.assertEqual
1130n/a bytes = b'\xfa\xfb\xfc\xfd\xfe\xff'
1131n/a msg = MIMEApplication(bytes)
1132n/a eq(msg.get_payload(), '+vv8/f7/')
1133n/a eq(msg.get_payload(decode=True), bytes)
1134n/a
1135n/a
1136n/a
1137n/a# Test the basic MIMEText class
1138n/aclass TestMIMEText(unittest.TestCase):
1139n/a def setUp(self):
1140n/a self._msg = MIMEText('hello there')
1141n/a
1142n/a def test_types(self):
1143n/a eq = self.assertEqual
1144n/a unless = self.assertTrue
1145n/a eq(self._msg.get_content_type(), 'text/plain')
1146n/a eq(self._msg.get_param('charset'), 'us-ascii')
1147n/a missing = []
1148n/a unless(self._msg.get_param('foobar', missing) is missing)
1149n/a unless(self._msg.get_param('charset', missing, header='foobar')
1150n/a is missing)
1151n/a
1152n/a def test_payload(self):
1153n/a self.assertEqual(self._msg.get_payload(), 'hello there')
1154n/a self.assertTrue(not self._msg.is_multipart())
1155n/a
1156n/a def test_charset(self):
1157n/a eq = self.assertEqual
1158n/a msg = MIMEText('hello there', _charset='us-ascii')
1159n/a eq(msg.get_charset().input_charset, 'us-ascii')
1160n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1161n/a
1162n/a def test_7bit_input(self):
1163n/a eq = self.assertEqual
1164n/a msg = MIMEText('hello there', _charset='us-ascii')
1165n/a eq(msg.get_charset().input_charset, 'us-ascii')
1166n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1167n/a
1168n/a def test_7bit_input_no_charset(self):
1169n/a eq = self.assertEqual
1170n/a msg = MIMEText('hello there')
1171n/a eq(msg.get_charset(), 'us-ascii')
1172n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1173n/a self.assertTrue('hello there' in msg.as_string())
1174n/a
1175n/a def test_utf8_input(self):
1176n/a teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1177n/a eq = self.assertEqual
1178n/a msg = MIMEText(teststr, _charset='utf-8')
1179n/a eq(msg.get_charset().output_charset, 'utf-8')
1180n/a eq(msg['content-type'], 'text/plain; charset="utf-8"')
1181n/a eq(msg.get_payload(decode=True), teststr.encode('utf-8'))
1182n/a
1183n/a @unittest.skip("can't fix because of backward compat in email5, "
1184n/a "will fix in email6")
1185n/a def test_utf8_input_no_charset(self):
1186n/a teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1187n/a self.assertRaises(UnicodeEncodeError, MIMEText, teststr)
1188n/a
1189n/a
1190n/a
1191n/a# Test complicated multipart/* messages
1192n/aclass TestMultipart(TestEmailBase):
1193n/a def setUp(self):
1194n/a with openfile('PyBanner048.gif', 'rb') as fp:
1195n/a data = fp.read()
1196n/a container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
1197n/a image = MIMEImage(data, name='dingusfish.gif')
1198n/a image.add_header('content-disposition', 'attachment',
1199n/a filename='dingusfish.gif')
1200n/a intro = MIMEText('''\
1201n/aHi there,
1202n/a
1203n/aThis is the dingus fish.
1204n/a''')
1205n/a container.attach(intro)
1206n/a container.attach(image)
1207n/a container['From'] = 'Barry <barry@digicool.com>'
1208n/a container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1209n/a container['Subject'] = 'Here is your dingus fish'
1210n/a
1211n/a now = 987809702.54848599
1212n/a timetuple = time.localtime(now)
1213n/a if timetuple[-1] == 0:
1214n/a tzsecs = time.timezone
1215n/a else:
1216n/a tzsecs = time.altzone
1217n/a if tzsecs > 0:
1218n/a sign = '-'
1219n/a else:
1220n/a sign = '+'
1221n/a tzoffset = ' %s%04d' % (sign, tzsecs / 36)
1222n/a container['Date'] = time.strftime(
1223n/a '%a, %d %b %Y %H:%M:%S',
1224n/a time.localtime(now)) + tzoffset
1225n/a self._msg = container
1226n/a self._im = image
1227n/a self._txt = intro
1228n/a
1229n/a def test_hierarchy(self):
1230n/a # convenience
1231n/a eq = self.assertEqual
1232n/a unless = self.assertTrue
1233n/a raises = self.assertRaises
1234n/a # tests
1235n/a m = self._msg
1236n/a unless(m.is_multipart())
1237n/a eq(m.get_content_type(), 'multipart/mixed')
1238n/a eq(len(m.get_payload()), 2)
1239n/a raises(IndexError, m.get_payload, 2)
1240n/a m0 = m.get_payload(0)
1241n/a m1 = m.get_payload(1)
1242n/a unless(m0 is self._txt)
1243n/a unless(m1 is self._im)
1244n/a eq(m.get_payload(), [m0, m1])
1245n/a unless(not m0.is_multipart())
1246n/a unless(not m1.is_multipart())
1247n/a
1248n/a def test_empty_multipart_idempotent(self):
1249n/a text = """\
1250n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1251n/aMIME-Version: 1.0
1252n/aSubject: A subject
1253n/aTo: aperson@dom.ain
1254n/aFrom: bperson@dom.ain
1255n/a
1256n/a
1257n/a--BOUNDARY
1258n/a
1259n/a
1260n/a--BOUNDARY--
1261n/a"""
1262n/a msg = Parser().parsestr(text)
1263n/a self.ndiffAssertEqual(text, msg.as_string())
1264n/a
1265n/a def test_no_parts_in_a_multipart_with_none_epilogue(self):
1266n/a outer = MIMEBase('multipart', 'mixed')
1267n/a outer['Subject'] = 'A subject'
1268n/a outer['To'] = 'aperson@dom.ain'
1269n/a outer['From'] = 'bperson@dom.ain'
1270n/a outer.set_boundary('BOUNDARY')
1271n/a self.ndiffAssertEqual(outer.as_string(), '''\
1272n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1273n/aMIME-Version: 1.0
1274n/aSubject: A subject
1275n/aTo: aperson@dom.ain
1276n/aFrom: bperson@dom.ain
1277n/a
1278n/a--BOUNDARY
1279n/a
1280n/a--BOUNDARY--''')
1281n/a
1282n/a def test_no_parts_in_a_multipart_with_empty_epilogue(self):
1283n/a outer = MIMEBase('multipart', 'mixed')
1284n/a outer['Subject'] = 'A subject'
1285n/a outer['To'] = 'aperson@dom.ain'
1286n/a outer['From'] = 'bperson@dom.ain'
1287n/a outer.preamble = ''
1288n/a outer.epilogue = ''
1289n/a outer.set_boundary('BOUNDARY')
1290n/a self.ndiffAssertEqual(outer.as_string(), '''\
1291n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1292n/aMIME-Version: 1.0
1293n/aSubject: A subject
1294n/aTo: aperson@dom.ain
1295n/aFrom: bperson@dom.ain
1296n/a
1297n/a
1298n/a--BOUNDARY
1299n/a
1300n/a--BOUNDARY--
1301n/a''')
1302n/a
1303n/a def test_one_part_in_a_multipart(self):
1304n/a eq = self.ndiffAssertEqual
1305n/a outer = MIMEBase('multipart', 'mixed')
1306n/a outer['Subject'] = 'A subject'
1307n/a outer['To'] = 'aperson@dom.ain'
1308n/a outer['From'] = 'bperson@dom.ain'
1309n/a outer.set_boundary('BOUNDARY')
1310n/a msg = MIMEText('hello world')
1311n/a outer.attach(msg)
1312n/a eq(outer.as_string(), '''\
1313n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1314n/aMIME-Version: 1.0
1315n/aSubject: A subject
1316n/aTo: aperson@dom.ain
1317n/aFrom: bperson@dom.ain
1318n/a
1319n/a--BOUNDARY
1320n/aContent-Type: text/plain; charset="us-ascii"
1321n/aMIME-Version: 1.0
1322n/aContent-Transfer-Encoding: 7bit
1323n/a
1324n/ahello world
1325n/a--BOUNDARY--''')
1326n/a
1327n/a def test_seq_parts_in_a_multipart_with_empty_preamble(self):
1328n/a eq = self.ndiffAssertEqual
1329n/a outer = MIMEBase('multipart', 'mixed')
1330n/a outer['Subject'] = 'A subject'
1331n/a outer['To'] = 'aperson@dom.ain'
1332n/a outer['From'] = 'bperson@dom.ain'
1333n/a outer.preamble = ''
1334n/a msg = MIMEText('hello world')
1335n/a outer.attach(msg)
1336n/a outer.set_boundary('BOUNDARY')
1337n/a eq(outer.as_string(), '''\
1338n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1339n/aMIME-Version: 1.0
1340n/aSubject: A subject
1341n/aTo: aperson@dom.ain
1342n/aFrom: bperson@dom.ain
1343n/a
1344n/a
1345n/a--BOUNDARY
1346n/aContent-Type: text/plain; charset="us-ascii"
1347n/aMIME-Version: 1.0
1348n/aContent-Transfer-Encoding: 7bit
1349n/a
1350n/ahello world
1351n/a--BOUNDARY--''')
1352n/a
1353n/a
1354n/a def test_seq_parts_in_a_multipart_with_none_preamble(self):
1355n/a eq = self.ndiffAssertEqual
1356n/a outer = MIMEBase('multipart', 'mixed')
1357n/a outer['Subject'] = 'A subject'
1358n/a outer['To'] = 'aperson@dom.ain'
1359n/a outer['From'] = 'bperson@dom.ain'
1360n/a outer.preamble = None
1361n/a msg = MIMEText('hello world')
1362n/a outer.attach(msg)
1363n/a outer.set_boundary('BOUNDARY')
1364n/a eq(outer.as_string(), '''\
1365n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1366n/aMIME-Version: 1.0
1367n/aSubject: A subject
1368n/aTo: aperson@dom.ain
1369n/aFrom: bperson@dom.ain
1370n/a
1371n/a--BOUNDARY
1372n/aContent-Type: text/plain; charset="us-ascii"
1373n/aMIME-Version: 1.0
1374n/aContent-Transfer-Encoding: 7bit
1375n/a
1376n/ahello world
1377n/a--BOUNDARY--''')
1378n/a
1379n/a
1380n/a def test_seq_parts_in_a_multipart_with_none_epilogue(self):
1381n/a eq = self.ndiffAssertEqual
1382n/a outer = MIMEBase('multipart', 'mixed')
1383n/a outer['Subject'] = 'A subject'
1384n/a outer['To'] = 'aperson@dom.ain'
1385n/a outer['From'] = 'bperson@dom.ain'
1386n/a outer.epilogue = None
1387n/a msg = MIMEText('hello world')
1388n/a outer.attach(msg)
1389n/a outer.set_boundary('BOUNDARY')
1390n/a eq(outer.as_string(), '''\
1391n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1392n/aMIME-Version: 1.0
1393n/aSubject: A subject
1394n/aTo: aperson@dom.ain
1395n/aFrom: bperson@dom.ain
1396n/a
1397n/a--BOUNDARY
1398n/aContent-Type: text/plain; charset="us-ascii"
1399n/aMIME-Version: 1.0
1400n/aContent-Transfer-Encoding: 7bit
1401n/a
1402n/ahello world
1403n/a--BOUNDARY--''')
1404n/a
1405n/a
1406n/a def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
1407n/a eq = self.ndiffAssertEqual
1408n/a outer = MIMEBase('multipart', 'mixed')
1409n/a outer['Subject'] = 'A subject'
1410n/a outer['To'] = 'aperson@dom.ain'
1411n/a outer['From'] = 'bperson@dom.ain'
1412n/a outer.epilogue = ''
1413n/a msg = MIMEText('hello world')
1414n/a outer.attach(msg)
1415n/a outer.set_boundary('BOUNDARY')
1416n/a eq(outer.as_string(), '''\
1417n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1418n/aMIME-Version: 1.0
1419n/aSubject: A subject
1420n/aTo: aperson@dom.ain
1421n/aFrom: bperson@dom.ain
1422n/a
1423n/a--BOUNDARY
1424n/aContent-Type: text/plain; charset="us-ascii"
1425n/aMIME-Version: 1.0
1426n/aContent-Transfer-Encoding: 7bit
1427n/a
1428n/ahello world
1429n/a--BOUNDARY--
1430n/a''')
1431n/a
1432n/a
1433n/a def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
1434n/a eq = self.ndiffAssertEqual
1435n/a outer = MIMEBase('multipart', 'mixed')
1436n/a outer['Subject'] = 'A subject'
1437n/a outer['To'] = 'aperson@dom.ain'
1438n/a outer['From'] = 'bperson@dom.ain'
1439n/a outer.epilogue = '\n'
1440n/a msg = MIMEText('hello world')
1441n/a outer.attach(msg)
1442n/a outer.set_boundary('BOUNDARY')
1443n/a eq(outer.as_string(), '''\
1444n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1445n/aMIME-Version: 1.0
1446n/aSubject: A subject
1447n/aTo: aperson@dom.ain
1448n/aFrom: bperson@dom.ain
1449n/a
1450n/a--BOUNDARY
1451n/aContent-Type: text/plain; charset="us-ascii"
1452n/aMIME-Version: 1.0
1453n/aContent-Transfer-Encoding: 7bit
1454n/a
1455n/ahello world
1456n/a--BOUNDARY--
1457n/a
1458n/a''')
1459n/a
1460n/a def test_message_external_body(self):
1461n/a eq = self.assertEqual
1462n/a msg = self._msgobj('msg_36.txt')
1463n/a eq(len(msg.get_payload()), 2)
1464n/a msg1 = msg.get_payload(1)
1465n/a eq(msg1.get_content_type(), 'multipart/alternative')
1466n/a eq(len(msg1.get_payload()), 2)
1467n/a for subpart in msg1.get_payload():
1468n/a eq(subpart.get_content_type(), 'message/external-body')
1469n/a eq(len(subpart.get_payload()), 1)
1470n/a subsubpart = subpart.get_payload(0)
1471n/a eq(subsubpart.get_content_type(), 'text/plain')
1472n/a
1473n/a def test_double_boundary(self):
1474n/a # msg_37.txt is a multipart that contains two dash-boundary's in a
1475n/a # row. Our interpretation of RFC 2046 calls for ignoring the second
1476n/a # and subsequent boundaries.
1477n/a msg = self._msgobj('msg_37.txt')
1478n/a self.assertEqual(len(msg.get_payload()), 3)
1479n/a
1480n/a def test_nested_inner_contains_outer_boundary(self):
1481n/a eq = self.ndiffAssertEqual
1482n/a # msg_38.txt has an inner part that contains outer boundaries. My
1483n/a # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
1484n/a # these are illegal and should be interpreted as unterminated inner
1485n/a # parts.
1486n/a msg = self._msgobj('msg_38.txt')
1487n/a sfp = StringIO()
1488n/a iterators._structure(msg, sfp)
1489n/a eq(sfp.getvalue(), """\
1490n/amultipart/mixed
1491n/a multipart/mixed
1492n/a multipart/alternative
1493n/a text/plain
1494n/a text/plain
1495n/a text/plain
1496n/a text/plain
1497n/a""")
1498n/a
1499n/a def test_nested_with_same_boundary(self):
1500n/a eq = self.ndiffAssertEqual
1501n/a # msg 39.txt is similarly evil in that it's got inner parts that use
1502n/a # the same boundary as outer parts. Again, I believe the way this is
1503n/a # parsed is closest to the spirit of RFC 2046
1504n/a msg = self._msgobj('msg_39.txt')
1505n/a sfp = StringIO()
1506n/a iterators._structure(msg, sfp)
1507n/a eq(sfp.getvalue(), """\
1508n/amultipart/mixed
1509n/a multipart/mixed
1510n/a multipart/alternative
1511n/a application/octet-stream
1512n/a application/octet-stream
1513n/a text/plain
1514n/a""")
1515n/a
1516n/a def test_boundary_in_non_multipart(self):
1517n/a msg = self._msgobj('msg_40.txt')
1518n/a self.assertEqual(msg.as_string(), '''\
1519n/aMIME-Version: 1.0
1520n/aContent-Type: text/html; boundary="--961284236552522269"
1521n/a
1522n/a----961284236552522269
1523n/aContent-Type: text/html;
1524n/aContent-Transfer-Encoding: 7Bit
1525n/a
1526n/a<html></html>
1527n/a
1528n/a----961284236552522269--
1529n/a''')
1530n/a
1531n/a def test_boundary_with_leading_space(self):
1532n/a eq = self.assertEqual
1533n/a msg = email.message_from_string('''\
1534n/aMIME-Version: 1.0
1535n/aContent-Type: multipart/mixed; boundary=" XXXX"
1536n/a
1537n/a-- XXXX
1538n/aContent-Type: text/plain
1539n/a
1540n/a
1541n/a-- XXXX
1542n/aContent-Type: text/plain
1543n/a
1544n/a-- XXXX--
1545n/a''')
1546n/a self.assertTrue(msg.is_multipart())
1547n/a eq(msg.get_boundary(), ' XXXX')
1548n/a eq(len(msg.get_payload()), 2)
1549n/a
1550n/a def test_boundary_without_trailing_newline(self):
1551n/a m = Parser().parsestr("""\
1552n/aContent-Type: multipart/mixed; boundary="===============0012394164=="
1553n/aMIME-Version: 1.0
1554n/a
1555n/a--===============0012394164==
1556n/aContent-Type: image/file1.jpg
1557n/aMIME-Version: 1.0
1558n/aContent-Transfer-Encoding: base64
1559n/a
1560n/aYXNkZg==
1561n/a--===============0012394164==--""")
1562n/a self.assertEqual(m.get_payload(0).get_payload(), 'YXNkZg==')
1563n/a
1564n/a
1565n/a
1566n/a# Test some badly formatted messages
1567n/aclass TestNonConformant(TestEmailBase):
1568n/a def test_parse_missing_minor_type(self):
1569n/a eq = self.assertEqual
1570n/a msg = self._msgobj('msg_14.txt')
1571n/a eq(msg.get_content_type(), 'text/plain')
1572n/a eq(msg.get_content_maintype(), 'text')
1573n/a eq(msg.get_content_subtype(), 'plain')
1574n/a
1575n/a def test_same_boundary_inner_outer(self):
1576n/a unless = self.assertTrue
1577n/a msg = self._msgobj('msg_15.txt')
1578n/a # XXX We can probably eventually do better
1579n/a inner = msg.get_payload(0)
1580n/a unless(hasattr(inner, 'defects'))
1581n/a self.assertEqual(len(inner.defects), 1)
1582n/a unless(isinstance(inner.defects[0],
1583n/a errors.StartBoundaryNotFoundDefect))
1584n/a
1585n/a def test_multipart_no_boundary(self):
1586n/a unless = self.assertTrue
1587n/a msg = self._msgobj('msg_25.txt')
1588n/a unless(isinstance(msg.get_payload(), str))
1589n/a self.assertEqual(len(msg.defects), 2)
1590n/a unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
1591n/a unless(isinstance(msg.defects[1],
1592n/a errors.MultipartInvariantViolationDefect))
1593n/a
1594n/a def test_invalid_content_type(self):
1595n/a eq = self.assertEqual
1596n/a neq = self.ndiffAssertEqual
1597n/a msg = Message()
1598n/a # RFC 2045, $5.2 says invalid yields text/plain
1599n/a msg['Content-Type'] = 'text'
1600n/a eq(msg.get_content_maintype(), 'text')
1601n/a eq(msg.get_content_subtype(), 'plain')
1602n/a eq(msg.get_content_type(), 'text/plain')
1603n/a # Clear the old value and try something /really/ invalid
1604n/a del msg['content-type']
1605n/a msg['Content-Type'] = 'foo'
1606n/a eq(msg.get_content_maintype(), 'text')
1607n/a eq(msg.get_content_subtype(), 'plain')
1608n/a eq(msg.get_content_type(), 'text/plain')
1609n/a # Still, make sure that the message is idempotently generated
1610n/a s = StringIO()
1611n/a g = Generator(s)
1612n/a g.flatten(msg)
1613n/a neq(s.getvalue(), 'Content-Type: foo\n\n')
1614n/a
1615n/a def test_no_start_boundary(self):
1616n/a eq = self.ndiffAssertEqual
1617n/a msg = self._msgobj('msg_31.txt')
1618n/a eq(msg.get_payload(), """\
1619n/a--BOUNDARY
1620n/aContent-Type: text/plain
1621n/a
1622n/amessage 1
1623n/a
1624n/a--BOUNDARY
1625n/aContent-Type: text/plain
1626n/a
1627n/amessage 2
1628n/a
1629n/a--BOUNDARY--
1630n/a""")
1631n/a
1632n/a def test_no_separating_blank_line(self):
1633n/a eq = self.ndiffAssertEqual
1634n/a msg = self._msgobj('msg_35.txt')
1635n/a eq(msg.as_string(), """\
1636n/aFrom: aperson@dom.ain
1637n/aTo: bperson@dom.ain
1638n/aSubject: here's something interesting
1639n/a
1640n/acounter to RFC 2822, there's no separating newline here
1641n/a""")
1642n/a
1643n/a def test_lying_multipart(self):
1644n/a unless = self.assertTrue
1645n/a msg = self._msgobj('msg_41.txt')
1646n/a unless(hasattr(msg, 'defects'))
1647n/a self.assertEqual(len(msg.defects), 2)
1648n/a unless(isinstance(msg.defects[0], errors.NoBoundaryInMultipartDefect))
1649n/a unless(isinstance(msg.defects[1],
1650n/a errors.MultipartInvariantViolationDefect))
1651n/a
1652n/a def test_missing_start_boundary(self):
1653n/a outer = self._msgobj('msg_42.txt')
1654n/a # The message structure is:
1655n/a #
1656n/a # multipart/mixed
1657n/a # text/plain
1658n/a # message/rfc822
1659n/a # multipart/mixed [*]
1660n/a #
1661n/a # [*] This message is missing its start boundary
1662n/a bad = outer.get_payload(1).get_payload(0)
1663n/a self.assertEqual(len(bad.defects), 1)
1664n/a self.assertTrue(isinstance(bad.defects[0],
1665n/a errors.StartBoundaryNotFoundDefect))
1666n/a
1667n/a def test_first_line_is_continuation_header(self):
1668n/a eq = self.assertEqual
1669n/a m = ' Line 1\nLine 2\nLine 3'
1670n/a msg = email.message_from_string(m)
1671n/a eq(msg.keys(), [])
1672n/a eq(msg.get_payload(), 'Line 2\nLine 3')
1673n/a eq(len(msg.defects), 1)
1674n/a self.assertTrue(isinstance(msg.defects[0],
1675n/a errors.FirstHeaderLineIsContinuationDefect))
1676n/a eq(msg.defects[0].line, ' Line 1\n')
1677n/a
1678n/a
1679n/a
1680n/a# Test RFC 2047 header encoding and decoding
1681n/aclass TestRFC2047(TestEmailBase):
1682n/a def test_rfc2047_multiline(self):
1683n/a eq = self.assertEqual
1684n/a s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
1685n/a foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
1686n/a dh = decode_header(s)
1687n/a eq(dh, [
1688n/a (b'Re:', None),
1689n/a (b'r\x8aksm\x9arg\x8cs', 'mac-iceland'),
1690n/a (b'baz foo bar', None),
1691n/a (b'r\x8aksm\x9arg\x8cs', 'mac-iceland')])
1692n/a header = make_header(dh)
1693n/a eq(str(header),
1694n/a 'Re: r\xe4ksm\xf6rg\xe5s baz foo bar r\xe4ksm\xf6rg\xe5s')
1695n/a self.ndiffAssertEqual(header.encode(maxlinelen=76), """\
1696n/aRe: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?=
1697n/a =?mac-iceland?q?=9Arg=8Cs?=""")
1698n/a
1699n/a def test_whitespace_eater_unicode(self):
1700n/a eq = self.assertEqual
1701n/a s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
1702n/a dh = decode_header(s)
1703n/a eq(dh, [(b'Andr\xe9', 'iso-8859-1'),
1704n/a (b'Pirard <pirard@dom.ain>', None)])
1705n/a header = str(make_header(dh))
1706n/a eq(header, 'Andr\xe9 Pirard <pirard@dom.ain>')
1707n/a
1708n/a def test_whitespace_eater_unicode_2(self):
1709n/a eq = self.assertEqual
1710n/a s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
1711n/a dh = decode_header(s)
1712n/a eq(dh, [(b'The', None), (b'quick brown fox', 'iso-8859-1'),
1713n/a (b'jumped over the', None), (b'lazy dog', 'iso-8859-1')])
1714n/a hu = str(make_header(dh))
1715n/a eq(hu, 'The quick brown fox jumped over the lazy dog')
1716n/a
1717n/a def test_rfc2047_missing_whitespace(self):
1718n/a s = 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
1719n/a dh = decode_header(s)
1720n/a self.assertEqual(dh, [(s, None)])
1721n/a
1722n/a def test_rfc2047_with_whitespace(self):
1723n/a s = 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
1724n/a dh = decode_header(s)
1725n/a self.assertEqual(dh, [(b'Sm', None), (b'\xf6', 'iso-8859-1'),
1726n/a (b'rg', None), (b'\xe5', 'iso-8859-1'),
1727n/a (b'sbord', None)])
1728n/a
1729n/a def test_rfc2047_B_bad_padding(self):
1730n/a s = '=?iso-8859-1?B?%s?='
1731n/a data = [ # only test complete bytes
1732n/a ('dm==', b'v'), ('dm=', b'v'), ('dm', b'v'),
1733n/a ('dmk=', b'vi'), ('dmk', b'vi')
1734n/a ]
1735n/a for q, a in data:
1736n/a dh = decode_header(s % q)
1737n/a self.assertEqual(dh, [(a, 'iso-8859-1')])
1738n/a
1739n/a def test_rfc2047_Q_invalid_digits(self):
1740n/a # issue 10004.
1741n/a s = '=?iso-8659-1?Q?andr=e9=zz?='
1742n/a self.assertEqual(decode_header(s),
1743n/a [(b'andr\xe9=zz', 'iso-8659-1')])
1744n/a
1745n/a
1746n/a# Test the MIMEMessage class
1747n/aclass TestMIMEMessage(TestEmailBase):
1748n/a def setUp(self):
1749n/a with openfile('msg_11.txt') as fp:
1750n/a self._text = fp.read()
1751n/a
1752n/a def test_type_error(self):
1753n/a self.assertRaises(TypeError, MIMEMessage, 'a plain string')
1754n/a
1755n/a def test_valid_argument(self):
1756n/a eq = self.assertEqual
1757n/a unless = self.assertTrue
1758n/a subject = 'A sub-message'
1759n/a m = Message()
1760n/a m['Subject'] = subject
1761n/a r = MIMEMessage(m)
1762n/a eq(r.get_content_type(), 'message/rfc822')
1763n/a payload = r.get_payload()
1764n/a unless(isinstance(payload, list))
1765n/a eq(len(payload), 1)
1766n/a subpart = payload[0]
1767n/a unless(subpart is m)
1768n/a eq(subpart['subject'], subject)
1769n/a
1770n/a def test_bad_multipart(self):
1771n/a eq = self.assertEqual
1772n/a msg1 = Message()
1773n/a msg1['Subject'] = 'subpart 1'
1774n/a msg2 = Message()
1775n/a msg2['Subject'] = 'subpart 2'
1776n/a r = MIMEMessage(msg1)
1777n/a self.assertRaises(errors.MultipartConversionError, r.attach, msg2)
1778n/a
1779n/a def test_generate(self):
1780n/a # First craft the message to be encapsulated
1781n/a m = Message()
1782n/a m['Subject'] = 'An enclosed message'
1783n/a m.set_payload('Here is the body of the message.\n')
1784n/a r = MIMEMessage(m)
1785n/a r['Subject'] = 'The enclosing message'
1786n/a s = StringIO()
1787n/a g = Generator(s)
1788n/a g.flatten(r)
1789n/a self.assertEqual(s.getvalue(), """\
1790n/aContent-Type: message/rfc822
1791n/aMIME-Version: 1.0
1792n/aSubject: The enclosing message
1793n/a
1794n/aSubject: An enclosed message
1795n/a
1796n/aHere is the body of the message.
1797n/a""")
1798n/a
1799n/a def test_parse_message_rfc822(self):
1800n/a eq = self.assertEqual
1801n/a unless = self.assertTrue
1802n/a msg = self._msgobj('msg_11.txt')
1803n/a eq(msg.get_content_type(), 'message/rfc822')
1804n/a payload = msg.get_payload()
1805n/a unless(isinstance(payload, list))
1806n/a eq(len(payload), 1)
1807n/a submsg = payload[0]
1808n/a self.assertTrue(isinstance(submsg, Message))
1809n/a eq(submsg['subject'], 'An enclosed message')
1810n/a eq(submsg.get_payload(), 'Here is the body of the message.\n')
1811n/a
1812n/a def test_dsn(self):
1813n/a eq = self.assertEqual
1814n/a unless = self.assertTrue
1815n/a # msg 16 is a Delivery Status Notification, see RFC 1894
1816n/a msg = self._msgobj('msg_16.txt')
1817n/a eq(msg.get_content_type(), 'multipart/report')
1818n/a unless(msg.is_multipart())
1819n/a eq(len(msg.get_payload()), 3)
1820n/a # Subpart 1 is a text/plain, human readable section
1821n/a subpart = msg.get_payload(0)
1822n/a eq(subpart.get_content_type(), 'text/plain')
1823n/a eq(subpart.get_payload(), """\
1824n/aThis report relates to a message you sent with the following header fields:
1825n/a
1826n/a Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1827n/a Date: Sun, 23 Sep 2001 20:10:55 -0700
1828n/a From: "Ian T. Henry" <henryi@oxy.edu>
1829n/a To: SoCal Raves <scr@socal-raves.org>
1830n/a Subject: [scr] yeah for Ians!!
1831n/a
1832n/aYour message cannot be delivered to the following recipients:
1833n/a
1834n/a Recipient address: jangel1@cougar.noc.ucla.edu
1835n/a Reason: recipient reached disk quota
1836n/a
1837n/a""")
1838n/a # Subpart 2 contains the machine parsable DSN information. It
1839n/a # consists of two blocks of headers, represented by two nested Message
1840n/a # objects.
1841n/a subpart = msg.get_payload(1)
1842n/a eq(subpart.get_content_type(), 'message/delivery-status')
1843n/a eq(len(subpart.get_payload()), 2)
1844n/a # message/delivery-status should treat each block as a bunch of
1845n/a # headers, i.e. a bunch of Message objects.
1846n/a dsn1 = subpart.get_payload(0)
1847n/a unless(isinstance(dsn1, Message))
1848n/a eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1849n/a eq(dsn1.get_param('dns', header='reporting-mta'), '')
1850n/a # Try a missing one <wink>
1851n/a eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1852n/a dsn2 = subpart.get_payload(1)
1853n/a unless(isinstance(dsn2, Message))
1854n/a eq(dsn2['action'], 'failed')
1855n/a eq(dsn2.get_params(header='original-recipient'),
1856n/a [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1857n/a eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1858n/a # Subpart 3 is the original message
1859n/a subpart = msg.get_payload(2)
1860n/a eq(subpart.get_content_type(), 'message/rfc822')
1861n/a payload = subpart.get_payload()
1862n/a unless(isinstance(payload, list))
1863n/a eq(len(payload), 1)
1864n/a subsubpart = payload[0]
1865n/a unless(isinstance(subsubpart, Message))
1866n/a eq(subsubpart.get_content_type(), 'text/plain')
1867n/a eq(subsubpart['message-id'],
1868n/a '<002001c144a6$8752e060$56104586@oxy.edu>')
1869n/a
1870n/a def test_epilogue(self):
1871n/a eq = self.ndiffAssertEqual
1872n/a with openfile('msg_21.txt') as fp:
1873n/a text = fp.read()
1874n/a msg = Message()
1875n/a msg['From'] = 'aperson@dom.ain'
1876n/a msg['To'] = 'bperson@dom.ain'
1877n/a msg['Subject'] = 'Test'
1878n/a msg.preamble = 'MIME message'
1879n/a msg.epilogue = 'End of MIME message\n'
1880n/a msg1 = MIMEText('One')
1881n/a msg2 = MIMEText('Two')
1882n/a msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1883n/a msg.attach(msg1)
1884n/a msg.attach(msg2)
1885n/a sfp = StringIO()
1886n/a g = Generator(sfp)
1887n/a g.flatten(msg)
1888n/a eq(sfp.getvalue(), text)
1889n/a
1890n/a def test_no_nl_preamble(self):
1891n/a eq = self.ndiffAssertEqual
1892n/a msg = Message()
1893n/a msg['From'] = 'aperson@dom.ain'
1894n/a msg['To'] = 'bperson@dom.ain'
1895n/a msg['Subject'] = 'Test'
1896n/a msg.preamble = 'MIME message'
1897n/a msg.epilogue = ''
1898n/a msg1 = MIMEText('One')
1899n/a msg2 = MIMEText('Two')
1900n/a msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
1901n/a msg.attach(msg1)
1902n/a msg.attach(msg2)
1903n/a eq(msg.as_string(), """\
1904n/aFrom: aperson@dom.ain
1905n/aTo: bperson@dom.ain
1906n/aSubject: Test
1907n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1908n/a
1909n/aMIME message
1910n/a--BOUNDARY
1911n/aContent-Type: text/plain; charset="us-ascii"
1912n/aMIME-Version: 1.0
1913n/aContent-Transfer-Encoding: 7bit
1914n/a
1915n/aOne
1916n/a--BOUNDARY
1917n/aContent-Type: text/plain; charset="us-ascii"
1918n/aMIME-Version: 1.0
1919n/aContent-Transfer-Encoding: 7bit
1920n/a
1921n/aTwo
1922n/a--BOUNDARY--
1923n/a""")
1924n/a
1925n/a def test_default_type(self):
1926n/a eq = self.assertEqual
1927n/a with openfile('msg_30.txt') as fp:
1928n/a msg = email.message_from_file(fp)
1929n/a container1 = msg.get_payload(0)
1930n/a eq(container1.get_default_type(), 'message/rfc822')
1931n/a eq(container1.get_content_type(), 'message/rfc822')
1932n/a container2 = msg.get_payload(1)
1933n/a eq(container2.get_default_type(), 'message/rfc822')
1934n/a eq(container2.get_content_type(), 'message/rfc822')
1935n/a container1a = container1.get_payload(0)
1936n/a eq(container1a.get_default_type(), 'text/plain')
1937n/a eq(container1a.get_content_type(), 'text/plain')
1938n/a container2a = container2.get_payload(0)
1939n/a eq(container2a.get_default_type(), 'text/plain')
1940n/a eq(container2a.get_content_type(), 'text/plain')
1941n/a
1942n/a def test_default_type_with_explicit_container_type(self):
1943n/a eq = self.assertEqual
1944n/a with openfile('msg_28.txt') as fp:
1945n/a msg = email.message_from_file(fp)
1946n/a container1 = msg.get_payload(0)
1947n/a eq(container1.get_default_type(), 'message/rfc822')
1948n/a eq(container1.get_content_type(), 'message/rfc822')
1949n/a container2 = msg.get_payload(1)
1950n/a eq(container2.get_default_type(), 'message/rfc822')
1951n/a eq(container2.get_content_type(), 'message/rfc822')
1952n/a container1a = container1.get_payload(0)
1953n/a eq(container1a.get_default_type(), 'text/plain')
1954n/a eq(container1a.get_content_type(), 'text/plain')
1955n/a container2a = container2.get_payload(0)
1956n/a eq(container2a.get_default_type(), 'text/plain')
1957n/a eq(container2a.get_content_type(), 'text/plain')
1958n/a
1959n/a def test_default_type_non_parsed(self):
1960n/a eq = self.assertEqual
1961n/a neq = self.ndiffAssertEqual
1962n/a # Set up container
1963n/a container = MIMEMultipart('digest', 'BOUNDARY')
1964n/a container.epilogue = ''
1965n/a # Set up subparts
1966n/a subpart1a = MIMEText('message 1\n')
1967n/a subpart2a = MIMEText('message 2\n')
1968n/a subpart1 = MIMEMessage(subpart1a)
1969n/a subpart2 = MIMEMessage(subpart2a)
1970n/a container.attach(subpart1)
1971n/a container.attach(subpart2)
1972n/a eq(subpart1.get_content_type(), 'message/rfc822')
1973n/a eq(subpart1.get_default_type(), 'message/rfc822')
1974n/a eq(subpart2.get_content_type(), 'message/rfc822')
1975n/a eq(subpart2.get_default_type(), 'message/rfc822')
1976n/a neq(container.as_string(0), '''\
1977n/aContent-Type: multipart/digest; boundary="BOUNDARY"
1978n/aMIME-Version: 1.0
1979n/a
1980n/a--BOUNDARY
1981n/aContent-Type: message/rfc822
1982n/aMIME-Version: 1.0
1983n/a
1984n/aContent-Type: text/plain; charset="us-ascii"
1985n/aMIME-Version: 1.0
1986n/aContent-Transfer-Encoding: 7bit
1987n/a
1988n/amessage 1
1989n/a
1990n/a--BOUNDARY
1991n/aContent-Type: message/rfc822
1992n/aMIME-Version: 1.0
1993n/a
1994n/aContent-Type: text/plain; charset="us-ascii"
1995n/aMIME-Version: 1.0
1996n/aContent-Transfer-Encoding: 7bit
1997n/a
1998n/amessage 2
1999n/a
2000n/a--BOUNDARY--
2001n/a''')
2002n/a del subpart1['content-type']
2003n/a del subpart1['mime-version']
2004n/a del subpart2['content-type']
2005n/a del subpart2['mime-version']
2006n/a eq(subpart1.get_content_type(), 'message/rfc822')
2007n/a eq(subpart1.get_default_type(), 'message/rfc822')
2008n/a eq(subpart2.get_content_type(), 'message/rfc822')
2009n/a eq(subpart2.get_default_type(), 'message/rfc822')
2010n/a neq(container.as_string(0), '''\
2011n/aContent-Type: multipart/digest; boundary="BOUNDARY"
2012n/aMIME-Version: 1.0
2013n/a
2014n/a--BOUNDARY
2015n/a
2016n/aContent-Type: text/plain; charset="us-ascii"
2017n/aMIME-Version: 1.0
2018n/aContent-Transfer-Encoding: 7bit
2019n/a
2020n/amessage 1
2021n/a
2022n/a--BOUNDARY
2023n/a
2024n/aContent-Type: text/plain; charset="us-ascii"
2025n/aMIME-Version: 1.0
2026n/aContent-Transfer-Encoding: 7bit
2027n/a
2028n/amessage 2
2029n/a
2030n/a--BOUNDARY--
2031n/a''')
2032n/a
2033n/a def test_mime_attachments_in_constructor(self):
2034n/a eq = self.assertEqual
2035n/a text1 = MIMEText('')
2036n/a text2 = MIMEText('')
2037n/a msg = MIMEMultipart(_subparts=(text1, text2))
2038n/a eq(len(msg.get_payload()), 2)
2039n/a eq(msg.get_payload(0), text1)
2040n/a eq(msg.get_payload(1), text2)
2041n/a
2042n/a def test_default_multipart_constructor(self):
2043n/a msg = MIMEMultipart()
2044n/a self.assertTrue(msg.is_multipart())
2045n/a
2046n/a
2047n/a# A general test of parser->model->generator idempotency. IOW, read a message
2048n/a# in, parse it into a message object tree, then without touching the tree,
2049n/a# regenerate the plain text. The original text and the transformed text
2050n/a# should be identical. Note: that we ignore the Unix-From since that may
2051n/a# contain a changed date.
2052n/aclass TestIdempotent(TestEmailBase):
2053n/a
2054n/a linesep = '\n'
2055n/a
2056n/a def _msgobj(self, filename):
2057n/a with openfile(filename) as fp:
2058n/a data = fp.read()
2059n/a msg = email.message_from_string(data)
2060n/a return msg, data
2061n/a
2062n/a def _idempotent(self, msg, text, unixfrom=False):
2063n/a eq = self.ndiffAssertEqual
2064n/a s = StringIO()
2065n/a g = Generator(s, maxheaderlen=0)
2066n/a g.flatten(msg, unixfrom=unixfrom)
2067n/a eq(text, s.getvalue())
2068n/a
2069n/a def test_parse_text_message(self):
2070n/a eq = self.assertEqual
2071n/a msg, text = self._msgobj('msg_01.txt')
2072n/a eq(msg.get_content_type(), 'text/plain')
2073n/a eq(msg.get_content_maintype(), 'text')
2074n/a eq(msg.get_content_subtype(), 'plain')
2075n/a eq(msg.get_params()[1], ('charset', 'us-ascii'))
2076n/a eq(msg.get_param('charset'), 'us-ascii')
2077n/a eq(msg.preamble, None)
2078n/a eq(msg.epilogue, None)
2079n/a self._idempotent(msg, text)
2080n/a
2081n/a def test_parse_untyped_message(self):
2082n/a eq = self.assertEqual
2083n/a msg, text = self._msgobj('msg_03.txt')
2084n/a eq(msg.get_content_type(), 'text/plain')
2085n/a eq(msg.get_params(), None)
2086n/a eq(msg.get_param('charset'), None)
2087n/a self._idempotent(msg, text)
2088n/a
2089n/a def test_simple_multipart(self):
2090n/a msg, text = self._msgobj('msg_04.txt')
2091n/a self._idempotent(msg, text)
2092n/a
2093n/a def test_MIME_digest(self):
2094n/a msg, text = self._msgobj('msg_02.txt')
2095n/a self._idempotent(msg, text)
2096n/a
2097n/a def test_long_header(self):
2098n/a msg, text = self._msgobj('msg_27.txt')
2099n/a self._idempotent(msg, text)
2100n/a
2101n/a def test_MIME_digest_with_part_headers(self):
2102n/a msg, text = self._msgobj('msg_28.txt')
2103n/a self._idempotent(msg, text)
2104n/a
2105n/a def test_mixed_with_image(self):
2106n/a msg, text = self._msgobj('msg_06.txt')
2107n/a self._idempotent(msg, text)
2108n/a
2109n/a def test_multipart_report(self):
2110n/a msg, text = self._msgobj('msg_05.txt')
2111n/a self._idempotent(msg, text)
2112n/a
2113n/a def test_dsn(self):
2114n/a msg, text = self._msgobj('msg_16.txt')
2115n/a self._idempotent(msg, text)
2116n/a
2117n/a def test_preamble_epilogue(self):
2118n/a msg, text = self._msgobj('msg_21.txt')
2119n/a self._idempotent(msg, text)
2120n/a
2121n/a def test_multipart_one_part(self):
2122n/a msg, text = self._msgobj('msg_23.txt')
2123n/a self._idempotent(msg, text)
2124n/a
2125n/a def test_multipart_no_parts(self):
2126n/a msg, text = self._msgobj('msg_24.txt')
2127n/a self._idempotent(msg, text)
2128n/a
2129n/a def test_no_start_boundary(self):
2130n/a msg, text = self._msgobj('msg_31.txt')
2131n/a self._idempotent(msg, text)
2132n/a
2133n/a def test_rfc2231_charset(self):
2134n/a msg, text = self._msgobj('msg_32.txt')
2135n/a self._idempotent(msg, text)
2136n/a
2137n/a def test_more_rfc2231_parameters(self):
2138n/a msg, text = self._msgobj('msg_33.txt')
2139n/a self._idempotent(msg, text)
2140n/a
2141n/a def test_text_plain_in_a_multipart_digest(self):
2142n/a msg, text = self._msgobj('msg_34.txt')
2143n/a self._idempotent(msg, text)
2144n/a
2145n/a def test_nested_multipart_mixeds(self):
2146n/a msg, text = self._msgobj('msg_12a.txt')
2147n/a self._idempotent(msg, text)
2148n/a
2149n/a def test_message_external_body_idempotent(self):
2150n/a msg, text = self._msgobj('msg_36.txt')
2151n/a self._idempotent(msg, text)
2152n/a
2153n/a def test_message_delivery_status(self):
2154n/a msg, text = self._msgobj('msg_43.txt')
2155n/a self._idempotent(msg, text, unixfrom=True)
2156n/a
2157n/a def test_message_signed_idempotent(self):
2158n/a msg, text = self._msgobj('msg_45.txt')
2159n/a self._idempotent(msg, text)
2160n/a
2161n/a def test_content_type(self):
2162n/a eq = self.assertEqual
2163n/a unless = self.assertTrue
2164n/a # Get a message object and reset the seek pointer for other tests
2165n/a msg, text = self._msgobj('msg_05.txt')
2166n/a eq(msg.get_content_type(), 'multipart/report')
2167n/a # Test the Content-Type: parameters
2168n/a params = {}
2169n/a for pk, pv in msg.get_params():
2170n/a params[pk] = pv
2171n/a eq(params['report-type'], 'delivery-status')
2172n/a eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
2173n/a eq(msg.preamble, 'This is a MIME-encapsulated message.' + self.linesep)
2174n/a eq(msg.epilogue, self.linesep)
2175n/a eq(len(msg.get_payload()), 3)
2176n/a # Make sure the subparts are what we expect
2177n/a msg1 = msg.get_payload(0)
2178n/a eq(msg1.get_content_type(), 'text/plain')
2179n/a eq(msg1.get_payload(), 'Yadda yadda yadda' + self.linesep)
2180n/a msg2 = msg.get_payload(1)
2181n/a eq(msg2.get_content_type(), 'text/plain')
2182n/a eq(msg2.get_payload(), 'Yadda yadda yadda' + self.linesep)
2183n/a msg3 = msg.get_payload(2)
2184n/a eq(msg3.get_content_type(), 'message/rfc822')
2185n/a self.assertTrue(isinstance(msg3, Message))
2186n/a payload = msg3.get_payload()
2187n/a unless(isinstance(payload, list))
2188n/a eq(len(payload), 1)
2189n/a msg4 = payload[0]
2190n/a unless(isinstance(msg4, Message))
2191n/a eq(msg4.get_payload(), 'Yadda yadda yadda' + self.linesep)
2192n/a
2193n/a def test_parser(self):
2194n/a eq = self.assertEqual
2195n/a unless = self.assertTrue
2196n/a msg, text = self._msgobj('msg_06.txt')
2197n/a # Check some of the outer headers
2198n/a eq(msg.get_content_type(), 'message/rfc822')
2199n/a # Make sure the payload is a list of exactly one sub-Message, and that
2200n/a # that submessage has a type of text/plain
2201n/a payload = msg.get_payload()
2202n/a unless(isinstance(payload, list))
2203n/a eq(len(payload), 1)
2204n/a msg1 = payload[0]
2205n/a self.assertTrue(isinstance(msg1, Message))
2206n/a eq(msg1.get_content_type(), 'text/plain')
2207n/a self.assertTrue(isinstance(msg1.get_payload(), str))
2208n/a eq(msg1.get_payload(), self.linesep)
2209n/a
2210n/a
2211n/a
2212n/a# Test various other bits of the package's functionality
2213n/aclass TestMiscellaneous(TestEmailBase):
2214n/a def test_message_from_string(self):
2215n/a with openfile('msg_01.txt') as fp:
2216n/a text = fp.read()
2217n/a msg = email.message_from_string(text)
2218n/a s = StringIO()
2219n/a # Don't wrap/continue long headers since we're trying to test
2220n/a # idempotency.
2221n/a g = Generator(s, maxheaderlen=0)
2222n/a g.flatten(msg)
2223n/a self.assertEqual(text, s.getvalue())
2224n/a
2225n/a def test_message_from_file(self):
2226n/a with openfile('msg_01.txt') as fp:
2227n/a text = fp.read()
2228n/a fp.seek(0)
2229n/a msg = email.message_from_file(fp)
2230n/a s = StringIO()
2231n/a # Don't wrap/continue long headers since we're trying to test
2232n/a # idempotency.
2233n/a g = Generator(s, maxheaderlen=0)
2234n/a g.flatten(msg)
2235n/a self.assertEqual(text, s.getvalue())
2236n/a
2237n/a def test_message_from_string_with_class(self):
2238n/a unless = self.assertTrue
2239n/a with openfile('msg_01.txt') as fp:
2240n/a text = fp.read()
2241n/a
2242n/a # Create a subclass
2243n/a class MyMessage(Message):
2244n/a pass
2245n/a
2246n/a msg = email.message_from_string(text, MyMessage)
2247n/a unless(isinstance(msg, MyMessage))
2248n/a # Try something more complicated
2249n/a with openfile('msg_02.txt') as fp:
2250n/a text = fp.read()
2251n/a msg = email.message_from_string(text, MyMessage)
2252n/a for subpart in msg.walk():
2253n/a unless(isinstance(subpart, MyMessage))
2254n/a
2255n/a def test_message_from_file_with_class(self):
2256n/a unless = self.assertTrue
2257n/a # Create a subclass
2258n/a class MyMessage(Message):
2259n/a pass
2260n/a
2261n/a with openfile('msg_01.txt') as fp:
2262n/a msg = email.message_from_file(fp, MyMessage)
2263n/a unless(isinstance(msg, MyMessage))
2264n/a # Try something more complicated
2265n/a with openfile('msg_02.txt') as fp:
2266n/a msg = email.message_from_file(fp, MyMessage)
2267n/a for subpart in msg.walk():
2268n/a unless(isinstance(subpart, MyMessage))
2269n/a
2270n/a def test__all__(self):
2271n/a module = __import__('email')
2272n/a # Can't use sorted() here due to Python 2.3 compatibility
2273n/a all = module.__all__[:]
2274n/a all.sort()
2275n/a self.assertEqual(all, [
2276n/a 'base64mime', 'charset', 'encoders', 'errors', 'generator',
2277n/a 'header', 'iterators', 'message', 'message_from_binary_file',
2278n/a 'message_from_bytes', 'message_from_file',
2279n/a 'message_from_string', 'mime', 'parser',
2280n/a 'quoprimime', 'utils',
2281n/a ])
2282n/a
2283n/a def test_formatdate(self):
2284n/a now = time.time()
2285n/a self.assertEqual(utils.parsedate(utils.formatdate(now))[:6],
2286n/a time.gmtime(now)[:6])
2287n/a
2288n/a def test_formatdate_localtime(self):
2289n/a now = time.time()
2290n/a self.assertEqual(
2291n/a utils.parsedate(utils.formatdate(now, localtime=True))[:6],
2292n/a time.localtime(now)[:6])
2293n/a
2294n/a def test_formatdate_usegmt(self):
2295n/a now = time.time()
2296n/a self.assertEqual(
2297n/a utils.formatdate(now, localtime=False),
2298n/a time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
2299n/a self.assertEqual(
2300n/a utils.formatdate(now, localtime=False, usegmt=True),
2301n/a time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
2302n/a
2303n/a def test_parsedate_none(self):
2304n/a self.assertEqual(utils.parsedate(''), None)
2305n/a
2306n/a def test_parsedate_compact(self):
2307n/a # The FWS after the comma is optional
2308n/a self.assertEqual(utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2309n/a utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2310n/a
2311n/a def test_parsedate_no_dayofweek(self):
2312n/a eq = self.assertEqual
2313n/a eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2314n/a (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
2315n/a
2316n/a def test_parsedate_compact_no_dayofweek(self):
2317n/a eq = self.assertEqual
2318n/a eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2319n/a (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
2320n/a
2321n/a def test_parsedate_no_space_before_positive_offset(self):
2322n/a self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26+0800'),
2323n/a (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
2324n/a
2325n/a def test_parsedate_no_space_before_negative_offset(self):
2326n/a # Issue 1155362: we already handled '+' for this case.
2327n/a self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26-0800'),
2328n/a (2002, 4, 3, 14, 58, 26, 0, 1, -1, -28800))
2329n/a
2330n/a
2331n/a def test_parsedate_acceptable_to_time_functions(self):
2332n/a eq = self.assertEqual
2333n/a timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
2334n/a t = int(time.mktime(timetup))
2335n/a eq(time.localtime(t)[:6], timetup[:6])
2336n/a eq(int(time.strftime('%Y', timetup)), 2003)
2337n/a timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
2338n/a t = int(time.mktime(timetup[:9]))
2339n/a eq(time.localtime(t)[:6], timetup[:6])
2340n/a eq(int(time.strftime('%Y', timetup[:9])), 2003)
2341n/a
2342n/a def test_parsedate_y2k(self):
2343n/a """Test for parsing a date with a two-digit year.
2344n/a
2345n/a Parsing a date with a two-digit year should return the correct
2346n/a four-digit year. RFC822 allows two-digit years, but RFC2822 (which
2347n/a obsoletes RFC822) requires four-digit years.
2348n/a
2349n/a """
2350n/a self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'),
2351n/a utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'))
2352n/a self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'),
2353n/a utils.parsedate_tz('25 Feb 1971 13:47:26 -0800'))
2354n/a
2355n/a def test_parseaddr_empty(self):
2356n/a self.assertEqual(utils.parseaddr('<>'), ('', ''))
2357n/a self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '')
2358n/a
2359n/a def test_noquote_dump(self):
2360n/a self.assertEqual(
2361n/a utils.formataddr(('A Silly Person', 'person@dom.ain')),
2362n/a 'A Silly Person <person@dom.ain>')
2363n/a
2364n/a def test_escape_dump(self):
2365n/a self.assertEqual(
2366n/a utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
2367n/a r'"A \(Very\) Silly Person" <person@dom.ain>')
2368n/a a = r'A \(Special\) Person'
2369n/a b = 'person@dom.ain'
2370n/a self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
2371n/a
2372n/a def test_escape_backslashes(self):
2373n/a self.assertEqual(
2374n/a utils.formataddr(('Arthur \Backslash\ Foobar', 'person@dom.ain')),
2375n/a r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
2376n/a a = r'Arthur \Backslash\ Foobar'
2377n/a b = 'person@dom.ain'
2378n/a self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
2379n/a
2380n/a def test_name_with_dot(self):
2381n/a x = 'John X. Doe <jxd@example.com>'
2382n/a y = '"John X. Doe" <jxd@example.com>'
2383n/a a, b = ('John X. Doe', 'jxd@example.com')
2384n/a self.assertEqual(utils.parseaddr(x), (a, b))
2385n/a self.assertEqual(utils.parseaddr(y), (a, b))
2386n/a # formataddr() quotes the name if there's a dot in it
2387n/a self.assertEqual(utils.formataddr((a, b)), y)
2388n/a
2389n/a def test_parseaddr_preserves_quoted_pairs_in_addresses(self):
2390n/a # issue 10005. Note that in the third test the second pair of
2391n/a # backslashes is not actually a quoted pair because it is not inside a
2392n/a # comment or quoted string: the address being parsed has a quoted
2393n/a # string containing a quoted backslash, followed by 'example' and two
2394n/a # backslashes, followed by another quoted string containing a space and
2395n/a # the word 'example'. parseaddr copies those two backslashes
2396n/a # literally. Per rfc5322 this is not technically correct since a \ may
2397n/a # not appear in an address outside of a quoted string. It is probably
2398n/a # a sensible Postel interpretation, though.
2399n/a eq = self.assertEqual
2400n/a eq(utils.parseaddr('""example" example"@example.com'),
2401n/a ('', '""example" example"@example.com'))
2402n/a eq(utils.parseaddr('"\\"example\\" example"@example.com'),
2403n/a ('', '"\\"example\\" example"@example.com'))
2404n/a eq(utils.parseaddr('"\\\\"example\\\\" example"@example.com'),
2405n/a ('', '"\\\\"example\\\\" example"@example.com'))
2406n/a
2407n/a def test_parseaddr_preserves_spaces_in_local_part(self):
2408n/a # issue 9286. A normal RFC5322 local part should not contain any
2409n/a # folding white space, but legacy local parts can (they are a sequence
2410n/a # of atoms, not dotatoms). On the other hand we strip whitespace from
2411n/a # before the @ and around dots, on the assumption that the whitespace
2412n/a # around the punctuation is a mistake in what would otherwise be
2413n/a # an RFC5322 local part. Leading whitespace is, usual, stripped as well.
2414n/a self.assertEqual(('', "merwok wok@xample.com"),
2415n/a utils.parseaddr("merwok wok@xample.com"))
2416n/a self.assertEqual(('', "merwok wok@xample.com"),
2417n/a utils.parseaddr("merwok wok@xample.com"))
2418n/a self.assertEqual(('', "merwok wok@xample.com"),
2419n/a utils.parseaddr(" merwok wok @xample.com"))
2420n/a self.assertEqual(('', 'merwok"wok" wok@xample.com'),
2421n/a utils.parseaddr('merwok"wok" wok@xample.com'))
2422n/a self.assertEqual(('', 'merwok.wok.wok@xample.com'),
2423n/a utils.parseaddr('merwok. wok . wok@xample.com'))
2424n/a
2425n/a def test_multiline_from_comment(self):
2426n/a x = """\
2427n/aFoo
2428n/a\tBar <foo@example.com>"""
2429n/a self.assertEqual(utils.parseaddr(x), ('Foo Bar', 'foo@example.com'))
2430n/a
2431n/a def test_quote_dump(self):
2432n/a self.assertEqual(
2433n/a utils.formataddr(('A Silly; Person', 'person@dom.ain')),
2434n/a r'"A Silly; Person" <person@dom.ain>')
2435n/a
2436n/a def test_charset_richcomparisons(self):
2437n/a eq = self.assertEqual
2438n/a ne = self.assertNotEqual
2439n/a cset1 = Charset()
2440n/a cset2 = Charset()
2441n/a eq(cset1, 'us-ascii')
2442n/a eq(cset1, 'US-ASCII')
2443n/a eq(cset1, 'Us-AsCiI')
2444n/a eq('us-ascii', cset1)
2445n/a eq('US-ASCII', cset1)
2446n/a eq('Us-AsCiI', cset1)
2447n/a ne(cset1, 'usascii')
2448n/a ne(cset1, 'USASCII')
2449n/a ne(cset1, 'UsAsCiI')
2450n/a ne('usascii', cset1)
2451n/a ne('USASCII', cset1)
2452n/a ne('UsAsCiI', cset1)
2453n/a eq(cset1, cset2)
2454n/a eq(cset2, cset1)
2455n/a
2456n/a def test_getaddresses(self):
2457n/a eq = self.assertEqual
2458n/a eq(utils.getaddresses(['aperson@dom.ain (Al Person)',
2459n/a 'Bud Person <bperson@dom.ain>']),
2460n/a [('Al Person', 'aperson@dom.ain'),
2461n/a ('Bud Person', 'bperson@dom.ain')])
2462n/a
2463n/a def test_getaddresses_nasty(self):
2464n/a eq = self.assertEqual
2465n/a eq(utils.getaddresses(['foo: ;']), [('', '')])
2466n/a eq(utils.getaddresses(
2467n/a ['[]*-- =~$']),
2468n/a [('', ''), ('', ''), ('', '*--')])
2469n/a eq(utils.getaddresses(
2470n/a ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
2471n/a [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
2472n/a
2473n/a def test_getaddresses_embedded_comment(self):
2474n/a """Test proper handling of a nested comment"""
2475n/a eq = self.assertEqual
2476n/a addrs = utils.getaddresses(['User ((nested comment)) <foo@bar.com>'])
2477n/a eq(addrs[0][1], 'foo@bar.com')
2478n/a
2479n/a def test_utils_quote_unquote(self):
2480n/a eq = self.assertEqual
2481n/a msg = Message()
2482n/a msg.add_header('content-disposition', 'attachment',
2483n/a filename='foo\\wacky"name')
2484n/a eq(msg.get_filename(), 'foo\\wacky"name')
2485n/a
2486n/a def test_get_body_encoding_with_bogus_charset(self):
2487n/a charset = Charset('not a charset')
2488n/a self.assertEqual(charset.get_body_encoding(), 'base64')
2489n/a
2490n/a def test_get_body_encoding_with_uppercase_charset(self):
2491n/a eq = self.assertEqual
2492n/a msg = Message()
2493n/a msg['Content-Type'] = 'text/plain; charset=UTF-8'
2494n/a eq(msg['content-type'], 'text/plain; charset=UTF-8')
2495n/a charsets = msg.get_charsets()
2496n/a eq(len(charsets), 1)
2497n/a eq(charsets[0], 'utf-8')
2498n/a charset = Charset(charsets[0])
2499n/a eq(charset.get_body_encoding(), 'base64')
2500n/a msg.set_payload(b'hello world', charset=charset)
2501n/a eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n')
2502n/a eq(msg.get_payload(decode=True), b'hello world')
2503n/a eq(msg['content-transfer-encoding'], 'base64')
2504n/a # Try another one
2505n/a msg = Message()
2506n/a msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
2507n/a charsets = msg.get_charsets()
2508n/a eq(len(charsets), 1)
2509n/a eq(charsets[0], 'us-ascii')
2510n/a charset = Charset(charsets[0])
2511n/a eq(charset.get_body_encoding(), encoders.encode_7or8bit)
2512n/a msg.set_payload('hello world', charset=charset)
2513n/a eq(msg.get_payload(), 'hello world')
2514n/a eq(msg['content-transfer-encoding'], '7bit')
2515n/a
2516n/a def test_charsets_case_insensitive(self):
2517n/a lc = Charset('us-ascii')
2518n/a uc = Charset('US-ASCII')
2519n/a self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
2520n/a
2521n/a def test_partial_falls_inside_message_delivery_status(self):
2522n/a eq = self.ndiffAssertEqual
2523n/a # The Parser interface provides chunks of data to FeedParser in 8192
2524n/a # byte gulps. SF bug #1076485 found one of those chunks inside
2525n/a # message/delivery-status header block, which triggered an
2526n/a # unreadline() of NeedMoreData.
2527n/a msg = self._msgobj('msg_43.txt')
2528n/a sfp = StringIO()
2529n/a iterators._structure(msg, sfp)
2530n/a eq(sfp.getvalue(), """\
2531n/amultipart/report
2532n/a text/plain
2533n/a message/delivery-status
2534n/a text/plain
2535n/a text/plain
2536n/a text/plain
2537n/a text/plain
2538n/a text/plain
2539n/a text/plain
2540n/a text/plain
2541n/a text/plain
2542n/a text/plain
2543n/a text/plain
2544n/a text/plain
2545n/a text/plain
2546n/a text/plain
2547n/a text/plain
2548n/a text/plain
2549n/a text/plain
2550n/a text/plain
2551n/a text/plain
2552n/a text/plain
2553n/a text/plain
2554n/a text/plain
2555n/a text/plain
2556n/a text/plain
2557n/a text/plain
2558n/a text/plain
2559n/a text/plain
2560n/a text/rfc822-headers
2561n/a""")
2562n/a
2563n/a def test_make_msgid_domain(self):
2564n/a self.assertEqual(
2565n/a email.utils.make_msgid(domain='testdomain-string')[-19:],
2566n/a '@testdomain-string>')
2567n/a
2568n/a
2569n/a# Test the iterator/generators
2570n/aclass TestIterators(TestEmailBase):
2571n/a def test_body_line_iterator(self):
2572n/a eq = self.assertEqual
2573n/a neq = self.ndiffAssertEqual
2574n/a # First a simple non-multipart message
2575n/a msg = self._msgobj('msg_01.txt')
2576n/a it = iterators.body_line_iterator(msg)
2577n/a lines = list(it)
2578n/a eq(len(lines), 6)
2579n/a neq(EMPTYSTRING.join(lines), msg.get_payload())
2580n/a # Now a more complicated multipart
2581n/a msg = self._msgobj('msg_02.txt')
2582n/a it = iterators.body_line_iterator(msg)
2583n/a lines = list(it)
2584n/a eq(len(lines), 43)
2585n/a with openfile('msg_19.txt') as fp:
2586n/a neq(EMPTYSTRING.join(lines), fp.read())
2587n/a
2588n/a def test_typed_subpart_iterator(self):
2589n/a eq = self.assertEqual
2590n/a msg = self._msgobj('msg_04.txt')
2591n/a it = iterators.typed_subpart_iterator(msg, 'text')
2592n/a lines = []
2593n/a subparts = 0
2594n/a for subpart in it:
2595n/a subparts += 1
2596n/a lines.append(subpart.get_payload())
2597n/a eq(subparts, 2)
2598n/a eq(EMPTYSTRING.join(lines), """\
2599n/aa simple kind of mirror
2600n/ato reflect upon our own
2601n/aa simple kind of mirror
2602n/ato reflect upon our own
2603n/a""")
2604n/a
2605n/a def test_typed_subpart_iterator_default_type(self):
2606n/a eq = self.assertEqual
2607n/a msg = self._msgobj('msg_03.txt')
2608n/a it = iterators.typed_subpart_iterator(msg, 'text', 'plain')
2609n/a lines = []
2610n/a subparts = 0
2611n/a for subpart in it:
2612n/a subparts += 1
2613n/a lines.append(subpart.get_payload())
2614n/a eq(subparts, 1)
2615n/a eq(EMPTYSTRING.join(lines), """\
2616n/a
2617n/aHi,
2618n/a
2619n/aDo you like this message?
2620n/a
2621n/a-Me
2622n/a""")
2623n/a
2624n/a def test_pushCR_LF(self):
2625n/a '''FeedParser BufferedSubFile.push() assumed it received complete
2626n/a line endings. A CR ending one push() followed by a LF starting
2627n/a the next push() added an empty line.
2628n/a '''
2629n/a imt = [
2630n/a ("a\r \n", 2),
2631n/a ("b", 0),
2632n/a ("c\n", 1),
2633n/a ("", 0),
2634n/a ("d\r\n", 1),
2635n/a ("e\r", 0),
2636n/a ("\nf", 1),
2637n/a ("\r\n", 1),
2638n/a ]
2639n/a from email.feedparser import BufferedSubFile, NeedMoreData
2640n/a bsf = BufferedSubFile()
2641n/a om = []
2642n/a nt = 0
2643n/a for il, n in imt:
2644n/a bsf.push(il)
2645n/a nt += n
2646n/a n1 = 0
2647n/a while True:
2648n/a ol = bsf.readline()
2649n/a if ol == NeedMoreData:
2650n/a break
2651n/a om.append(ol)
2652n/a n1 += 1
2653n/a self.assertTrue(n == n1)
2654n/a self.assertTrue(len(om) == nt)
2655n/a self.assertTrue(''.join([il for il, n in imt]) == ''.join(om))
2656n/a
2657n/a
2658n/a
2659n/aclass TestParsers(TestEmailBase):
2660n/a def test_header_parser(self):
2661n/a eq = self.assertEqual
2662n/a # Parse only the headers of a complex multipart MIME document
2663n/a with openfile('msg_02.txt') as fp:
2664n/a msg = HeaderParser().parse(fp)
2665n/a eq(msg['from'], 'ppp-request@zzz.org')
2666n/a eq(msg['to'], 'ppp@zzz.org')
2667n/a eq(msg.get_content_type(), 'multipart/mixed')
2668n/a self.assertFalse(msg.is_multipart())
2669n/a self.assertTrue(isinstance(msg.get_payload(), str))
2670n/a
2671n/a def test_whitespace_continuation(self):
2672n/a eq = self.assertEqual
2673n/a # This message contains a line after the Subject: header that has only
2674n/a # whitespace, but it is not empty!
2675n/a msg = email.message_from_string("""\
2676n/aFrom: aperson@dom.ain
2677n/aTo: bperson@dom.ain
2678n/aSubject: the next line has a space on it
2679n/a\x20
2680n/aDate: Mon, 8 Apr 2002 15:09:19 -0400
2681n/aMessage-ID: spam
2682n/a
2683n/aHere's the message body
2684n/a""")
2685n/a eq(msg['subject'], 'the next line has a space on it\n ')
2686n/a eq(msg['message-id'], 'spam')
2687n/a eq(msg.get_payload(), "Here's the message body\n")
2688n/a
2689n/a def test_whitespace_continuation_last_header(self):
2690n/a eq = self.assertEqual
2691n/a # Like the previous test, but the subject line is the last
2692n/a # header.
2693n/a msg = email.message_from_string("""\
2694n/aFrom: aperson@dom.ain
2695n/aTo: bperson@dom.ain
2696n/aDate: Mon, 8 Apr 2002 15:09:19 -0400
2697n/aMessage-ID: spam
2698n/aSubject: the next line has a space on it
2699n/a\x20
2700n/a
2701n/aHere's the message body
2702n/a""")
2703n/a eq(msg['subject'], 'the next line has a space on it\n ')
2704n/a eq(msg['message-id'], 'spam')
2705n/a eq(msg.get_payload(), "Here's the message body\n")
2706n/a
2707n/a def test_crlf_separation(self):
2708n/a eq = self.assertEqual
2709n/a with openfile('msg_26.txt', newline='\n') as fp:
2710n/a msg = Parser().parse(fp)
2711n/a eq(len(msg.get_payload()), 2)
2712n/a part1 = msg.get_payload(0)
2713n/a eq(part1.get_content_type(), 'text/plain')
2714n/a eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
2715n/a part2 = msg.get_payload(1)
2716n/a eq(part2.get_content_type(), 'application/riscos')
2717n/a
2718n/a def test_crlf_flatten(self):
2719n/a # Using newline='\n' preserves the crlfs in this input file.
2720n/a with openfile('msg_26.txt', newline='\n') as fp:
2721n/a text = fp.read()
2722n/a msg = email.message_from_string(text)
2723n/a s = StringIO()
2724n/a g = Generator(s)
2725n/a g.flatten(msg, linesep='\r\n')
2726n/a self.assertEqual(s.getvalue(), text)
2727n/a
2728n/a maxDiff = None
2729n/a
2730n/a def test_multipart_digest_with_extra_mime_headers(self):
2731n/a eq = self.assertEqual
2732n/a neq = self.ndiffAssertEqual
2733n/a with openfile('msg_28.txt') as fp:
2734n/a msg = email.message_from_file(fp)
2735n/a # Structure is:
2736n/a # multipart/digest
2737n/a # message/rfc822
2738n/a # text/plain
2739n/a # message/rfc822
2740n/a # text/plain
2741n/a eq(msg.is_multipart(), 1)
2742n/a eq(len(msg.get_payload()), 2)
2743n/a part1 = msg.get_payload(0)
2744n/a eq(part1.get_content_type(), 'message/rfc822')
2745n/a eq(part1.is_multipart(), 1)
2746n/a eq(len(part1.get_payload()), 1)
2747n/a part1a = part1.get_payload(0)
2748n/a eq(part1a.is_multipart(), 0)
2749n/a eq(part1a.get_content_type(), 'text/plain')
2750n/a neq(part1a.get_payload(), 'message 1\n')
2751n/a # next message/rfc822
2752n/a part2 = msg.get_payload(1)
2753n/a eq(part2.get_content_type(), 'message/rfc822')
2754n/a eq(part2.is_multipart(), 1)
2755n/a eq(len(part2.get_payload()), 1)
2756n/a part2a = part2.get_payload(0)
2757n/a eq(part2a.is_multipart(), 0)
2758n/a eq(part2a.get_content_type(), 'text/plain')
2759n/a neq(part2a.get_payload(), 'message 2\n')
2760n/a
2761n/a def test_three_lines(self):
2762n/a # A bug report by Andrew McNamara
2763n/a lines = ['From: Andrew Person <aperson@dom.ain',
2764n/a 'Subject: Test',
2765n/a 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
2766n/a msg = email.message_from_string(NL.join(lines))
2767n/a self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
2768n/a
2769n/a def test_strip_line_feed_and_carriage_return_in_headers(self):
2770n/a eq = self.assertEqual
2771n/a # For [ 1002475 ] email message parser doesn't handle \r\n correctly
2772n/a value1 = 'text'
2773n/a value2 = 'more text'
2774n/a m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
2775n/a value1, value2)
2776n/a msg = email.message_from_string(m)
2777n/a eq(msg.get('Header'), value1)
2778n/a eq(msg.get('Next-Header'), value2)
2779n/a
2780n/a def test_rfc2822_header_syntax(self):
2781n/a eq = self.assertEqual
2782n/a m = '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2783n/a msg = email.message_from_string(m)
2784n/a eq(len(msg), 3)
2785n/a eq(sorted(field for field in msg), ['!"#QUX;~', '>From', 'From'])
2786n/a eq(msg.get_payload(), 'body')
2787n/a
2788n/a def test_rfc2822_space_not_allowed_in_header(self):
2789n/a eq = self.assertEqual
2790n/a m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
2791n/a msg = email.message_from_string(m)
2792n/a eq(len(msg.keys()), 0)
2793n/a
2794n/a def test_rfc2822_one_character_header(self):
2795n/a eq = self.assertEqual
2796n/a m = 'A: first header\nB: second header\nCC: third header\n\nbody'
2797n/a msg = email.message_from_string(m)
2798n/a headers = msg.keys()
2799n/a headers.sort()
2800n/a eq(headers, ['A', 'B', 'CC'])
2801n/a eq(msg.get_payload(), 'body')
2802n/a
2803n/a def test_CRLFLF_at_end_of_part(self):
2804n/a # issue 5610: feedparser should not eat two chars from body part ending
2805n/a # with "\r\n\n".
2806n/a m = (
2807n/a "From: foo@bar.com\n"
2808n/a "To: baz\n"
2809n/a "Mime-Version: 1.0\n"
2810n/a "Content-Type: multipart/mixed; boundary=BOUNDARY\n"
2811n/a "\n"
2812n/a "--BOUNDARY\n"
2813n/a "Content-Type: text/plain\n"
2814n/a "\n"
2815n/a "body ending with CRLF newline\r\n"
2816n/a "\n"
2817n/a "--BOUNDARY--\n"
2818n/a )
2819n/a msg = email.message_from_string(m)
2820n/a self.assertTrue(msg.get_payload(0).get_payload().endswith('\r\n'))
2821n/a
2822n/a
2823n/aclass Test8BitBytesHandling(unittest.TestCase):
2824n/a # In Python3 all input is string, but that doesn't work if the actual input
2825n/a # uses an 8bit transfer encoding. To hack around that, in email 5.1 we
2826n/a # decode byte streams using the surrogateescape error handler, and
2827n/a # reconvert to binary at appropriate places if we detect surrogates. This
2828n/a # doesn't allow us to transform headers with 8bit bytes (they get munged),
2829n/a # but it does allow us to parse and preserve them, and to decode body
2830n/a # parts that use an 8bit CTE.
2831n/a
2832n/a bodytest_msg = textwrap.dedent("""\
2833n/a From: foo@bar.com
2834n/a To: baz
2835n/a Mime-Version: 1.0
2836n/a Content-Type: text/plain; charset={charset}
2837n/a Content-Transfer-Encoding: {cte}
2838n/a
2839n/a {bodyline}
2840n/a """)
2841n/a
2842n/a def test_known_8bit_CTE(self):
2843n/a m = self.bodytest_msg.format(charset='utf-8',
2844n/a cte='8bit',
2845n/a bodyline='pöstal').encode('utf-8')
2846n/a msg = email.message_from_bytes(m)
2847n/a self.assertEqual(msg.get_payload(), "pöstal\n")
2848n/a self.assertEqual(msg.get_payload(decode=True),
2849n/a "pöstal\n".encode('utf-8'))
2850n/a
2851n/a def test_unknown_8bit_CTE(self):
2852n/a m = self.bodytest_msg.format(charset='notavalidcharset',
2853n/a cte='8bit',
2854n/a bodyline='pöstal').encode('utf-8')
2855n/a msg = email.message_from_bytes(m)
2856n/a self.assertEqual(msg.get_payload(), "p\uFFFD\uFFFDstal\n")
2857n/a self.assertEqual(msg.get_payload(decode=True),
2858n/a "pöstal\n".encode('utf-8'))
2859n/a
2860n/a def test_8bit_in_quopri_body(self):
2861n/a # This is non-RFC compliant data...without 'decode' the library code
2862n/a # decodes the body using the charset from the headers, and because the
2863n/a # source byte really is utf-8 this works. This is likely to fail
2864n/a # against real dirty data (ie: produce mojibake), but the data is
2865n/a # invalid anyway so it is as good a guess as any. But this means that
2866n/a # this test just confirms the current behavior; that behavior is not
2867n/a # necessarily the best possible behavior. With 'decode' it is
2868n/a # returning the raw bytes, so that test should be of correct behavior,
2869n/a # or at least produce the same result that email4 did.
2870n/a m = self.bodytest_msg.format(charset='utf-8',
2871n/a cte='quoted-printable',
2872n/a bodyline='p=C3=B6stál').encode('utf-8')
2873n/a msg = email.message_from_bytes(m)
2874n/a self.assertEqual(msg.get_payload(), 'p=C3=B6stál\n')
2875n/a self.assertEqual(msg.get_payload(decode=True),
2876n/a 'pöstál\n'.encode('utf-8'))
2877n/a
2878n/a def test_invalid_8bit_in_non_8bit_cte_uses_replace(self):
2879n/a # This is similar to the previous test, but proves that if the 8bit
2880n/a # byte is undecodeable in the specified charset, it gets replaced
2881n/a # by the unicode 'unknown' character. Again, this may or may not
2882n/a # be the ideal behavior. Note that if decode=False none of the
2883n/a # decoders will get involved, so this is the only test we need
2884n/a # for this behavior.
2885n/a m = self.bodytest_msg.format(charset='ascii',
2886n/a cte='quoted-printable',
2887n/a bodyline='p=C3=B6stál').encode('utf-8')
2888n/a msg = email.message_from_bytes(m)
2889n/a self.assertEqual(msg.get_payload(), 'p=C3=B6st\uFFFD\uFFFDl\n')
2890n/a self.assertEqual(msg.get_payload(decode=True),
2891n/a 'pöstál\n'.encode('utf-8'))
2892n/a
2893n/a def test_8bit_in_base64_body(self):
2894n/a # Sticking an 8bit byte in a base64 block makes it undecodable by
2895n/a # normal means, so the block is returned undecoded, but as bytes.
2896n/a m = self.bodytest_msg.format(charset='utf-8',
2897n/a cte='base64',
2898n/a bodyline='cMO2c3RhbAá=').encode('utf-8')
2899n/a msg = email.message_from_bytes(m)
2900n/a self.assertEqual(msg.get_payload(decode=True),
2901n/a 'cMO2c3RhbAá=\n'.encode('utf-8'))
2902n/a
2903n/a def test_8bit_in_uuencode_body(self):
2904n/a # Sticking an 8bit byte in a uuencode block makes it undecodable by
2905n/a # normal means, so the block is returned undecoded, but as bytes.
2906n/a m = self.bodytest_msg.format(charset='utf-8',
2907n/a cte='uuencode',
2908n/a bodyline='<,.V<W1A; á ').encode('utf-8')
2909n/a msg = email.message_from_bytes(m)
2910n/a self.assertEqual(msg.get_payload(decode=True),
2911n/a '<,.V<W1A; á \n'.encode('utf-8'))
2912n/a
2913n/a
2914n/a headertest_headers = (
2915n/a ('From: foo@bar.com', ('From', 'foo@bar.com')),
2916n/a ('To: báz', ('To', '=?unknown-8bit?q?b=C3=A1z?=')),
2917n/a ('Subject: Maintenant je vous présente mon collègue, le pouf célèbre\n'
2918n/a '\tJean de Baddie',
2919n/a ('Subject', '=?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
2920n/a 'coll=C3=A8gue=2C_le_pouf_c=C3=A9l=C3=A8bre?=\n'
2921n/a ' =?unknown-8bit?q?_Jean_de_Baddie?=')),
2922n/a ('From: göst', ('From', '=?unknown-8bit?b?Z8O2c3Q=?=')),
2923n/a )
2924n/a headertest_msg = ('\n'.join([src for (src, _) in headertest_headers]) +
2925n/a '\nYes, they are flying.\n').encode('utf-8')
2926n/a
2927n/a def test_get_8bit_header(self):
2928n/a msg = email.message_from_bytes(self.headertest_msg)
2929n/a self.assertEqual(str(msg.get('to')), 'b\uFFFD\uFFFDz')
2930n/a self.assertEqual(str(msg['to']), 'b\uFFFD\uFFFDz')
2931n/a
2932n/a def test_print_8bit_headers(self):
2933n/a msg = email.message_from_bytes(self.headertest_msg)
2934n/a self.assertEqual(str(msg),
2935n/a textwrap.dedent("""\
2936n/a From: {}
2937n/a To: {}
2938n/a Subject: {}
2939n/a From: {}
2940n/a
2941n/a Yes, they are flying.
2942n/a """).format(*[expected[1] for (_, expected) in
2943n/a self.headertest_headers]))
2944n/a
2945n/a def test_values_with_8bit_headers(self):
2946n/a msg = email.message_from_bytes(self.headertest_msg)
2947n/a self.assertListEqual([str(x) for x in msg.values()],
2948n/a ['foo@bar.com',
2949n/a 'b\uFFFD\uFFFDz',
2950n/a 'Maintenant je vous pr\uFFFD\uFFFDsente mon '
2951n/a 'coll\uFFFD\uFFFDgue, le pouf '
2952n/a 'c\uFFFD\uFFFDl\uFFFD\uFFFDbre\n'
2953n/a '\tJean de Baddie',
2954n/a "g\uFFFD\uFFFDst"])
2955n/a
2956n/a def test_items_with_8bit_headers(self):
2957n/a msg = email.message_from_bytes(self.headertest_msg)
2958n/a self.assertListEqual([(str(x), str(y)) for (x, y) in msg.items()],
2959n/a [('From', 'foo@bar.com'),
2960n/a ('To', 'b\uFFFD\uFFFDz'),
2961n/a ('Subject', 'Maintenant je vous '
2962n/a 'pr\uFFFD\uFFFDsente '
2963n/a 'mon coll\uFFFD\uFFFDgue, le pouf '
2964n/a 'c\uFFFD\uFFFDl\uFFFD\uFFFDbre\n'
2965n/a '\tJean de Baddie'),
2966n/a ('From', 'g\uFFFD\uFFFDst')])
2967n/a
2968n/a def test_get_all_with_8bit_headers(self):
2969n/a msg = email.message_from_bytes(self.headertest_msg)
2970n/a self.assertListEqual([str(x) for x in msg.get_all('from')],
2971n/a ['foo@bar.com',
2972n/a 'g\uFFFD\uFFFDst'])
2973n/a
2974n/a non_latin_bin_msg = textwrap.dedent("""\
2975n/a From: foo@bar.com
2976n/a To: báz
2977n/a Subject: Maintenant je vous présente mon collègue, le pouf célèbre
2978n/a \tJean de Baddie
2979n/a Mime-Version: 1.0
2980n/a Content-Type: text/plain; charset="utf-8"
2981n/a Content-Transfer-Encoding: 8bit
2982n/a
2983n/a Да, они летят.
2984n/a """).encode('utf-8')
2985n/a
2986n/a def test_bytes_generator(self):
2987n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
2988n/a out = BytesIO()
2989n/a email.generator.BytesGenerator(out).flatten(msg)
2990n/a self.assertEqual(out.getvalue(), self.non_latin_bin_msg)
2991n/a
2992n/a def test_bytes_generator_handles_None_body(self):
2993n/a #Issue 11019
2994n/a msg = email.message.Message()
2995n/a out = BytesIO()
2996n/a email.generator.BytesGenerator(out).flatten(msg)
2997n/a self.assertEqual(out.getvalue(), b"\n")
2998n/a
2999n/a non_latin_bin_msg_as7bit_wrapped = textwrap.dedent("""\
3000n/a From: foo@bar.com
3001n/a To: =?unknown-8bit?q?b=C3=A1z?=
3002n/a Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_coll=C3=A8gue?=
3003n/a =?unknown-8bit?q?=2C_le_pouf_c=C3=A9l=C3=A8bre?=
3004n/a =?unknown-8bit?q?_Jean_de_Baddie?=
3005n/a Mime-Version: 1.0
3006n/a Content-Type: text/plain; charset="utf-8"
3007n/a Content-Transfer-Encoding: base64
3008n/a
3009n/a 0JTQsCwg0L7QvdC4INC70LXRgtGP0YIuCg==
3010n/a """)
3011n/a
3012n/a def test_generator_handles_8bit(self):
3013n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
3014n/a out = StringIO()
3015n/a email.generator.Generator(out).flatten(msg)
3016n/a self.assertEqual(out.getvalue(), self.non_latin_bin_msg_as7bit_wrapped)
3017n/a
3018n/a def test_bytes_generator_with_unix_from(self):
3019n/a # The unixfrom contains a current date, so we can't check it
3020n/a # literally. Just make sure the first word is 'From' and the
3021n/a # rest of the message matches the input.
3022n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
3023n/a out = BytesIO()
3024n/a email.generator.BytesGenerator(out).flatten(msg, unixfrom=True)
3025n/a lines = out.getvalue().split(b'\n')
3026n/a self.assertEqual(lines[0].split()[0], b'From')
3027n/a self.assertEqual(b'\n'.join(lines[1:]), self.non_latin_bin_msg)
3028n/a
3029n/a non_latin_bin_msg_as7bit = non_latin_bin_msg_as7bit_wrapped.split('\n')
3030n/a non_latin_bin_msg_as7bit[2:4] = [
3031n/a 'Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
3032n/a 'coll=C3=A8gue=2C_le_pouf_c=C3=A9l=C3=A8bre?=']
3033n/a non_latin_bin_msg_as7bit = '\n'.join(non_latin_bin_msg_as7bit)
3034n/a
3035n/a def test_message_from_binary_file(self):
3036n/a fn = 'test.msg'
3037n/a self.addCleanup(unlink, fn)
3038n/a with open(fn, 'wb') as testfile:
3039n/a testfile.write(self.non_latin_bin_msg)
3040n/a with open(fn, 'rb') as testfile:
3041n/a m = email.parser.BytesParser().parse(testfile)
3042n/a self.assertEqual(str(m), self.non_latin_bin_msg_as7bit)
3043n/a
3044n/a latin_bin_msg = textwrap.dedent("""\
3045n/a From: foo@bar.com
3046n/a To: Dinsdale
3047n/a Subject: Nudge nudge, wink, wink
3048n/a Mime-Version: 1.0
3049n/a Content-Type: text/plain; charset="latin-1"
3050n/a Content-Transfer-Encoding: 8bit
3051n/a
3052n/a oh là là, know what I mean, know what I mean?
3053n/a """).encode('latin-1')
3054n/a
3055n/a latin_bin_msg_as7bit = textwrap.dedent("""\
3056n/a From: foo@bar.com
3057n/a To: Dinsdale
3058n/a Subject: Nudge nudge, wink, wink
3059n/a Mime-Version: 1.0
3060n/a Content-Type: text/plain; charset="iso-8859-1"
3061n/a Content-Transfer-Encoding: quoted-printable
3062n/a
3063n/a oh l=E0 l=E0, know what I mean, know what I mean?
3064n/a """)
3065n/a
3066n/a def test_string_generator_reencodes_to_quopri_when_appropriate(self):
3067n/a m = email.message_from_bytes(self.latin_bin_msg)
3068n/a self.assertEqual(str(m), self.latin_bin_msg_as7bit)
3069n/a
3070n/a def test_decoded_generator_emits_unicode_body(self):
3071n/a m = email.message_from_bytes(self.latin_bin_msg)
3072n/a out = StringIO()
3073n/a email.generator.DecodedGenerator(out).flatten(m)
3074n/a #DecodedHeader output contains an extra blank line compared
3075n/a #to the input message. RDM: not sure if this is a bug or not,
3076n/a #but it is not specific to the 8bit->7bit conversion.
3077n/a self.assertEqual(out.getvalue(),
3078n/a self.latin_bin_msg.decode('latin-1')+'\n')
3079n/a
3080n/a def test_bytes_feedparser(self):
3081n/a bfp = email.feedparser.BytesFeedParser()
3082n/a for i in range(0, len(self.latin_bin_msg), 10):
3083n/a bfp.feed(self.latin_bin_msg[i:i+10])
3084n/a m = bfp.close()
3085n/a self.assertEqual(str(m), self.latin_bin_msg_as7bit)
3086n/a
3087n/a def test_crlf_flatten(self):
3088n/a with openfile('msg_26.txt', 'rb') as fp:
3089n/a text = fp.read()
3090n/a msg = email.message_from_bytes(text)
3091n/a s = BytesIO()
3092n/a g = email.generator.BytesGenerator(s)
3093n/a g.flatten(msg, linesep='\r\n')
3094n/a self.assertEqual(s.getvalue(), text)
3095n/a maxDiff = None
3096n/a
3097n/a
3098n/aclass BaseTestBytesGeneratorIdempotent:
3099n/a
3100n/a maxDiff = None
3101n/a
3102n/a def _msgobj(self, filename):
3103n/a with openfile(filename, 'rb') as fp:
3104n/a data = fp.read()
3105n/a data = self.normalize_linesep_regex.sub(self.blinesep, data)
3106n/a msg = email.message_from_bytes(data)
3107n/a return msg, data
3108n/a
3109n/a def _idempotent(self, msg, data, unixfrom=False):
3110n/a b = BytesIO()
3111n/a g = email.generator.BytesGenerator(b, maxheaderlen=0)
3112n/a g.flatten(msg, unixfrom=unixfrom, linesep=self.linesep)
3113n/a self.assertByteStringsEqual(data, b.getvalue())
3114n/a
3115n/a def assertByteStringsEqual(self, str1, str2):
3116n/a # Not using self.blinesep here is intentional. This way the output
3117n/a # is more useful when the failure results in mixed line endings.
3118n/a self.assertListEqual(str1.split(b'\n'), str2.split(b'\n'))
3119n/a
3120n/a
3121n/aclass TestBytesGeneratorIdempotentNL(BaseTestBytesGeneratorIdempotent,
3122n/a TestIdempotent):
3123n/a linesep = '\n'
3124n/a blinesep = b'\n'
3125n/a normalize_linesep_regex = re.compile(br'\r\n')
3126n/a
3127n/a
3128n/aclass TestBytesGeneratorIdempotentCRLF(BaseTestBytesGeneratorIdempotent,
3129n/a TestIdempotent):
3130n/a linesep = '\r\n'
3131n/a blinesep = b'\r\n'
3132n/a normalize_linesep_regex = re.compile(br'(?<!\r)\n')
3133n/a
3134n/a
3135n/aclass TestBase64(unittest.TestCase):
3136n/a def test_len(self):
3137n/a eq = self.assertEqual
3138n/a eq(base64mime.header_length('hello'),
3139n/a len(base64mime.body_encode(b'hello', eol='')))
3140n/a for size in range(15):
3141n/a if size == 0 : bsize = 0
3142n/a elif size <= 3 : bsize = 4
3143n/a elif size <= 6 : bsize = 8
3144n/a elif size <= 9 : bsize = 12
3145n/a elif size <= 12: bsize = 16
3146n/a else : bsize = 20
3147n/a eq(base64mime.header_length('x' * size), bsize)
3148n/a
3149n/a def test_decode(self):
3150n/a eq = self.assertEqual
3151n/a eq(base64mime.decode(''), b'')
3152n/a eq(base64mime.decode('aGVsbG8='), b'hello')
3153n/a
3154n/a def test_encode(self):
3155n/a eq = self.assertEqual
3156n/a eq(base64mime.body_encode(b''), b'')
3157n/a eq(base64mime.body_encode(b'hello'), 'aGVsbG8=\n')
3158n/a # Test the binary flag
3159n/a eq(base64mime.body_encode(b'hello\n'), 'aGVsbG8K\n')
3160n/a # Test the maxlinelen arg
3161n/a eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40), """\
3162n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
3163n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
3164n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
3165n/aeHh4eCB4eHh4IA==
3166n/a""")
3167n/a # Test the eol argument
3168n/a eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40, eol='\r\n'),
3169n/a """\
3170n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
3171n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
3172n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
3173n/aeHh4eCB4eHh4IA==\r
3174n/a""")
3175n/a
3176n/a def test_header_encode(self):
3177n/a eq = self.assertEqual
3178n/a he = base64mime.header_encode
3179n/a eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
3180n/a eq(he('hello\r\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
3181n/a eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
3182n/a # Test the charset option
3183n/a eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
3184n/a eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
3185n/a
3186n/a
3187n/a
3188n/aclass TestQuopri(unittest.TestCase):
3189n/a def setUp(self):
3190n/a # Set of characters (as byte integers) that don't need to be encoded
3191n/a # in headers.
3192n/a self.hlit = list(chain(
3193n/a range(ord('a'), ord('z') + 1),
3194n/a range(ord('A'), ord('Z') + 1),
3195n/a range(ord('0'), ord('9') + 1),
3196n/a (c for c in b'!*+-/')))
3197n/a # Set of characters (as byte integers) that do need to be encoded in
3198n/a # headers.
3199n/a self.hnon = [c for c in range(256) if c not in self.hlit]
3200n/a assert len(self.hlit) + len(self.hnon) == 256
3201n/a # Set of characters (as byte integers) that don't need to be encoded
3202n/a # in bodies.
3203n/a self.blit = list(range(ord(' '), ord('~') + 1))
3204n/a self.blit.append(ord('\t'))
3205n/a self.blit.remove(ord('='))
3206n/a # Set of characters (as byte integers) that do need to be encoded in
3207n/a # bodies.
3208n/a self.bnon = [c for c in range(256) if c not in self.blit]
3209n/a assert len(self.blit) + len(self.bnon) == 256
3210n/a
3211n/a def test_quopri_header_check(self):
3212n/a for c in self.hlit:
3213n/a self.assertFalse(quoprimime.header_check(c),
3214n/a 'Should not be header quopri encoded: %s' % chr(c))
3215n/a for c in self.hnon:
3216n/a self.assertTrue(quoprimime.header_check(c),
3217n/a 'Should be header quopri encoded: %s' % chr(c))
3218n/a
3219n/a def test_quopri_body_check(self):
3220n/a for c in self.blit:
3221n/a self.assertFalse(quoprimime.body_check(c),
3222n/a 'Should not be body quopri encoded: %s' % chr(c))
3223n/a for c in self.bnon:
3224n/a self.assertTrue(quoprimime.body_check(c),
3225n/a 'Should be body quopri encoded: %s' % chr(c))
3226n/a
3227n/a def test_header_quopri_len(self):
3228n/a eq = self.assertEqual
3229n/a eq(quoprimime.header_length(b'hello'), 5)
3230n/a # RFC 2047 chrome is not included in header_length().
3231n/a eq(len(quoprimime.header_encode(b'hello', charset='xxx')),
3232n/a quoprimime.header_length(b'hello') +
3233n/a # =?xxx?q?...?= means 10 extra characters
3234n/a 10)
3235n/a eq(quoprimime.header_length(b'h@e@l@l@o@'), 20)
3236n/a # RFC 2047 chrome is not included in header_length().
3237n/a eq(len(quoprimime.header_encode(b'h@e@l@l@o@', charset='xxx')),
3238n/a quoprimime.header_length(b'h@e@l@l@o@') +
3239n/a # =?xxx?q?...?= means 10 extra characters
3240n/a 10)
3241n/a for c in self.hlit:
3242n/a eq(quoprimime.header_length(bytes([c])), 1,
3243n/a 'expected length 1 for %r' % chr(c))
3244n/a for c in self.hnon:
3245n/a # Space is special; it's encoded to _
3246n/a if c == ord(' '):
3247n/a continue
3248n/a eq(quoprimime.header_length(bytes([c])), 3,
3249n/a 'expected length 3 for %r' % chr(c))
3250n/a eq(quoprimime.header_length(b' '), 1)
3251n/a
3252n/a def test_body_quopri_len(self):
3253n/a eq = self.assertEqual
3254n/a for c in self.blit:
3255n/a eq(quoprimime.body_length(bytes([c])), 1)
3256n/a for c in self.bnon:
3257n/a eq(quoprimime.body_length(bytes([c])), 3)
3258n/a
3259n/a def test_quote_unquote_idempotent(self):
3260n/a for x in range(256):
3261n/a c = chr(x)
3262n/a self.assertEqual(quoprimime.unquote(quoprimime.quote(c)), c)
3263n/a
3264n/a def test_header_encode(self):
3265n/a eq = self.assertEqual
3266n/a he = quoprimime.header_encode
3267n/a eq(he(b'hello'), '=?iso-8859-1?q?hello?=')
3268n/a eq(he(b'hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
3269n/a eq(he(b'hello\nworld'), '=?iso-8859-1?q?hello=0Aworld?=')
3270n/a # Test a non-ASCII character
3271n/a eq(he(b'hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
3272n/a
3273n/a def test_decode(self):
3274n/a eq = self.assertEqual
3275n/a eq(quoprimime.decode(''), '')
3276n/a eq(quoprimime.decode('hello'), 'hello')
3277n/a eq(quoprimime.decode('hello', 'X'), 'hello')
3278n/a eq(quoprimime.decode('hello\nworld', 'X'), 'helloXworld')
3279n/a
3280n/a def test_encode(self):
3281n/a eq = self.assertEqual
3282n/a eq(quoprimime.body_encode(''), '')
3283n/a eq(quoprimime.body_encode('hello'), 'hello')
3284n/a # Test the binary flag
3285n/a eq(quoprimime.body_encode('hello\r\nworld'), 'hello\nworld')
3286n/a # Test the maxlinelen arg
3287n/a eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40), """\
3288n/axxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
3289n/a xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
3290n/ax xxxx xxxx xxxx xxxx=20""")
3291n/a # Test the eol argument
3292n/a eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'),
3293n/a """\
3294n/axxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
3295n/a xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
3296n/ax xxxx xxxx xxxx xxxx=20""")
3297n/a eq(quoprimime.body_encode("""\
3298n/aone line
3299n/a
3300n/atwo line"""), """\
3301n/aone line
3302n/a
3303n/atwo line""")
3304n/a
3305n/a
3306n/a
3307n/a# Test the Charset class
3308n/aclass TestCharset(unittest.TestCase):
3309n/a def tearDown(self):
3310n/a from email import charset as CharsetModule
3311n/a try:
3312n/a del CharsetModule.CHARSETS['fake']
3313n/a except KeyError:
3314n/a pass
3315n/a
3316n/a def test_codec_encodeable(self):
3317n/a eq = self.assertEqual
3318n/a # Make sure us-ascii = no Unicode conversion
3319n/a c = Charset('us-ascii')
3320n/a eq(c.header_encode('Hello World!'), 'Hello World!')
3321n/a # Test 8-bit idempotency with us-ascii
3322n/a s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
3323n/a self.assertRaises(UnicodeError, c.header_encode, s)
3324n/a c = Charset('utf-8')
3325n/a eq(c.header_encode(s), '=?utf-8?b?wqTCosKkwqTCpMKmwqTCqMKkwqo=?=')
3326n/a
3327n/a def test_body_encode(self):
3328n/a eq = self.assertEqual
3329n/a # Try a charset with QP body encoding
3330n/a c = Charset('iso-8859-1')
3331n/a eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
3332n/a # Try a charset with Base64 body encoding
3333n/a c = Charset('utf-8')
3334n/a eq('aGVsbG8gd29ybGQ=\n', c.body_encode(b'hello world'))
3335n/a # Try a charset with None body encoding
3336n/a c = Charset('us-ascii')
3337n/a eq('hello world', c.body_encode('hello world'))
3338n/a # Try the convert argument, where input codec != output codec
3339n/a c = Charset('euc-jp')
3340n/a # With apologies to Tokio Kikuchi ;)
3341n/a # XXX FIXME
3342n/a## try:
3343n/a## eq('\x1b$B5FCO;~IW\x1b(B',
3344n/a## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
3345n/a## eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
3346n/a## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
3347n/a## except LookupError:
3348n/a## # We probably don't have the Japanese codecs installed
3349n/a## pass
3350n/a # Testing SF bug #625509, which we have to fake, since there are no
3351n/a # built-in encodings where the header encoding is QP but the body
3352n/a # encoding is not.
3353n/a from email import charset as CharsetModule
3354n/a CharsetModule.add_charset('fake', CharsetModule.QP, None)
3355n/a c = Charset('fake')
3356n/a eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
3357n/a
3358n/a def test_unicode_charset_name(self):
3359n/a charset = Charset('us-ascii')
3360n/a self.assertEqual(str(charset), 'us-ascii')
3361n/a self.assertRaises(errors.CharsetError, Charset, 'asc\xffii')
3362n/a
3363n/a
3364n/a
3365n/a# Test multilingual MIME headers.
3366n/aclass TestHeader(TestEmailBase):
3367n/a def test_simple(self):
3368n/a eq = self.ndiffAssertEqual
3369n/a h = Header('Hello World!')
3370n/a eq(h.encode(), 'Hello World!')
3371n/a h.append(' Goodbye World!')
3372n/a eq(h.encode(), 'Hello World! Goodbye World!')
3373n/a
3374n/a def test_simple_surprise(self):
3375n/a eq = self.ndiffAssertEqual
3376n/a h = Header('Hello World!')
3377n/a eq(h.encode(), 'Hello World!')
3378n/a h.append('Goodbye World!')
3379n/a eq(h.encode(), 'Hello World! Goodbye World!')
3380n/a
3381n/a def test_header_needs_no_decoding(self):
3382n/a h = 'no decoding needed'
3383n/a self.assertEqual(decode_header(h), [(h, None)])
3384n/a
3385n/a def test_long(self):
3386n/a h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
3387n/a maxlinelen=76)
3388n/a for l in h.encode(splitchars=' ').split('\n '):
3389n/a self.assertTrue(len(l) <= 76)
3390n/a
3391n/a def test_multilingual(self):
3392n/a eq = self.ndiffAssertEqual
3393n/a g = Charset("iso-8859-1")
3394n/a cz = Charset("iso-8859-2")
3395n/a utf8 = Charset("utf-8")
3396n/a g_head = (b'Die Mieter treten hier ein werden mit einem '
3397n/a b'Foerderband komfortabel den Korridor entlang, '
3398n/a b'an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, '
3399n/a b'gegen die rotierenden Klingen bef\xf6rdert. ')
3400n/a cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
3401n/a b'd\xf9vtipu.. ')
3402n/a utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
3403n/a '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
3404n/a '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
3405n/a '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
3406n/a '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
3407n/a 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
3408n/a 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
3409n/a '\u3044\u307e\u3059\u3002')
3410n/a h = Header(g_head, g)
3411n/a h.append(cz_head, cz)
3412n/a h.append(utf8_head, utf8)
3413n/a enc = h.encode(maxlinelen=76)
3414n/a eq(enc, """\
3415n/a=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_kom?=
3416n/a =?iso-8859-1?q?fortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wand?=
3417n/a =?iso-8859-1?q?gem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6r?=
3418n/a =?iso-8859-1?q?dert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
3419n/a =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
3420n/a =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
3421n/a =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
3422n/a =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
3423n/a =?utf-8?b?IE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5k?=
3424n/a =?utf-8?b?IGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIA=?=
3425n/a =?utf-8?b?44Gj44Gm44GE44G+44GZ44CC?=""")
3426n/a decoded = decode_header(enc)
3427n/a eq(len(decoded), 3)
3428n/a eq(decoded[0], (g_head, 'iso-8859-1'))
3429n/a eq(decoded[1], (cz_head, 'iso-8859-2'))
3430n/a eq(decoded[2], (utf8_head.encode('utf-8'), 'utf-8'))
3431n/a ustr = str(h)
3432n/a eq(ustr,
3433n/a (b'Die Mieter treten hier ein werden mit einem Foerderband '
3434n/a b'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
3435n/a b'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
3436n/a b'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
3437n/a b'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
3438n/a b'\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
3439n/a b'\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
3440n/a b'\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
3441n/a b'\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
3442n/a b'\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
3443n/a b'\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
3444n/a b'\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
3445n/a b'\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
3446n/a b'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
3447n/a b'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
3448n/a b'\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'
3449n/a ).decode('utf-8'))
3450n/a # Test make_header()
3451n/a newh = make_header(decode_header(enc))
3452n/a eq(newh, h)
3453n/a
3454n/a def test_empty_header_encode(self):
3455n/a h = Header()
3456n/a self.assertEqual(h.encode(), '')
3457n/a
3458n/a def test_header_ctor_default_args(self):
3459n/a eq = self.ndiffAssertEqual
3460n/a h = Header()
3461n/a eq(h, '')
3462n/a h.append('foo', Charset('iso-8859-1'))
3463n/a eq(h, 'foo')
3464n/a
3465n/a def test_explicit_maxlinelen(self):
3466n/a eq = self.ndiffAssertEqual
3467n/a hstr = ('A very long line that must get split to something other '
3468n/a 'than at the 76th character boundary to test the non-default '
3469n/a 'behavior')
3470n/a h = Header(hstr)
3471n/a eq(h.encode(), '''\
3472n/aA very long line that must get split to something other than at the 76th
3473n/a character boundary to test the non-default behavior''')
3474n/a eq(str(h), hstr)
3475n/a h = Header(hstr, header_name='Subject')
3476n/a eq(h.encode(), '''\
3477n/aA very long line that must get split to something other than at the
3478n/a 76th character boundary to test the non-default behavior''')
3479n/a eq(str(h), hstr)
3480n/a h = Header(hstr, maxlinelen=1024, header_name='Subject')
3481n/a eq(h.encode(), hstr)
3482n/a eq(str(h), hstr)
3483n/a
3484n/a def test_quopri_splittable(self):
3485n/a eq = self.ndiffAssertEqual
3486n/a h = Header(charset='iso-8859-1', maxlinelen=20)
3487n/a x = 'xxxx ' * 20
3488n/a h.append(x)
3489n/a s = h.encode()
3490n/a eq(s, """\
3491n/a=?iso-8859-1?q?xxx?=
3492n/a =?iso-8859-1?q?x_?=
3493n/a =?iso-8859-1?q?xx?=
3494n/a =?iso-8859-1?q?xx?=
3495n/a =?iso-8859-1?q?_x?=
3496n/a =?iso-8859-1?q?xx?=
3497n/a =?iso-8859-1?q?x_?=
3498n/a =?iso-8859-1?q?xx?=
3499n/a =?iso-8859-1?q?xx?=
3500n/a =?iso-8859-1?q?_x?=
3501n/a =?iso-8859-1?q?xx?=
3502n/a =?iso-8859-1?q?x_?=
3503n/a =?iso-8859-1?q?xx?=
3504n/a =?iso-8859-1?q?xx?=
3505n/a =?iso-8859-1?q?_x?=
3506n/a =?iso-8859-1?q?xx?=
3507n/a =?iso-8859-1?q?x_?=
3508n/a =?iso-8859-1?q?xx?=
3509n/a =?iso-8859-1?q?xx?=
3510n/a =?iso-8859-1?q?_x?=
3511n/a =?iso-8859-1?q?xx?=
3512n/a =?iso-8859-1?q?x_?=
3513n/a =?iso-8859-1?q?xx?=
3514n/a =?iso-8859-1?q?xx?=
3515n/a =?iso-8859-1?q?_x?=
3516n/a =?iso-8859-1?q?xx?=
3517n/a =?iso-8859-1?q?x_?=
3518n/a =?iso-8859-1?q?xx?=
3519n/a =?iso-8859-1?q?xx?=
3520n/a =?iso-8859-1?q?_x?=
3521n/a =?iso-8859-1?q?xx?=
3522n/a =?iso-8859-1?q?x_?=
3523n/a =?iso-8859-1?q?xx?=
3524n/a =?iso-8859-1?q?xx?=
3525n/a =?iso-8859-1?q?_x?=
3526n/a =?iso-8859-1?q?xx?=
3527n/a =?iso-8859-1?q?x_?=
3528n/a =?iso-8859-1?q?xx?=
3529n/a =?iso-8859-1?q?xx?=
3530n/a =?iso-8859-1?q?_x?=
3531n/a =?iso-8859-1?q?xx?=
3532n/a =?iso-8859-1?q?x_?=
3533n/a =?iso-8859-1?q?xx?=
3534n/a =?iso-8859-1?q?xx?=
3535n/a =?iso-8859-1?q?_x?=
3536n/a =?iso-8859-1?q?xx?=
3537n/a =?iso-8859-1?q?x_?=
3538n/a =?iso-8859-1?q?xx?=
3539n/a =?iso-8859-1?q?xx?=
3540n/a =?iso-8859-1?q?_?=""")
3541n/a eq(x, str(make_header(decode_header(s))))
3542n/a h = Header(charset='iso-8859-1', maxlinelen=40)
3543n/a h.append('xxxx ' * 20)
3544n/a s = h.encode()
3545n/a eq(s, """\
3546n/a=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xxx?=
3547n/a =?iso-8859-1?q?x_xxxx_xxxx_xxxx_xxxx_?=
3548n/a =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
3549n/a =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
3550n/a =?iso-8859-1?q?_xxxx_xxxx_?=""")
3551n/a eq(x, str(make_header(decode_header(s))))
3552n/a
3553n/a def test_base64_splittable(self):
3554n/a eq = self.ndiffAssertEqual
3555n/a h = Header(charset='koi8-r', maxlinelen=20)
3556n/a x = 'xxxx ' * 20
3557n/a h.append(x)
3558n/a s = h.encode()
3559n/a eq(s, """\
3560n/a=?koi8-r?b?eHh4?=
3561n/a =?koi8-r?b?eCB4?=
3562n/a =?koi8-r?b?eHh4?=
3563n/a =?koi8-r?b?IHh4?=
3564n/a =?koi8-r?b?eHgg?=
3565n/a =?koi8-r?b?eHh4?=
3566n/a =?koi8-r?b?eCB4?=
3567n/a =?koi8-r?b?eHh4?=
3568n/a =?koi8-r?b?IHh4?=
3569n/a =?koi8-r?b?eHgg?=
3570n/a =?koi8-r?b?eHh4?=
3571n/a =?koi8-r?b?eCB4?=
3572n/a =?koi8-r?b?eHh4?=
3573n/a =?koi8-r?b?IHh4?=
3574n/a =?koi8-r?b?eHgg?=
3575n/a =?koi8-r?b?eHh4?=
3576n/a =?koi8-r?b?eCB4?=
3577n/a =?koi8-r?b?eHh4?=
3578n/a =?koi8-r?b?IHh4?=
3579n/a =?koi8-r?b?eHgg?=
3580n/a =?koi8-r?b?eHh4?=
3581n/a =?koi8-r?b?eCB4?=
3582n/a =?koi8-r?b?eHh4?=
3583n/a =?koi8-r?b?IHh4?=
3584n/a =?koi8-r?b?eHgg?=
3585n/a =?koi8-r?b?eHh4?=
3586n/a =?koi8-r?b?eCB4?=
3587n/a =?koi8-r?b?eHh4?=
3588n/a =?koi8-r?b?IHh4?=
3589n/a =?koi8-r?b?eHgg?=
3590n/a =?koi8-r?b?eHh4?=
3591n/a =?koi8-r?b?eCB4?=
3592n/a =?koi8-r?b?eHh4?=
3593n/a =?koi8-r?b?IA==?=""")
3594n/a eq(x, str(make_header(decode_header(s))))
3595n/a h = Header(charset='koi8-r', maxlinelen=40)
3596n/a h.append(x)
3597n/a s = h.encode()
3598n/a eq(s, """\
3599n/a=?koi8-r?b?eHh4eCB4eHh4IHh4eHggeHh4?=
3600n/a =?koi8-r?b?eCB4eHh4IHh4eHggeHh4eCB4?=
3601n/a =?koi8-r?b?eHh4IHh4eHggeHh4eCB4eHh4?=
3602n/a =?koi8-r?b?IHh4eHggeHh4eCB4eHh4IHh4?=
3603n/a =?koi8-r?b?eHggeHh4eCB4eHh4IHh4eHgg?=
3604n/a =?koi8-r?b?eHh4eCB4eHh4IA==?=""")
3605n/a eq(x, str(make_header(decode_header(s))))
3606n/a
3607n/a def test_us_ascii_header(self):
3608n/a eq = self.assertEqual
3609n/a s = 'hello'
3610n/a x = decode_header(s)
3611n/a eq(x, [('hello', None)])
3612n/a h = make_header(x)
3613n/a eq(s, h.encode())
3614n/a
3615n/a def test_string_charset(self):
3616n/a eq = self.assertEqual
3617n/a h = Header()
3618n/a h.append('hello', 'iso-8859-1')
3619n/a eq(h, 'hello')
3620n/a
3621n/a## def test_unicode_error(self):
3622n/a## raises = self.assertRaises
3623n/a## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
3624n/a## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
3625n/a## h = Header()
3626n/a## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
3627n/a## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
3628n/a## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
3629n/a
3630n/a def test_utf8_shortest(self):
3631n/a eq = self.assertEqual
3632n/a h = Header('p\xf6stal', 'utf-8')
3633n/a eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
3634n/a h = Header('\u83ca\u5730\u6642\u592b', 'utf-8')
3635n/a eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
3636n/a
3637n/a def test_bad_8bit_header(self):
3638n/a raises = self.assertRaises
3639n/a eq = self.assertEqual
3640n/a x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
3641n/a raises(UnicodeError, Header, x)
3642n/a h = Header()
3643n/a raises(UnicodeError, h.append, x)
3644n/a e = x.decode('utf-8', 'replace')
3645n/a eq(str(Header(x, errors='replace')), e)
3646n/a h.append(x, errors='replace')
3647n/a eq(str(h), e)
3648n/a
3649n/a def test_encoded_adjacent_nonencoded(self):
3650n/a eq = self.assertEqual
3651n/a h = Header()
3652n/a h.append('hello', 'iso-8859-1')
3653n/a h.append('world')
3654n/a s = h.encode()
3655n/a eq(s, '=?iso-8859-1?q?hello?= world')
3656n/a h = make_header(decode_header(s))
3657n/a eq(h.encode(), s)
3658n/a
3659n/a def test_whitespace_eater(self):
3660n/a eq = self.assertEqual
3661n/a s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
3662n/a parts = decode_header(s)
3663n/a eq(parts, [(b'Subject:', None), (b'\xf0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 \xce\xc1 \xc6\xc9\xce\xc1\xcc\xd8\xce\xd9\xca', 'koi8-r'), (b'zz.', None)])
3664n/a hdr = make_header(parts)
3665n/a eq(hdr.encode(),
3666n/a 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
3667n/a
3668n/a def test_broken_base64_header(self):
3669n/a raises = self.assertRaises
3670n/a s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3I ?='
3671n/a raises(errors.HeaderParseError, decode_header, s)
3672n/a
3673n/a def test_shift_jis_charset(self):
3674n/a h = Header('æ–‡', charset='shift_jis')
3675n/a self.assertEqual(h.encode(), '=?iso-2022-jp?b?GyRCSjgbKEI=?=')
3676n/a
3677n/a
3678n/a
3679n/a# Test RFC 2231 header parameters (en/de)coding
3680n/aclass TestRFC2231(TestEmailBase):
3681n/a def test_get_param(self):
3682n/a eq = self.assertEqual
3683n/a msg = self._msgobj('msg_29.txt')
3684n/a eq(msg.get_param('title'),
3685n/a ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
3686n/a eq(msg.get_param('title', unquote=False),
3687n/a ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
3688n/a
3689n/a def test_set_param(self):
3690n/a eq = self.ndiffAssertEqual
3691n/a msg = Message()
3692n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3693n/a charset='us-ascii')
3694n/a eq(msg.get_param('title'),
3695n/a ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
3696n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3697n/a charset='us-ascii', language='en')
3698n/a eq(msg.get_param('title'),
3699n/a ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
3700n/a msg = self._msgobj('msg_01.txt')
3701n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3702n/a charset='us-ascii', language='en')
3703n/a eq(msg.as_string(maxheaderlen=78), """\
3704n/aReturn-Path: <bbb@zzz.org>
3705n/aDelivered-To: bbb@zzz.org
3706n/aReceived: by mail.zzz.org (Postfix, from userid 889)
3707n/a\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3708n/aMIME-Version: 1.0
3709n/aContent-Transfer-Encoding: 7bit
3710n/aMessage-ID: <15090.61304.110929.45684@aaa.zzz.org>
3711n/aFrom: bbb@ddd.com (John X. Doe)
3712n/aTo: bbb@zzz.org
3713n/aSubject: This is a test message
3714n/aDate: Fri, 4 May 2001 14:05:44 -0400
3715n/aContent-Type: text/plain; charset=us-ascii;
3716n/a title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21
3717n/a
3718n/a
3719n/aHi,
3720n/a
3721n/aDo you like this message?
3722n/a
3723n/a-Me
3724n/a""")
3725n/a
3726n/a def test_del_param(self):
3727n/a eq = self.ndiffAssertEqual
3728n/a msg = self._msgobj('msg_01.txt')
3729n/a msg.set_param('foo', 'bar', charset='us-ascii', language='en')
3730n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
3731n/a charset='us-ascii', language='en')
3732n/a msg.del_param('foo', header='Content-Type')
3733n/a eq(msg.as_string(maxheaderlen=78), """\
3734n/aReturn-Path: <bbb@zzz.org>
3735n/aDelivered-To: bbb@zzz.org
3736n/aReceived: by mail.zzz.org (Postfix, from userid 889)
3737n/a\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
3738n/aMIME-Version: 1.0
3739n/aContent-Transfer-Encoding: 7bit
3740n/aMessage-ID: <15090.61304.110929.45684@aaa.zzz.org>
3741n/aFrom: bbb@ddd.com (John X. Doe)
3742n/aTo: bbb@zzz.org
3743n/aSubject: This is a test message
3744n/aDate: Fri, 4 May 2001 14:05:44 -0400
3745n/aContent-Type: text/plain; charset="us-ascii";
3746n/a title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21
3747n/a
3748n/a
3749n/aHi,
3750n/a
3751n/aDo you like this message?
3752n/a
3753n/a-Me
3754n/a""")
3755n/a
3756n/a def test_rfc2231_get_content_charset(self):
3757n/a eq = self.assertEqual
3758n/a msg = self._msgobj('msg_32.txt')
3759n/a eq(msg.get_content_charset(), 'us-ascii')
3760n/a
3761n/a def test_rfc2231_parse_rfc_quoting(self):
3762n/a m = textwrap.dedent('''\
3763n/a Content-Disposition: inline;
3764n/a \tfilename*0*=''This%20is%20even%20more%20;
3765n/a \tfilename*1*=%2A%2A%2Afun%2A%2A%2A%20;
3766n/a \tfilename*2="is it not.pdf"
3767n/a
3768n/a ''')
3769n/a msg = email.message_from_string(m)
3770n/a self.assertEqual(msg.get_filename(),
3771n/a 'This is even more ***fun*** is it not.pdf')
3772n/a self.assertEqual(m, msg.as_string())
3773n/a
3774n/a def test_rfc2231_parse_extra_quoting(self):
3775n/a m = textwrap.dedent('''\
3776n/a Content-Disposition: inline;
3777n/a \tfilename*0*="''This%20is%20even%20more%20";
3778n/a \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3779n/a \tfilename*2="is it not.pdf"
3780n/a
3781n/a ''')
3782n/a msg = email.message_from_string(m)
3783n/a self.assertEqual(msg.get_filename(),
3784n/a 'This is even more ***fun*** is it not.pdf')
3785n/a self.assertEqual(m, msg.as_string())
3786n/a
3787n/a def test_rfc2231_no_language_or_charset(self):
3788n/a m = '''\
3789n/aContent-Transfer-Encoding: 8bit
3790n/aContent-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
3791n/aContent-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
3792n/a
3793n/a'''
3794n/a msg = email.message_from_string(m)
3795n/a param = msg.get_param('NAME')
3796n/a self.assertFalse(isinstance(param, tuple))
3797n/a self.assertEqual(
3798n/a param,
3799n/a 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
3800n/a
3801n/a def test_rfc2231_no_language_or_charset_in_filename(self):
3802n/a m = '''\
3803n/aContent-Disposition: inline;
3804n/a\tfilename*0*="''This%20is%20even%20more%20";
3805n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3806n/a\tfilename*2="is it not.pdf"
3807n/a
3808n/a'''
3809n/a msg = email.message_from_string(m)
3810n/a self.assertEqual(msg.get_filename(),
3811n/a 'This is even more ***fun*** is it not.pdf')
3812n/a
3813n/a def test_rfc2231_no_language_or_charset_in_filename_encoded(self):
3814n/a m = '''\
3815n/aContent-Disposition: inline;
3816n/a\tfilename*0*="''This%20is%20even%20more%20";
3817n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3818n/a\tfilename*2="is it not.pdf"
3819n/a
3820n/a'''
3821n/a msg = email.message_from_string(m)
3822n/a self.assertEqual(msg.get_filename(),
3823n/a 'This is even more ***fun*** is it not.pdf')
3824n/a
3825n/a def test_rfc2231_partly_encoded(self):
3826n/a m = '''\
3827n/aContent-Disposition: inline;
3828n/a\tfilename*0="''This%20is%20even%20more%20";
3829n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3830n/a\tfilename*2="is it not.pdf"
3831n/a
3832n/a'''
3833n/a msg = email.message_from_string(m)
3834n/a self.assertEqual(
3835n/a msg.get_filename(),
3836n/a 'This%20is%20even%20more%20***fun*** is it not.pdf')
3837n/a
3838n/a def test_rfc2231_partly_nonencoded(self):
3839n/a m = '''\
3840n/aContent-Disposition: inline;
3841n/a\tfilename*0="This%20is%20even%20more%20";
3842n/a\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
3843n/a\tfilename*2="is it not.pdf"
3844n/a
3845n/a'''
3846n/a msg = email.message_from_string(m)
3847n/a self.assertEqual(
3848n/a msg.get_filename(),
3849n/a 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
3850n/a
3851n/a def test_rfc2231_no_language_or_charset_in_boundary(self):
3852n/a m = '''\
3853n/aContent-Type: multipart/alternative;
3854n/a\tboundary*0*="''This%20is%20even%20more%20";
3855n/a\tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
3856n/a\tboundary*2="is it not.pdf"
3857n/a
3858n/a'''
3859n/a msg = email.message_from_string(m)
3860n/a self.assertEqual(msg.get_boundary(),
3861n/a 'This is even more ***fun*** is it not.pdf')
3862n/a
3863n/a def test_rfc2231_no_language_or_charset_in_charset(self):
3864n/a # This is a nonsensical charset value, but tests the code anyway
3865n/a m = '''\
3866n/aContent-Type: text/plain;
3867n/a\tcharset*0*="This%20is%20even%20more%20";
3868n/a\tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
3869n/a\tcharset*2="is it not.pdf"
3870n/a
3871n/a'''
3872n/a msg = email.message_from_string(m)
3873n/a self.assertEqual(msg.get_content_charset(),
3874n/a 'this is even more ***fun*** is it not.pdf')
3875n/a
3876n/a def test_rfc2231_bad_encoding_in_filename(self):
3877n/a m = '''\
3878n/aContent-Disposition: inline;
3879n/a\tfilename*0*="bogus'xx'This%20is%20even%20more%20";
3880n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3881n/a\tfilename*2="is it not.pdf"
3882n/a
3883n/a'''
3884n/a msg = email.message_from_string(m)
3885n/a self.assertEqual(msg.get_filename(),
3886n/a 'This is even more ***fun*** is it not.pdf')
3887n/a
3888n/a def test_rfc2231_bad_encoding_in_charset(self):
3889n/a m = """\
3890n/aContent-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
3891n/a
3892n/a"""
3893n/a msg = email.message_from_string(m)
3894n/a # This should return None because non-ascii characters in the charset
3895n/a # are not allowed.
3896n/a self.assertEqual(msg.get_content_charset(), None)
3897n/a
3898n/a def test_rfc2231_bad_character_in_charset(self):
3899n/a m = """\
3900n/aContent-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
3901n/a
3902n/a"""
3903n/a msg = email.message_from_string(m)
3904n/a # This should return None because non-ascii characters in the charset
3905n/a # are not allowed.
3906n/a self.assertEqual(msg.get_content_charset(), None)
3907n/a
3908n/a def test_rfc2231_bad_character_in_filename(self):
3909n/a m = '''\
3910n/aContent-Disposition: inline;
3911n/a\tfilename*0*="ascii'xx'This%20is%20even%20more%20";
3912n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
3913n/a\tfilename*2*="is it not.pdf%E2"
3914n/a
3915n/a'''
3916n/a msg = email.message_from_string(m)
3917n/a self.assertEqual(msg.get_filename(),
3918n/a 'This is even more ***fun*** is it not.pdf\ufffd')
3919n/a
3920n/a def test_rfc2231_unknown_encoding(self):
3921n/a m = """\
3922n/aContent-Transfer-Encoding: 8bit
3923n/aContent-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
3924n/a
3925n/a"""
3926n/a msg = email.message_from_string(m)
3927n/a self.assertEqual(msg.get_filename(), 'myfile.txt')
3928n/a
3929n/a def test_rfc2231_single_tick_in_filename_extended(self):
3930n/a eq = self.assertEqual
3931n/a m = """\
3932n/aContent-Type: application/x-foo;
3933n/a\tname*0*=\"Frank's\"; name*1*=\" Document\"
3934n/a
3935n/a"""
3936n/a msg = email.message_from_string(m)
3937n/a charset, language, s = msg.get_param('name')
3938n/a eq(charset, None)
3939n/a eq(language, None)
3940n/a eq(s, "Frank's Document")
3941n/a
3942n/a def test_rfc2231_single_tick_in_filename(self):
3943n/a m = """\
3944n/aContent-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
3945n/a
3946n/a"""
3947n/a msg = email.message_from_string(m)
3948n/a param = msg.get_param('name')
3949n/a self.assertFalse(isinstance(param, tuple))
3950n/a self.assertEqual(param, "Frank's Document")
3951n/a
3952n/a def test_rfc2231_tick_attack_extended(self):
3953n/a eq = self.assertEqual
3954n/a m = """\
3955n/aContent-Type: application/x-foo;
3956n/a\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
3957n/a
3958n/a"""
3959n/a msg = email.message_from_string(m)
3960n/a charset, language, s = msg.get_param('name')
3961n/a eq(charset, 'us-ascii')
3962n/a eq(language, 'en-us')
3963n/a eq(s, "Frank's Document")
3964n/a
3965n/a def test_rfc2231_tick_attack(self):
3966n/a m = """\
3967n/aContent-Type: application/x-foo;
3968n/a\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
3969n/a
3970n/a"""
3971n/a msg = email.message_from_string(m)
3972n/a param = msg.get_param('name')
3973n/a self.assertFalse(isinstance(param, tuple))
3974n/a self.assertEqual(param, "us-ascii'en-us'Frank's Document")
3975n/a
3976n/a def test_rfc2231_no_extended_values(self):
3977n/a eq = self.assertEqual
3978n/a m = """\
3979n/aContent-Type: application/x-foo; name=\"Frank's Document\"
3980n/a
3981n/a"""
3982n/a msg = email.message_from_string(m)
3983n/a eq(msg.get_param('name'), "Frank's Document")
3984n/a
3985n/a def test_rfc2231_encoded_then_unencoded_segments(self):
3986n/a eq = self.assertEqual
3987n/a m = """\
3988n/aContent-Type: application/x-foo;
3989n/a\tname*0*=\"us-ascii'en-us'My\";
3990n/a\tname*1=\" Document\";
3991n/a\tname*2*=\" For You\"
3992n/a
3993n/a"""
3994n/a msg = email.message_from_string(m)
3995n/a charset, language, s = msg.get_param('name')
3996n/a eq(charset, 'us-ascii')
3997n/a eq(language, 'en-us')
3998n/a eq(s, 'My Document For You')
3999n/a
4000n/a def test_rfc2231_unencoded_then_encoded_segments(self):
4001n/a eq = self.assertEqual
4002n/a m = """\
4003n/aContent-Type: application/x-foo;
4004n/a\tname*0=\"us-ascii'en-us'My\";
4005n/a\tname*1*=\" Document\";
4006n/a\tname*2*=\" For You\"
4007n/a
4008n/a"""
4009n/a msg = email.message_from_string(m)
4010n/a charset, language, s = msg.get_param('name')
4011n/a eq(charset, 'us-ascii')
4012n/a eq(language, 'en-us')
4013n/a eq(s, 'My Document For You')
4014n/a
4015n/a
4016n/a
4017n/a# Tests to ensure that signed parts of an email are completely preserved, as
4018n/a# required by RFC1847 section 2.1. Note that these are incomplete, because the
4019n/a# email package does not currently always preserve the body. See issue 1670765.
4020n/aclass TestSigned(TestEmailBase):
4021n/a
4022n/a def _msg_and_obj(self, filename):
4023n/a with openfile(findfile(filename)) as fp:
4024n/a original = fp.read()
4025n/a msg = email.message_from_string(original)
4026n/a return original, msg
4027n/a
4028n/a def _signed_parts_eq(self, original, result):
4029n/a # Extract the first mime part of each message
4030n/a import re
4031n/a repart = re.compile(r'^--([^\n]+)\n(.*?)\n--\1$', re.S | re.M)
4032n/a inpart = repart.search(original).group(2)
4033n/a outpart = repart.search(result).group(2)
4034n/a self.assertEqual(outpart, inpart)
4035n/a
4036n/a def test_long_headers_as_string(self):
4037n/a original, msg = self._msg_and_obj('msg_45.txt')
4038n/a result = msg.as_string()
4039n/a self._signed_parts_eq(original, result)
4040n/a
4041n/a def test_long_headers_as_string_maxheaderlen(self):
4042n/a original, msg = self._msg_and_obj('msg_45.txt')
4043n/a result = msg.as_string(maxheaderlen=60)
4044n/a self._signed_parts_eq(original, result)
4045n/a
4046n/a def test_long_headers_flatten(self):
4047n/a original, msg = self._msg_and_obj('msg_45.txt')
4048n/a fp = StringIO()
4049n/a Generator(fp).flatten(msg)
4050n/a result = fp.getvalue()
4051n/a self._signed_parts_eq(original, result)
4052n/a
4053n/a
4054n/a
4055n/adef _testclasses():
4056n/a mod = sys.modules[__name__]
4057n/a return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
4058n/a
4059n/a
4060n/adef suite():
4061n/a suite = unittest.TestSuite()
4062n/a for testclass in _testclasses():
4063n/a suite.addTest(unittest.makeSuite(testclass))
4064n/a return suite
4065n/a
4066n/a
4067n/adef test_main():
4068n/a for testclass in _testclasses():
4069n/a run_unittest(testclass)
4070n/a
4071n/a
4072n/a
4073n/aif __name__ == '__main__':
4074n/a unittest.main(defaultTest='suite')