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

Python code coverage for Lib/test/test_email/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 re
6n/aimport time
7n/aimport base64
8n/aimport unittest
9n/aimport textwrap
10n/a
11n/afrom io import StringIO, BytesIO
12n/afrom itertools import chain
13n/afrom random import choice
14n/afrom socket import getfqdn
15n/atry:
16n/a from threading import Thread
17n/aexcept ImportError:
18n/a from dummy_threading import Thread
19n/a
20n/aimport email
21n/aimport email.policy
22n/a
23n/afrom email.charset import Charset
24n/afrom email.header import Header, decode_header, make_header
25n/afrom email.parser import Parser, HeaderParser
26n/afrom email.generator import Generator, DecodedGenerator, BytesGenerator
27n/afrom email.message import Message
28n/afrom email.mime.application import MIMEApplication
29n/afrom email.mime.audio import MIMEAudio
30n/afrom email.mime.text import MIMEText
31n/afrom email.mime.image import MIMEImage
32n/afrom email.mime.base import MIMEBase
33n/afrom email.mime.message import MIMEMessage
34n/afrom email.mime.multipart import MIMEMultipart
35n/afrom email.mime.nonmultipart import MIMENonMultipart
36n/afrom email import utils
37n/afrom email import errors
38n/afrom email import encoders
39n/afrom email import iterators
40n/afrom email import base64mime
41n/afrom email import quoprimime
42n/a
43n/afrom test.support import unlink, start_threads
44n/afrom test.test_email import openfile, TestEmailBase
45n/a
46n/a# These imports are documented to work, but we are testing them using a
47n/a# different path, so we import them here just to make sure they are importable.
48n/afrom email.parser import FeedParser, BytesFeedParser
49n/a
50n/aNL = '\n'
51n/aEMPTYSTRING = ''
52n/aSPACE = ' '
53n/a
54n/a
55n/a# Test various aspects of the Message class's API
56n/aclass TestMessageAPI(TestEmailBase):
57n/a def test_get_all(self):
58n/a eq = self.assertEqual
59n/a msg = self._msgobj('msg_20.txt')
60n/a eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
61n/a eq(msg.get_all('xx', 'n/a'), 'n/a')
62n/a
63n/a def test_getset_charset(self):
64n/a eq = self.assertEqual
65n/a msg = Message()
66n/a eq(msg.get_charset(), None)
67n/a charset = Charset('iso-8859-1')
68n/a msg.set_charset(charset)
69n/a eq(msg['mime-version'], '1.0')
70n/a eq(msg.get_content_type(), 'text/plain')
71n/a eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
72n/a eq(msg.get_param('charset'), 'iso-8859-1')
73n/a eq(msg['content-transfer-encoding'], 'quoted-printable')
74n/a eq(msg.get_charset().input_charset, 'iso-8859-1')
75n/a # Remove the charset
76n/a msg.set_charset(None)
77n/a eq(msg.get_charset(), None)
78n/a eq(msg['content-type'], 'text/plain')
79n/a # Try adding a charset when there's already MIME headers present
80n/a msg = Message()
81n/a msg['MIME-Version'] = '2.0'
82n/a msg['Content-Type'] = 'text/x-weird'
83n/a msg['Content-Transfer-Encoding'] = 'quinted-puntable'
84n/a msg.set_charset(charset)
85n/a eq(msg['mime-version'], '2.0')
86n/a eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
87n/a eq(msg['content-transfer-encoding'], 'quinted-puntable')
88n/a
89n/a def test_set_charset_from_string(self):
90n/a eq = self.assertEqual
91n/a msg = Message()
92n/a msg.set_charset('us-ascii')
93n/a eq(msg.get_charset().input_charset, 'us-ascii')
94n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
95n/a
96n/a def test_set_payload_with_charset(self):
97n/a msg = Message()
98n/a charset = Charset('iso-8859-1')
99n/a msg.set_payload('This is a string payload', charset)
100n/a self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
101n/a
102n/a def test_set_payload_with_8bit_data_and_charset(self):
103n/a data = b'\xd0\x90\xd0\x91\xd0\x92'
104n/a charset = Charset('utf-8')
105n/a msg = Message()
106n/a msg.set_payload(data, charset)
107n/a self.assertEqual(msg['content-transfer-encoding'], 'base64')
108n/a self.assertEqual(msg.get_payload(decode=True), data)
109n/a self.assertEqual(msg.get_payload(), '0JDQkdCS\n')
110n/a
111n/a def test_set_payload_with_non_ascii_and_charset_body_encoding_none(self):
112n/a data = b'\xd0\x90\xd0\x91\xd0\x92'
113n/a charset = Charset('utf-8')
114n/a charset.body_encoding = None # Disable base64 encoding
115n/a msg = Message()
116n/a msg.set_payload(data.decode('utf-8'), charset)
117n/a self.assertEqual(msg['content-transfer-encoding'], '8bit')
118n/a self.assertEqual(msg.get_payload(decode=True), data)
119n/a
120n/a def test_set_payload_with_8bit_data_and_charset_body_encoding_none(self):
121n/a data = b'\xd0\x90\xd0\x91\xd0\x92'
122n/a charset = Charset('utf-8')
123n/a charset.body_encoding = None # Disable base64 encoding
124n/a msg = Message()
125n/a msg.set_payload(data, charset)
126n/a self.assertEqual(msg['content-transfer-encoding'], '8bit')
127n/a self.assertEqual(msg.get_payload(decode=True), data)
128n/a
129n/a def test_set_payload_to_list(self):
130n/a msg = Message()
131n/a msg.set_payload([])
132n/a self.assertEqual(msg.get_payload(), [])
133n/a
134n/a def test_attach_when_payload_is_string(self):
135n/a msg = Message()
136n/a msg['Content-Type'] = 'multipart/mixed'
137n/a msg.set_payload('string payload')
138n/a sub_msg = MIMEMessage(Message())
139n/a self.assertRaisesRegex(TypeError, "[Aa]ttach.*non-multipart",
140n/a msg.attach, sub_msg)
141n/a
142n/a def test_get_charsets(self):
143n/a eq = self.assertEqual
144n/a
145n/a msg = self._msgobj('msg_08.txt')
146n/a charsets = msg.get_charsets()
147n/a eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
148n/a
149n/a msg = self._msgobj('msg_09.txt')
150n/a charsets = msg.get_charsets('dingbat')
151n/a eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
152n/a 'koi8-r'])
153n/a
154n/a msg = self._msgobj('msg_12.txt')
155n/a charsets = msg.get_charsets()
156n/a eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
157n/a 'iso-8859-3', 'us-ascii', 'koi8-r'])
158n/a
159n/a def test_get_filename(self):
160n/a eq = self.assertEqual
161n/a
162n/a msg = self._msgobj('msg_04.txt')
163n/a filenames = [p.get_filename() for p in msg.get_payload()]
164n/a eq(filenames, ['msg.txt', 'msg.txt'])
165n/a
166n/a msg = self._msgobj('msg_07.txt')
167n/a subpart = msg.get_payload(1)
168n/a eq(subpart.get_filename(), 'dingusfish.gif')
169n/a
170n/a def test_get_filename_with_name_parameter(self):
171n/a eq = self.assertEqual
172n/a
173n/a msg = self._msgobj('msg_44.txt')
174n/a filenames = [p.get_filename() for p in msg.get_payload()]
175n/a eq(filenames, ['msg.txt', 'msg.txt'])
176n/a
177n/a def test_get_boundary(self):
178n/a eq = self.assertEqual
179n/a msg = self._msgobj('msg_07.txt')
180n/a # No quotes!
181n/a eq(msg.get_boundary(), 'BOUNDARY')
182n/a
183n/a def test_set_boundary(self):
184n/a eq = self.assertEqual
185n/a # This one has no existing boundary parameter, but the Content-Type:
186n/a # header appears fifth.
187n/a msg = self._msgobj('msg_01.txt')
188n/a msg.set_boundary('BOUNDARY')
189n/a header, value = msg.items()[4]
190n/a eq(header.lower(), 'content-type')
191n/a eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
192n/a # This one has a Content-Type: header, with a boundary, stuck in the
193n/a # middle of its headers. Make sure the order is preserved; it should
194n/a # be fifth.
195n/a msg = self._msgobj('msg_04.txt')
196n/a msg.set_boundary('BOUNDARY')
197n/a header, value = msg.items()[4]
198n/a eq(header.lower(), 'content-type')
199n/a eq(value, 'multipart/mixed; boundary="BOUNDARY"')
200n/a # And this one has no Content-Type: header at all.
201n/a msg = self._msgobj('msg_03.txt')
202n/a self.assertRaises(errors.HeaderParseError,
203n/a msg.set_boundary, 'BOUNDARY')
204n/a
205n/a def test_make_boundary(self):
206n/a msg = MIMEMultipart('form-data')
207n/a # Note that when the boundary gets created is an implementation
208n/a # detail and might change.
209n/a self.assertEqual(msg.items()[0][1], 'multipart/form-data')
210n/a # Trigger creation of boundary
211n/a msg.as_string()
212n/a self.assertEqual(msg.items()[0][1][:33],
213n/a 'multipart/form-data; boundary="==')
214n/a # XXX: there ought to be tests of the uniqueness of the boundary, too.
215n/a
216n/a def test_message_rfc822_only(self):
217n/a # Issue 7970: message/rfc822 not in multipart parsed by
218n/a # HeaderParser caused an exception when flattened.
219n/a with openfile('msg_46.txt') as fp:
220n/a msgdata = fp.read()
221n/a parser = HeaderParser()
222n/a msg = parser.parsestr(msgdata)
223n/a out = StringIO()
224n/a gen = Generator(out, True, 0)
225n/a gen.flatten(msg, False)
226n/a self.assertEqual(out.getvalue(), msgdata)
227n/a
228n/a def test_byte_message_rfc822_only(self):
229n/a # Make sure new bytes header parser also passes this.
230n/a with openfile('msg_46.txt') as fp:
231n/a msgdata = fp.read().encode('ascii')
232n/a parser = email.parser.BytesHeaderParser()
233n/a msg = parser.parsebytes(msgdata)
234n/a out = BytesIO()
235n/a gen = email.generator.BytesGenerator(out)
236n/a gen.flatten(msg)
237n/a self.assertEqual(out.getvalue(), msgdata)
238n/a
239n/a def test_get_decoded_payload(self):
240n/a eq = self.assertEqual
241n/a msg = self._msgobj('msg_10.txt')
242n/a # The outer message is a multipart
243n/a eq(msg.get_payload(decode=True), None)
244n/a # Subpart 1 is 7bit encoded
245n/a eq(msg.get_payload(0).get_payload(decode=True),
246n/a b'This is a 7bit encoded message.\n')
247n/a # Subpart 2 is quopri
248n/a eq(msg.get_payload(1).get_payload(decode=True),
249n/a b'\xa1This is a Quoted Printable encoded message!\n')
250n/a # Subpart 3 is base64
251n/a eq(msg.get_payload(2).get_payload(decode=True),
252n/a b'This is a Base64 encoded message.')
253n/a # Subpart 4 is base64 with a trailing newline, which
254n/a # used to be stripped (issue 7143).
255n/a eq(msg.get_payload(3).get_payload(decode=True),
256n/a b'This is a Base64 encoded message.\n')
257n/a # Subpart 5 has no Content-Transfer-Encoding: header.
258n/a eq(msg.get_payload(4).get_payload(decode=True),
259n/a b'This has no Content-Transfer-Encoding: header.\n')
260n/a
261n/a def test_get_decoded_uu_payload(self):
262n/a eq = self.assertEqual
263n/a msg = Message()
264n/a msg.set_payload('begin 666 -\n+:&5L;&\\@=V]R;&0 \n \nend\n')
265n/a for cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
266n/a msg['content-transfer-encoding'] = cte
267n/a eq(msg.get_payload(decode=True), b'hello world')
268n/a # Now try some bogus data
269n/a msg.set_payload('foo')
270n/a eq(msg.get_payload(decode=True), b'foo')
271n/a
272n/a def test_get_payload_n_raises_on_non_multipart(self):
273n/a msg = Message()
274n/a self.assertRaises(TypeError, msg.get_payload, 1)
275n/a
276n/a def test_decoded_generator(self):
277n/a eq = self.assertEqual
278n/a msg = self._msgobj('msg_07.txt')
279n/a with openfile('msg_17.txt') as fp:
280n/a text = fp.read()
281n/a s = StringIO()
282n/a g = DecodedGenerator(s)
283n/a g.flatten(msg)
284n/a eq(s.getvalue(), text)
285n/a
286n/a def test__contains__(self):
287n/a msg = Message()
288n/a msg['From'] = 'Me'
289n/a msg['to'] = 'You'
290n/a # Check for case insensitivity
291n/a self.assertIn('from', msg)
292n/a self.assertIn('From', msg)
293n/a self.assertIn('FROM', msg)
294n/a self.assertIn('to', msg)
295n/a self.assertIn('To', msg)
296n/a self.assertIn('TO', msg)
297n/a
298n/a def test_as_string(self):
299n/a msg = self._msgobj('msg_01.txt')
300n/a with openfile('msg_01.txt') as fp:
301n/a text = fp.read()
302n/a self.assertEqual(text, str(msg))
303n/a fullrepr = msg.as_string(unixfrom=True)
304n/a lines = fullrepr.split('\n')
305n/a self.assertTrue(lines[0].startswith('From '))
306n/a self.assertEqual(text, NL.join(lines[1:]))
307n/a
308n/a def test_as_string_policy(self):
309n/a msg = self._msgobj('msg_01.txt')
310n/a newpolicy = msg.policy.clone(linesep='\r\n')
311n/a fullrepr = msg.as_string(policy=newpolicy)
312n/a s = StringIO()
313n/a g = Generator(s, policy=newpolicy)
314n/a g.flatten(msg)
315n/a self.assertEqual(fullrepr, s.getvalue())
316n/a
317n/a def test_as_bytes(self):
318n/a msg = self._msgobj('msg_01.txt')
319n/a with openfile('msg_01.txt') as fp:
320n/a data = fp.read().encode('ascii')
321n/a self.assertEqual(data, bytes(msg))
322n/a fullrepr = msg.as_bytes(unixfrom=True)
323n/a lines = fullrepr.split(b'\n')
324n/a self.assertTrue(lines[0].startswith(b'From '))
325n/a self.assertEqual(data, b'\n'.join(lines[1:]))
326n/a
327n/a def test_as_bytes_policy(self):
328n/a msg = self._msgobj('msg_01.txt')
329n/a newpolicy = msg.policy.clone(linesep='\r\n')
330n/a fullrepr = msg.as_bytes(policy=newpolicy)
331n/a s = BytesIO()
332n/a g = BytesGenerator(s,policy=newpolicy)
333n/a g.flatten(msg)
334n/a self.assertEqual(fullrepr, s.getvalue())
335n/a
336n/a # test_headerregistry.TestContentTypeHeader.bad_params
337n/a def test_bad_param(self):
338n/a msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
339n/a self.assertEqual(msg.get_param('baz'), '')
340n/a
341n/a def test_missing_filename(self):
342n/a msg = email.message_from_string("From: foo\n")
343n/a self.assertEqual(msg.get_filename(), None)
344n/a
345n/a def test_bogus_filename(self):
346n/a msg = email.message_from_string(
347n/a "Content-Disposition: blarg; filename\n")
348n/a self.assertEqual(msg.get_filename(), '')
349n/a
350n/a def test_missing_boundary(self):
351n/a msg = email.message_from_string("From: foo\n")
352n/a self.assertEqual(msg.get_boundary(), None)
353n/a
354n/a def test_get_params(self):
355n/a eq = self.assertEqual
356n/a msg = email.message_from_string(
357n/a 'X-Header: foo=one; bar=two; baz=three\n')
358n/a eq(msg.get_params(header='x-header'),
359n/a [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
360n/a msg = email.message_from_string(
361n/a 'X-Header: foo; bar=one; baz=two\n')
362n/a eq(msg.get_params(header='x-header'),
363n/a [('foo', ''), ('bar', 'one'), ('baz', 'two')])
364n/a eq(msg.get_params(), None)
365n/a msg = email.message_from_string(
366n/a 'X-Header: foo; bar="one"; baz=two\n')
367n/a eq(msg.get_params(header='x-header'),
368n/a [('foo', ''), ('bar', 'one'), ('baz', 'two')])
369n/a
370n/a # test_headerregistry.TestContentTypeHeader.spaces_around_param_equals
371n/a def test_get_param_liberal(self):
372n/a msg = Message()
373n/a msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
374n/a self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
375n/a
376n/a def test_get_param(self):
377n/a eq = self.assertEqual
378n/a msg = email.message_from_string(
379n/a "X-Header: foo=one; bar=two; baz=three\n")
380n/a eq(msg.get_param('bar', header='x-header'), 'two')
381n/a eq(msg.get_param('quuz', header='x-header'), None)
382n/a eq(msg.get_param('quuz'), None)
383n/a msg = email.message_from_string(
384n/a 'X-Header: foo; bar="one"; baz=two\n')
385n/a eq(msg.get_param('foo', header='x-header'), '')
386n/a eq(msg.get_param('bar', header='x-header'), 'one')
387n/a eq(msg.get_param('baz', header='x-header'), 'two')
388n/a # XXX: We are not RFC-2045 compliant! We cannot parse:
389n/a # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
390n/a # msg.get_param("weird")
391n/a # yet.
392n/a
393n/a # test_headerregistry.TestContentTypeHeader.spaces_around_semis
394n/a def test_get_param_funky_continuation_lines(self):
395n/a msg = self._msgobj('msg_22.txt')
396n/a self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
397n/a
398n/a # test_headerregistry.TestContentTypeHeader.semis_inside_quotes
399n/a def test_get_param_with_semis_in_quotes(self):
400n/a msg = email.message_from_string(
401n/a 'Content-Type: image/pjpeg; name="Jim&amp;&amp;Jill"\n')
402n/a self.assertEqual(msg.get_param('name'), 'Jim&amp;&amp;Jill')
403n/a self.assertEqual(msg.get_param('name', unquote=False),
404n/a '"Jim&amp;&amp;Jill"')
405n/a
406n/a # test_headerregistry.TestContentTypeHeader.quotes_inside_rfc2231_value
407n/a def test_get_param_with_quotes(self):
408n/a msg = email.message_from_string(
409n/a 'Content-Type: foo; bar*0="baz\\"foobar"; bar*1="\\"baz"')
410n/a self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
411n/a msg = email.message_from_string(
412n/a "Content-Type: foo; bar*0=\"baz\\\"foobar\"; bar*1=\"\\\"baz\"")
413n/a self.assertEqual(msg.get_param('bar'), 'baz"foobar"baz')
414n/a
415n/a def test_field_containment(self):
416n/a msg = email.message_from_string('Header: exists')
417n/a self.assertIn('header', msg)
418n/a self.assertIn('Header', msg)
419n/a self.assertIn('HEADER', msg)
420n/a self.assertNotIn('headerx', msg)
421n/a
422n/a def test_set_param(self):
423n/a eq = self.assertEqual
424n/a msg = Message()
425n/a msg.set_param('charset', 'iso-2022-jp')
426n/a eq(msg.get_param('charset'), 'iso-2022-jp')
427n/a msg.set_param('importance', 'high value')
428n/a eq(msg.get_param('importance'), 'high value')
429n/a eq(msg.get_param('importance', unquote=False), '"high value"')
430n/a eq(msg.get_params(), [('text/plain', ''),
431n/a ('charset', 'iso-2022-jp'),
432n/a ('importance', 'high value')])
433n/a eq(msg.get_params(unquote=False), [('text/plain', ''),
434n/a ('charset', '"iso-2022-jp"'),
435n/a ('importance', '"high value"')])
436n/a msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
437n/a eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
438n/a
439n/a def test_del_param(self):
440n/a eq = self.assertEqual
441n/a msg = self._msgobj('msg_05.txt')
442n/a eq(msg.get_params(),
443n/a [('multipart/report', ''), ('report-type', 'delivery-status'),
444n/a ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
445n/a old_val = msg.get_param("report-type")
446n/a msg.del_param("report-type")
447n/a eq(msg.get_params(),
448n/a [('multipart/report', ''),
449n/a ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
450n/a msg.set_param("report-type", old_val)
451n/a eq(msg.get_params(),
452n/a [('multipart/report', ''),
453n/a ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
454n/a ('report-type', old_val)])
455n/a
456n/a def test_del_param_on_other_header(self):
457n/a msg = Message()
458n/a msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')
459n/a msg.del_param('filename', 'content-disposition')
460n/a self.assertEqual(msg['content-disposition'], 'attachment')
461n/a
462n/a def test_del_param_on_nonexistent_header(self):
463n/a msg = Message()
464n/a # Deleting param on empty msg should not raise exception.
465n/a msg.del_param('filename', 'content-disposition')
466n/a
467n/a def test_del_nonexistent_param(self):
468n/a msg = Message()
469n/a msg.add_header('Content-Type', 'text/plain', charset='utf-8')
470n/a existing_header = msg['Content-Type']
471n/a msg.del_param('foobar', header='Content-Type')
472n/a self.assertEqual(msg['Content-Type'], existing_header)
473n/a
474n/a def test_set_type(self):
475n/a eq = self.assertEqual
476n/a msg = Message()
477n/a self.assertRaises(ValueError, msg.set_type, 'text')
478n/a msg.set_type('text/plain')
479n/a eq(msg['content-type'], 'text/plain')
480n/a msg.set_param('charset', 'us-ascii')
481n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
482n/a msg.set_type('text/html')
483n/a eq(msg['content-type'], 'text/html; charset="us-ascii"')
484n/a
485n/a def test_set_type_on_other_header(self):
486n/a msg = Message()
487n/a msg['X-Content-Type'] = 'text/plain'
488n/a msg.set_type('application/octet-stream', 'X-Content-Type')
489n/a self.assertEqual(msg['x-content-type'], 'application/octet-stream')
490n/a
491n/a def test_get_content_type_missing(self):
492n/a msg = Message()
493n/a self.assertEqual(msg.get_content_type(), 'text/plain')
494n/a
495n/a def test_get_content_type_missing_with_default_type(self):
496n/a msg = Message()
497n/a msg.set_default_type('message/rfc822')
498n/a self.assertEqual(msg.get_content_type(), 'message/rfc822')
499n/a
500n/a def test_get_content_type_from_message_implicit(self):
501n/a msg = self._msgobj('msg_30.txt')
502n/a self.assertEqual(msg.get_payload(0).get_content_type(),
503n/a 'message/rfc822')
504n/a
505n/a def test_get_content_type_from_message_explicit(self):
506n/a msg = self._msgobj('msg_28.txt')
507n/a self.assertEqual(msg.get_payload(0).get_content_type(),
508n/a 'message/rfc822')
509n/a
510n/a def test_get_content_type_from_message_text_plain_implicit(self):
511n/a msg = self._msgobj('msg_03.txt')
512n/a self.assertEqual(msg.get_content_type(), 'text/plain')
513n/a
514n/a def test_get_content_type_from_message_text_plain_explicit(self):
515n/a msg = self._msgobj('msg_01.txt')
516n/a self.assertEqual(msg.get_content_type(), 'text/plain')
517n/a
518n/a def test_get_content_maintype_missing(self):
519n/a msg = Message()
520n/a self.assertEqual(msg.get_content_maintype(), 'text')
521n/a
522n/a def test_get_content_maintype_missing_with_default_type(self):
523n/a msg = Message()
524n/a msg.set_default_type('message/rfc822')
525n/a self.assertEqual(msg.get_content_maintype(), 'message')
526n/a
527n/a def test_get_content_maintype_from_message_implicit(self):
528n/a msg = self._msgobj('msg_30.txt')
529n/a self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
530n/a
531n/a def test_get_content_maintype_from_message_explicit(self):
532n/a msg = self._msgobj('msg_28.txt')
533n/a self.assertEqual(msg.get_payload(0).get_content_maintype(), 'message')
534n/a
535n/a def test_get_content_maintype_from_message_text_plain_implicit(self):
536n/a msg = self._msgobj('msg_03.txt')
537n/a self.assertEqual(msg.get_content_maintype(), 'text')
538n/a
539n/a def test_get_content_maintype_from_message_text_plain_explicit(self):
540n/a msg = self._msgobj('msg_01.txt')
541n/a self.assertEqual(msg.get_content_maintype(), 'text')
542n/a
543n/a def test_get_content_subtype_missing(self):
544n/a msg = Message()
545n/a self.assertEqual(msg.get_content_subtype(), 'plain')
546n/a
547n/a def test_get_content_subtype_missing_with_default_type(self):
548n/a msg = Message()
549n/a msg.set_default_type('message/rfc822')
550n/a self.assertEqual(msg.get_content_subtype(), 'rfc822')
551n/a
552n/a def test_get_content_subtype_from_message_implicit(self):
553n/a msg = self._msgobj('msg_30.txt')
554n/a self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
555n/a
556n/a def test_get_content_subtype_from_message_explicit(self):
557n/a msg = self._msgobj('msg_28.txt')
558n/a self.assertEqual(msg.get_payload(0).get_content_subtype(), 'rfc822')
559n/a
560n/a def test_get_content_subtype_from_message_text_plain_implicit(self):
561n/a msg = self._msgobj('msg_03.txt')
562n/a self.assertEqual(msg.get_content_subtype(), 'plain')
563n/a
564n/a def test_get_content_subtype_from_message_text_plain_explicit(self):
565n/a msg = self._msgobj('msg_01.txt')
566n/a self.assertEqual(msg.get_content_subtype(), 'plain')
567n/a
568n/a def test_get_content_maintype_error(self):
569n/a msg = Message()
570n/a msg['Content-Type'] = 'no-slash-in-this-string'
571n/a self.assertEqual(msg.get_content_maintype(), 'text')
572n/a
573n/a def test_get_content_subtype_error(self):
574n/a msg = Message()
575n/a msg['Content-Type'] = 'no-slash-in-this-string'
576n/a self.assertEqual(msg.get_content_subtype(), 'plain')
577n/a
578n/a def test_replace_header(self):
579n/a eq = self.assertEqual
580n/a msg = Message()
581n/a msg.add_header('First', 'One')
582n/a msg.add_header('Second', 'Two')
583n/a msg.add_header('Third', 'Three')
584n/a eq(msg.keys(), ['First', 'Second', 'Third'])
585n/a eq(msg.values(), ['One', 'Two', 'Three'])
586n/a msg.replace_header('Second', 'Twenty')
587n/a eq(msg.keys(), ['First', 'Second', 'Third'])
588n/a eq(msg.values(), ['One', 'Twenty', 'Three'])
589n/a msg.add_header('First', 'Eleven')
590n/a msg.replace_header('First', 'One Hundred')
591n/a eq(msg.keys(), ['First', 'Second', 'Third', 'First'])
592n/a eq(msg.values(), ['One Hundred', 'Twenty', 'Three', 'Eleven'])
593n/a self.assertRaises(KeyError, msg.replace_header, 'Fourth', 'Missing')
594n/a
595n/a def test_get_content_disposition(self):
596n/a msg = Message()
597n/a self.assertIsNone(msg.get_content_disposition())
598n/a msg.add_header('Content-Disposition', 'attachment',
599n/a filename='random.avi')
600n/a self.assertEqual(msg.get_content_disposition(), 'attachment')
601n/a msg.replace_header('Content-Disposition', 'inline')
602n/a self.assertEqual(msg.get_content_disposition(), 'inline')
603n/a msg.replace_header('Content-Disposition', 'InlinE')
604n/a self.assertEqual(msg.get_content_disposition(), 'inline')
605n/a
606n/a # test_defect_handling:test_invalid_chars_in_base64_payload
607n/a def test_broken_base64_payload(self):
608n/a x = 'AwDp0P7//y6LwKEAcPa/6Q=9'
609n/a msg = Message()
610n/a msg['content-type'] = 'audio/x-midi'
611n/a msg['content-transfer-encoding'] = 'base64'
612n/a msg.set_payload(x)
613n/a self.assertEqual(msg.get_payload(decode=True),
614n/a (b'\x03\x00\xe9\xd0\xfe\xff\xff.\x8b\xc0'
615n/a b'\xa1\x00p\xf6\xbf\xe9\x0f'))
616n/a self.assertIsInstance(msg.defects[0],
617n/a errors.InvalidBase64CharactersDefect)
618n/a
619n/a def test_broken_unicode_payload(self):
620n/a # This test improves coverage but is not a compliance test.
621n/a # The behavior in this situation is currently undefined by the API.
622n/a x = 'this is a br\xf6ken thing to do'
623n/a msg = Message()
624n/a msg['content-type'] = 'text/plain'
625n/a msg['content-transfer-encoding'] = '8bit'
626n/a msg.set_payload(x)
627n/a self.assertEqual(msg.get_payload(decode=True),
628n/a bytes(x, 'raw-unicode-escape'))
629n/a
630n/a def test_questionable_bytes_payload(self):
631n/a # This test improves coverage but is not a compliance test,
632n/a # since it involves poking inside the black box.
633n/a x = 'this is a quéstionable thing to do'.encode('utf-8')
634n/a msg = Message()
635n/a msg['content-type'] = 'text/plain; charset="utf-8"'
636n/a msg['content-transfer-encoding'] = '8bit'
637n/a msg._payload = x
638n/a self.assertEqual(msg.get_payload(decode=True), x)
639n/a
640n/a # Issue 1078919
641n/a def test_ascii_add_header(self):
642n/a msg = Message()
643n/a msg.add_header('Content-Disposition', 'attachment',
644n/a filename='bud.gif')
645n/a self.assertEqual('attachment; filename="bud.gif"',
646n/a msg['Content-Disposition'])
647n/a
648n/a def test_noascii_add_header(self):
649n/a msg = Message()
650n/a msg.add_header('Content-Disposition', 'attachment',
651n/a filename="Fußballer.ppt")
652n/a self.assertEqual(
653n/a 'attachment; filename*=utf-8\'\'Fu%C3%9Fballer.ppt',
654n/a msg['Content-Disposition'])
655n/a
656n/a def test_nonascii_add_header_via_triple(self):
657n/a msg = Message()
658n/a msg.add_header('Content-Disposition', 'attachment',
659n/a filename=('iso-8859-1', '', 'Fußballer.ppt'))
660n/a self.assertEqual(
661n/a 'attachment; filename*=iso-8859-1\'\'Fu%DFballer.ppt',
662n/a msg['Content-Disposition'])
663n/a
664n/a def test_ascii_add_header_with_tspecial(self):
665n/a msg = Message()
666n/a msg.add_header('Content-Disposition', 'attachment',
667n/a filename="windows [filename].ppt")
668n/a self.assertEqual(
669n/a 'attachment; filename="windows [filename].ppt"',
670n/a msg['Content-Disposition'])
671n/a
672n/a def test_nonascii_add_header_with_tspecial(self):
673n/a msg = Message()
674n/a msg.add_header('Content-Disposition', 'attachment',
675n/a filename="Fußballer [filename].ppt")
676n/a self.assertEqual(
677n/a "attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt",
678n/a msg['Content-Disposition'])
679n/a
680n/a def test_binary_quopri_payload(self):
681n/a for charset in ('latin-1', 'ascii'):
682n/a msg = Message()
683n/a msg['content-type'] = 'text/plain; charset=%s' % charset
684n/a msg['content-transfer-encoding'] = 'quoted-printable'
685n/a msg.set_payload(b'foo=e6=96=87bar')
686n/a self.assertEqual(
687n/a msg.get_payload(decode=True),
688n/a b'foo\xe6\x96\x87bar',
689n/a 'get_payload returns wrong result with charset %s.' % charset)
690n/a
691n/a def test_binary_base64_payload(self):
692n/a for charset in ('latin-1', 'ascii'):
693n/a msg = Message()
694n/a msg['content-type'] = 'text/plain; charset=%s' % charset
695n/a msg['content-transfer-encoding'] = 'base64'
696n/a msg.set_payload(b'Zm9v5paHYmFy')
697n/a self.assertEqual(
698n/a msg.get_payload(decode=True),
699n/a b'foo\xe6\x96\x87bar',
700n/a 'get_payload returns wrong result with charset %s.' % charset)
701n/a
702n/a def test_binary_uuencode_payload(self):
703n/a for charset in ('latin-1', 'ascii'):
704n/a for encoding in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
705n/a msg = Message()
706n/a msg['content-type'] = 'text/plain; charset=%s' % charset
707n/a msg['content-transfer-encoding'] = encoding
708n/a msg.set_payload(b"begin 666 -\n)9F]OYI:'8F%R\n \nend\n")
709n/a self.assertEqual(
710n/a msg.get_payload(decode=True),
711n/a b'foo\xe6\x96\x87bar',
712n/a str(('get_payload returns wrong result ',
713n/a 'with charset {0} and encoding {1}.')).\
714n/a format(charset, encoding))
715n/a
716n/a def test_add_header_with_name_only_param(self):
717n/a msg = Message()
718n/a msg.add_header('Content-Disposition', 'inline', foo_bar=None)
719n/a self.assertEqual("inline; foo-bar", msg['Content-Disposition'])
720n/a
721n/a def test_add_header_with_no_value(self):
722n/a msg = Message()
723n/a msg.add_header('X-Status', None)
724n/a self.assertEqual('', msg['X-Status'])
725n/a
726n/a # Issue 5871: reject an attempt to embed a header inside a header value
727n/a # (header injection attack).
728n/a def test_embedded_header_via_Header_rejected(self):
729n/a msg = Message()
730n/a msg['Dummy'] = Header('dummy\nX-Injected-Header: test')
731n/a self.assertRaises(errors.HeaderParseError, msg.as_string)
732n/a
733n/a def test_embedded_header_via_string_rejected(self):
734n/a msg = Message()
735n/a msg['Dummy'] = 'dummy\nX-Injected-Header: test'
736n/a self.assertRaises(errors.HeaderParseError, msg.as_string)
737n/a
738n/a def test_unicode_header_defaults_to_utf8_encoding(self):
739n/a # Issue 14291
740n/a m = MIMEText('abc\n')
741n/a m['Subject'] = 'É test'
742n/a self.assertEqual(str(m),textwrap.dedent("""\
743n/a Content-Type: text/plain; charset="us-ascii"
744n/a MIME-Version: 1.0
745n/a Content-Transfer-Encoding: 7bit
746n/a Subject: =?utf-8?q?=C3=89_test?=
747n/a
748n/a abc
749n/a """))
750n/a
751n/a def test_unicode_body_defaults_to_utf8_encoding(self):
752n/a # Issue 14291
753n/a m = MIMEText('É testabc\n')
754n/a self.assertEqual(str(m),textwrap.dedent("""\
755n/a Content-Type: text/plain; charset="utf-8"
756n/a MIME-Version: 1.0
757n/a Content-Transfer-Encoding: base64
758n/a
759n/a w4kgdGVzdGFiYwo=
760n/a """))
761n/a
762n/a
763n/a# Test the email.encoders module
764n/aclass TestEncoders(unittest.TestCase):
765n/a
766n/a def test_EncodersEncode_base64(self):
767n/a with openfile('PyBanner048.gif', 'rb') as fp:
768n/a bindata = fp.read()
769n/a mimed = email.mime.image.MIMEImage(bindata)
770n/a base64ed = mimed.get_payload()
771n/a # the transfer-encoded body lines should all be <=76 characters
772n/a lines = base64ed.split('\n')
773n/a self.assertLessEqual(max([ len(x) for x in lines ]), 76)
774n/a
775n/a def test_encode_empty_payload(self):
776n/a eq = self.assertEqual
777n/a msg = Message()
778n/a msg.set_charset('us-ascii')
779n/a eq(msg['content-transfer-encoding'], '7bit')
780n/a
781n/a def test_default_cte(self):
782n/a eq = self.assertEqual
783n/a # 7bit data and the default us-ascii _charset
784n/a msg = MIMEText('hello world')
785n/a eq(msg['content-transfer-encoding'], '7bit')
786n/a # Similar, but with 8bit data
787n/a msg = MIMEText('hello \xf8 world')
788n/a eq(msg['content-transfer-encoding'], 'base64')
789n/a # And now with a different charset
790n/a msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
791n/a eq(msg['content-transfer-encoding'], 'quoted-printable')
792n/a
793n/a def test_encode7or8bit(self):
794n/a # Make sure a charset whose input character set is 8bit but
795n/a # whose output character set is 7bit gets a transfer-encoding
796n/a # of 7bit.
797n/a eq = self.assertEqual
798n/a msg = MIMEText('æ–‡\n', _charset='euc-jp')
799n/a eq(msg['content-transfer-encoding'], '7bit')
800n/a eq(msg.as_string(), textwrap.dedent("""\
801n/a MIME-Version: 1.0
802n/a Content-Type: text/plain; charset="iso-2022-jp"
803n/a Content-Transfer-Encoding: 7bit
804n/a
805n/a \x1b$BJ8\x1b(B
806n/a """))
807n/a
808n/a def test_qp_encode_latin1(self):
809n/a msg = MIMEText('\xe1\xf6\n', 'text', 'ISO-8859-1')
810n/a self.assertEqual(str(msg), textwrap.dedent("""\
811n/a MIME-Version: 1.0
812n/a Content-Type: text/text; charset="iso-8859-1"
813n/a Content-Transfer-Encoding: quoted-printable
814n/a
815n/a =E1=F6
816n/a """))
817n/a
818n/a def test_qp_encode_non_latin1(self):
819n/a # Issue 16948
820n/a msg = MIMEText('\u017c\n', 'text', 'ISO-8859-2')
821n/a self.assertEqual(str(msg), textwrap.dedent("""\
822n/a MIME-Version: 1.0
823n/a Content-Type: text/text; charset="iso-8859-2"
824n/a Content-Transfer-Encoding: quoted-printable
825n/a
826n/a =BF
827n/a """))
828n/a
829n/a
830n/a# Test long header wrapping
831n/aclass TestLongHeaders(TestEmailBase):
832n/a
833n/a maxDiff = None
834n/a
835n/a def test_split_long_continuation(self):
836n/a eq = self.ndiffAssertEqual
837n/a msg = email.message_from_string("""\
838n/aSubject: bug demonstration
839n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
840n/a\tmore text
841n/a
842n/atest
843n/a""")
844n/a sfp = StringIO()
845n/a g = Generator(sfp)
846n/a g.flatten(msg)
847n/a eq(sfp.getvalue(), """\
848n/aSubject: bug demonstration
849n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
850n/a\tmore text
851n/a
852n/atest
853n/a""")
854n/a
855n/a def test_another_long_almost_unsplittable_header(self):
856n/a eq = self.ndiffAssertEqual
857n/a hstr = """\
858n/abug demonstration
859n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
860n/a\tmore text"""
861n/a h = Header(hstr, continuation_ws='\t')
862n/a eq(h.encode(), """\
863n/abug demonstration
864n/a\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
865n/a\tmore text""")
866n/a h = Header(hstr.replace('\t', ' '))
867n/a eq(h.encode(), """\
868n/abug demonstration
869n/a 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
870n/a more text""")
871n/a
872n/a def test_long_nonstring(self):
873n/a eq = self.ndiffAssertEqual
874n/a g = Charset("iso-8859-1")
875n/a cz = Charset("iso-8859-2")
876n/a utf8 = Charset("utf-8")
877n/a g_head = (b'Die Mieter treten hier ein werden mit einem Foerderband '
878n/a b'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
879n/a b'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
880n/a b'bef\xf6rdert. ')
881n/a cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
882n/a b'd\xf9vtipu.. ')
883n/a utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
884n/a '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
885n/a '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
886n/a '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
887n/a '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
888n/a 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
889n/a 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
890n/a '\u3044\u307e\u3059\u3002')
891n/a h = Header(g_head, g, header_name='Subject')
892n/a h.append(cz_head, cz)
893n/a h.append(utf8_head, utf8)
894n/a msg = Message()
895n/a msg['Subject'] = h
896n/a sfp = StringIO()
897n/a g = Generator(sfp)
898n/a g.flatten(msg)
899n/a eq(sfp.getvalue(), """\
900n/aSubject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderb?=
901n/a =?iso-8859-1?q?and_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen?=
902n/a =?iso-8859-1?q?_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef?=
903n/a =?iso-8859-1?q?=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hrouti?=
904n/a =?iso-8859-2?q?ly_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
905n/a =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
906n/a =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn44Gf44KJ?=
907n/a =?utf-8?b?44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFzIE51bnN0dWNr?=
908n/a =?utf-8?b?IGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5kIGRhcyBPZGVyIGRp?=
909n/a =?utf-8?b?ZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIDjgaPjgabjgYTjgb7jgZk=?=
910n/a =?utf-8?b?44CC?=
911n/a
912n/a""")
913n/a eq(h.encode(maxlinelen=76), """\
914n/a=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerde?=
915n/a =?iso-8859-1?q?rband_komfortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndis?=
916n/a =?iso-8859-1?q?chen_Wandgem=E4lden_vorbei=2C_gegen_die_rotierenden_Klinge?=
917n/a =?iso-8859-1?q?n_bef=F6rdert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se?=
918n/a =?iso-8859-2?q?_hroutily_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
919n/a =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb?=
920n/a =?utf-8?b?44KT44CC5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go?=
921n/a =?utf-8?b?44Gv44Gn44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBp?=
922n/a =?utf-8?b?c3QgZGFzIE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWlo?=
923n/a =?utf-8?b?ZXJodW5kIGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI0=?=
924n/a =?utf-8?b?44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
925n/a
926n/a def test_long_header_encode(self):
927n/a eq = self.ndiffAssertEqual
928n/a h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
929n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
930n/a header_name='X-Foobar-Spoink-Defrobnit')
931n/a eq(h.encode(), '''\
932n/awasnipoop; giraffes="very-long-necked-animals";
933n/a spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
934n/a
935n/a def test_long_header_encode_with_tab_continuation_is_just_a_hint(self):
936n/a eq = self.ndiffAssertEqual
937n/a h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
938n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
939n/a header_name='X-Foobar-Spoink-Defrobnit',
940n/a continuation_ws='\t')
941n/a eq(h.encode(), '''\
942n/awasnipoop; giraffes="very-long-necked-animals";
943n/a spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
944n/a
945n/a def test_long_header_encode_with_tab_continuation(self):
946n/a eq = self.ndiffAssertEqual
947n/a h = Header('wasnipoop; giraffes="very-long-necked-animals";\t'
948n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
949n/a header_name='X-Foobar-Spoink-Defrobnit',
950n/a continuation_ws='\t')
951n/a eq(h.encode(), '''\
952n/awasnipoop; giraffes="very-long-necked-animals";
953n/a\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
954n/a
955n/a def test_header_encode_with_different_output_charset(self):
956n/a h = Header('æ–‡', 'euc-jp')
957n/a self.assertEqual(h.encode(), "=?iso-2022-jp?b?GyRCSjgbKEI=?=")
958n/a
959n/a def test_long_header_encode_with_different_output_charset(self):
960n/a h = Header(b'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4'
961n/a b'\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4'
962n/a b'\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4'
963n/a b'\xa4\xa4\xde\xa4\xb9'.decode('euc-jp'), 'euc-jp')
964n/a res = """\
965n/a=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKMnE8VCROPjUbKEI=?=
966n/a =?iso-2022-jp?b?GyRCRyckckJUJEMkRiQkJF4kORsoQg==?="""
967n/a self.assertEqual(h.encode(), res)
968n/a
969n/a def test_header_splitter(self):
970n/a eq = self.ndiffAssertEqual
971n/a msg = MIMEText('')
972n/a # It'd be great if we could use add_header() here, but that doesn't
973n/a # guarantee an order of the parameters.
974n/a msg['X-Foobar-Spoink-Defrobnit'] = (
975n/a 'wasnipoop; giraffes="very-long-necked-animals"; '
976n/a 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
977n/a sfp = StringIO()
978n/a g = Generator(sfp)
979n/a g.flatten(msg)
980n/a eq(sfp.getvalue(), '''\
981n/aContent-Type: text/plain; charset="us-ascii"
982n/aMIME-Version: 1.0
983n/aContent-Transfer-Encoding: 7bit
984n/aX-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
985n/a spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
986n/a
987n/a''')
988n/a
989n/a def test_no_semis_header_splitter(self):
990n/a eq = self.ndiffAssertEqual
991n/a msg = Message()
992n/a msg['From'] = 'test@dom.ain'
993n/a msg['References'] = SPACE.join('<%d@dom.ain>' % i for i in range(10))
994n/a msg.set_payload('Test')
995n/a sfp = StringIO()
996n/a g = Generator(sfp)
997n/a g.flatten(msg)
998n/a eq(sfp.getvalue(), """\
999n/aFrom: test@dom.ain
1000n/aReferences: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
1001n/a <5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
1002n/a
1003n/aTest""")
1004n/a
1005n/a def test_last_split_chunk_does_not_fit(self):
1006n/a eq = self.ndiffAssertEqual
1007n/a h = Header('Subject: the first part of this is short, but_the_second'
1008n/a '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line'
1009n/a '_all_by_itself')
1010n/a eq(h.encode(), """\
1011n/aSubject: the first part of this is short,
1012n/a but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""")
1013n/a
1014n/a def test_splittable_leading_char_followed_by_overlong_unsplitable(self):
1015n/a eq = self.ndiffAssertEqual
1016n/a h = Header(', but_the_second'
1017n/a '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line'
1018n/a '_all_by_itself')
1019n/a eq(h.encode(), """\
1020n/a,
1021n/a but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""")
1022n/a
1023n/a def test_multiple_splittable_leading_char_followed_by_overlong_unsplitable(self):
1024n/a eq = self.ndiffAssertEqual
1025n/a h = Header(', , but_the_second'
1026n/a '_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line'
1027n/a '_all_by_itself')
1028n/a eq(h.encode(), """\
1029n/a, ,
1030n/a but_the_second_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself""")
1031n/a
1032n/a def test_trailing_splitable_on_overlong_unsplitable(self):
1033n/a eq = self.ndiffAssertEqual
1034n/a h = Header('this_part_does_not_fit_within_maxlinelen_and_thus_should_'
1035n/a 'be_on_a_line_all_by_itself;')
1036n/a eq(h.encode(), "this_part_does_not_fit_within_maxlinelen_and_thus_should_"
1037n/a "be_on_a_line_all_by_itself;")
1038n/a
1039n/a def test_trailing_splitable_on_overlong_unsplitable_with_leading_splitable(self):
1040n/a eq = self.ndiffAssertEqual
1041n/a h = Header('; '
1042n/a 'this_part_does_not_fit_within_maxlinelen_and_thus_should_'
1043n/a 'be_on_a_line_all_by_itself; ')
1044n/a eq(h.encode(), """\
1045n/a;
1046n/a this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """)
1047n/a
1048n/a def test_long_header_with_multiple_sequential_split_chars(self):
1049n/a eq = self.ndiffAssertEqual
1050n/a h = Header('This is a long line that has two whitespaces in a row. '
1051n/a 'This used to cause truncation of the header when folded')
1052n/a eq(h.encode(), """\
1053n/aThis is a long line that has two whitespaces in a row. This used to cause
1054n/a truncation of the header when folded""")
1055n/a
1056n/a def test_splitter_split_on_punctuation_only_if_fws_with_header(self):
1057n/a eq = self.ndiffAssertEqual
1058n/a h = Header('thisverylongheaderhas;semicolons;and,commas,but'
1059n/a 'they;arenotlegal;fold,points')
1060n/a eq(h.encode(), "thisverylongheaderhas;semicolons;and,commas,butthey;"
1061n/a "arenotlegal;fold,points")
1062n/a
1063n/a def test_leading_splittable_in_the_middle_just_before_overlong_last_part(self):
1064n/a eq = self.ndiffAssertEqual
1065n/a h = Header('this is a test where we need to have more than one line '
1066n/a 'before; our final line that is just too big to fit;; '
1067n/a 'this_part_does_not_fit_within_maxlinelen_and_thus_should_'
1068n/a 'be_on_a_line_all_by_itself;')
1069n/a eq(h.encode(), """\
1070n/athis is a test where we need to have more than one line before;
1071n/a our final line that is just too big to fit;;
1072n/a this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself;""")
1073n/a
1074n/a def test_overlong_last_part_followed_by_split_point(self):
1075n/a eq = self.ndiffAssertEqual
1076n/a h = Header('this_part_does_not_fit_within_maxlinelen_and_thus_should_'
1077n/a 'be_on_a_line_all_by_itself ')
1078n/a eq(h.encode(), "this_part_does_not_fit_within_maxlinelen_and_thus_"
1079n/a "should_be_on_a_line_all_by_itself ")
1080n/a
1081n/a def test_multiline_with_overlong_parts_separated_by_two_split_points(self):
1082n/a eq = self.ndiffAssertEqual
1083n/a h = Header('this_is_a__test_where_we_need_to_have_more_than_one_line_'
1084n/a 'before_our_final_line_; ; '
1085n/a 'this_part_does_not_fit_within_maxlinelen_and_thus_should_'
1086n/a 'be_on_a_line_all_by_itself; ')
1087n/a eq(h.encode(), """\
1088n/athis_is_a__test_where_we_need_to_have_more_than_one_line_before_our_final_line_;
1089n/a ;
1090n/a this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """)
1091n/a
1092n/a def test_multiline_with_overlong_last_part_followed_by_split_point(self):
1093n/a eq = self.ndiffAssertEqual
1094n/a h = Header('this is a test where we need to have more than one line '
1095n/a 'before our final line; ; '
1096n/a 'this_part_does_not_fit_within_maxlinelen_and_thus_should_'
1097n/a 'be_on_a_line_all_by_itself; ')
1098n/a eq(h.encode(), """\
1099n/athis is a test where we need to have more than one line before our final line;
1100n/a ;
1101n/a this_part_does_not_fit_within_maxlinelen_and_thus_should_be_on_a_line_all_by_itself; """)
1102n/a
1103n/a def test_long_header_with_whitespace_runs(self):
1104n/a eq = self.ndiffAssertEqual
1105n/a msg = Message()
1106n/a msg['From'] = 'test@dom.ain'
1107n/a msg['References'] = SPACE.join(['<foo@dom.ain> '] * 10)
1108n/a msg.set_payload('Test')
1109n/a sfp = StringIO()
1110n/a g = Generator(sfp)
1111n/a g.flatten(msg)
1112n/a eq(sfp.getvalue(), """\
1113n/aFrom: test@dom.ain
1114n/aReferences: <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain>
1115n/a <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain>
1116n/a <foo@dom.ain> <foo@dom.ain>\x20\x20
1117n/a
1118n/aTest""")
1119n/a
1120n/a def test_long_run_with_semi_header_splitter(self):
1121n/a eq = self.ndiffAssertEqual
1122n/a msg = Message()
1123n/a msg['From'] = 'test@dom.ain'
1124n/a msg['References'] = SPACE.join(['<foo@dom.ain>'] * 10) + '; abc'
1125n/a msg.set_payload('Test')
1126n/a sfp = StringIO()
1127n/a g = Generator(sfp)
1128n/a g.flatten(msg)
1129n/a eq(sfp.getvalue(), """\
1130n/aFrom: test@dom.ain
1131n/aReferences: <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain>
1132n/a <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain> <foo@dom.ain>
1133n/a <foo@dom.ain>; abc
1134n/a
1135n/aTest""")
1136n/a
1137n/a def test_splitter_split_on_punctuation_only_if_fws(self):
1138n/a eq = self.ndiffAssertEqual
1139n/a msg = Message()
1140n/a msg['From'] = 'test@dom.ain'
1141n/a msg['References'] = ('thisverylongheaderhas;semicolons;and,commas,but'
1142n/a 'they;arenotlegal;fold,points')
1143n/a msg.set_payload('Test')
1144n/a sfp = StringIO()
1145n/a g = Generator(sfp)
1146n/a g.flatten(msg)
1147n/a # XXX the space after the header should not be there.
1148n/a eq(sfp.getvalue(), """\
1149n/aFrom: test@dom.ain
1150n/aReferences:\x20
1151n/a thisverylongheaderhas;semicolons;and,commas,butthey;arenotlegal;fold,points
1152n/a
1153n/aTest""")
1154n/a
1155n/a def test_no_split_long_header(self):
1156n/a eq = self.ndiffAssertEqual
1157n/a hstr = 'References: ' + 'x' * 80
1158n/a h = Header(hstr)
1159n/a # These come on two lines because Headers are really field value
1160n/a # classes and don't really know about their field names.
1161n/a eq(h.encode(), """\
1162n/aReferences:
1163n/a xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
1164n/a h = Header('x' * 80)
1165n/a eq(h.encode(), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')
1166n/a
1167n/a def test_splitting_multiple_long_lines(self):
1168n/a eq = self.ndiffAssertEqual
1169n/a hstr = """\
1170n/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)
1171n/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)
1172n/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)
1173n/a"""
1174n/a h = Header(hstr, continuation_ws='\t')
1175n/a eq(h.encode(), """\
1176n/afrom babylon.socal-raves.org (localhost [127.0.0.1]);
1177n/a by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
1178n/a for <mailman-admin@babylon.socal-raves.org>;
1179n/a Sat, 2 Feb 2002 17:00:06 -0800 (PST)
1180n/a\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
1181n/a by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
1182n/a for <mailman-admin@babylon.socal-raves.org>;
1183n/a Sat, 2 Feb 2002 17:00:06 -0800 (PST)
1184n/a\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
1185n/a by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
1186n/a for <mailman-admin@babylon.socal-raves.org>;
1187n/a Sat, 2 Feb 2002 17:00:06 -0800 (PST)""")
1188n/a
1189n/a def test_splitting_first_line_only_is_long(self):
1190n/a eq = self.ndiffAssertEqual
1191n/a hstr = """\
1192n/afrom modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93] helo=cthulhu.gerg.ca)
1193n/a\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
1194n/a\tid 17k4h5-00034i-00
1195n/a\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400"""
1196n/a h = Header(hstr, maxlinelen=78, header_name='Received',
1197n/a continuation_ws='\t')
1198n/a eq(h.encode(), """\
1199n/afrom modemcable093.139-201-24.que.mc.videotron.ca ([24.201.139.93]
1200n/a helo=cthulhu.gerg.ca)
1201n/a\tby kronos.mems-exchange.org with esmtp (Exim 4.05)
1202n/a\tid 17k4h5-00034i-00
1203n/a\tfor test@mems-exchange.org; Wed, 28 Aug 2002 11:25:20 -0400""")
1204n/a
1205n/a def test_long_8bit_header(self):
1206n/a eq = self.ndiffAssertEqual
1207n/a msg = Message()
1208n/a h = Header('Britische Regierung gibt', 'iso-8859-1',
1209n/a header_name='Subject')
1210n/a h.append('gr\xfcnes Licht f\xfcr Offshore-Windkraftprojekte')
1211n/a eq(h.encode(maxlinelen=76), """\
1212n/a=?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
1213n/a =?iso-8859-1?q?hore-Windkraftprojekte?=""")
1214n/a msg['Subject'] = h
1215n/a eq(msg.as_string(maxheaderlen=76), """\
1216n/aSubject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offs?=
1217n/a =?iso-8859-1?q?hore-Windkraftprojekte?=
1218n/a
1219n/a""")
1220n/a eq(msg.as_string(maxheaderlen=0), """\
1221n/aSubject: =?iso-8859-1?q?Britische_Regierung_gibt_gr=FCnes_Licht_f=FCr_Offshore-Windkraftprojekte?=
1222n/a
1223n/a""")
1224n/a
1225n/a def test_long_8bit_header_no_charset(self):
1226n/a eq = self.ndiffAssertEqual
1227n/a msg = Message()
1228n/a header_string = ('Britische Regierung gibt gr\xfcnes Licht '
1229n/a 'f\xfcr Offshore-Windkraftprojekte '
1230n/a '<a-very-long-address@example.com>')
1231n/a msg['Reply-To'] = header_string
1232n/a eq(msg.as_string(maxheaderlen=78), """\
1233n/aReply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?=
1234n/a =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?=
1235n/a
1236n/a""")
1237n/a msg = Message()
1238n/a msg['Reply-To'] = Header(header_string,
1239n/a header_name='Reply-To')
1240n/a eq(msg.as_string(maxheaderlen=78), """\
1241n/aReply-To: =?utf-8?q?Britische_Regierung_gibt_gr=C3=BCnes_Licht_f=C3=BCr_Offs?=
1242n/a =?utf-8?q?hore-Windkraftprojekte_=3Ca-very-long-address=40example=2Ecom=3E?=
1243n/a
1244n/a""")
1245n/a
1246n/a def test_long_to_header(self):
1247n/a eq = self.ndiffAssertEqual
1248n/a to = ('"Someone Test #A" <someone@eecs.umich.edu>,'
1249n/a '<someone@eecs.umich.edu>, '
1250n/a '"Someone Test #B" <someone@umich.edu>, '
1251n/a '"Someone Test #C" <someone@eecs.umich.edu>, '
1252n/a '"Someone Test #D" <someone@eecs.umich.edu>')
1253n/a msg = Message()
1254n/a msg['To'] = to
1255n/a eq(msg.as_string(maxheaderlen=78), '''\
1256n/aTo: "Someone Test #A" <someone@eecs.umich.edu>,<someone@eecs.umich.edu>,
1257n/a "Someone Test #B" <someone@umich.edu>,
1258n/a "Someone Test #C" <someone@eecs.umich.edu>,
1259n/a "Someone Test #D" <someone@eecs.umich.edu>
1260n/a
1261n/a''')
1262n/a
1263n/a def test_long_line_after_append(self):
1264n/a eq = self.ndiffAssertEqual
1265n/a s = 'This is an example of string which has almost the limit of header length.'
1266n/a h = Header(s)
1267n/a h.append('Add another line.')
1268n/a eq(h.encode(maxlinelen=76), """\
1269n/aThis is an example of string which has almost the limit of header length.
1270n/a Add another line.""")
1271n/a
1272n/a def test_shorter_line_with_append(self):
1273n/a eq = self.ndiffAssertEqual
1274n/a s = 'This is a shorter line.'
1275n/a h = Header(s)
1276n/a h.append('Add another sentence. (Surprise?)')
1277n/a eq(h.encode(),
1278n/a 'This is a shorter line. Add another sentence. (Surprise?)')
1279n/a
1280n/a def test_long_field_name(self):
1281n/a eq = self.ndiffAssertEqual
1282n/a fn = 'X-Very-Very-Very-Long-Header-Name'
1283n/a gs = ('Die Mieter treten hier ein werden mit einem Foerderband '
1284n/a 'komfortabel den Korridor entlang, an s\xfcdl\xfcndischen '
1285n/a 'Wandgem\xe4lden vorbei, gegen die rotierenden Klingen '
1286n/a 'bef\xf6rdert. ')
1287n/a h = Header(gs, 'iso-8859-1', header_name=fn)
1288n/a # BAW: this seems broken because the first line is too long
1289n/a eq(h.encode(maxlinelen=76), """\
1290n/a=?iso-8859-1?q?Die_Mieter_treten_hier_e?=
1291n/a =?iso-8859-1?q?in_werden_mit_einem_Foerderband_komfortabel_den_Korridor_e?=
1292n/a =?iso-8859-1?q?ntlang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei=2C_ge?=
1293n/a =?iso-8859-1?q?gen_die_rotierenden_Klingen_bef=F6rdert=2E_?=""")
1294n/a
1295n/a def test_long_received_header(self):
1296n/a h = ('from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) '
1297n/a 'by hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP; '
1298n/a 'Wed, 05 Mar 2003 18:10:18 -0700')
1299n/a msg = Message()
1300n/a msg['Received-1'] = Header(h, continuation_ws='\t')
1301n/a msg['Received-2'] = h
1302n/a # This should be splitting on spaces not semicolons.
1303n/a self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
1304n/aReceived-1: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
1305n/a hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
1306n/a Wed, 05 Mar 2003 18:10:18 -0700
1307n/aReceived-2: from FOO.TLD (vizworld.acl.foo.tld [123.452.678.9]) by
1308n/a hrothgar.la.mastaler.com (tmda-ofmipd) with ESMTP;
1309n/a Wed, 05 Mar 2003 18:10:18 -0700
1310n/a
1311n/a""")
1312n/a
1313n/a def test_string_headerinst_eq(self):
1314n/a h = ('<15975.17901.207240.414604@sgigritzmann1.mathematik.'
1315n/a 'tu-muenchen.de> (David Bremner\'s message of '
1316n/a '"Thu, 6 Mar 2003 13:58:21 +0100")')
1317n/a msg = Message()
1318n/a msg['Received-1'] = Header(h, header_name='Received-1',
1319n/a continuation_ws='\t')
1320n/a msg['Received-2'] = h
1321n/a # XXX The space after the ':' should not be there.
1322n/a self.ndiffAssertEqual(msg.as_string(maxheaderlen=78), """\
1323n/aReceived-1:\x20
1324n/a <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David
1325n/a Bremner's message of \"Thu, 6 Mar 2003 13:58:21 +0100\")
1326n/aReceived-2:\x20
1327n/a <15975.17901.207240.414604@sgigritzmann1.mathematik.tu-muenchen.de> (David
1328n/a Bremner's message of \"Thu, 6 Mar 2003 13:58:21 +0100\")
1329n/a
1330n/a""")
1331n/a
1332n/a def test_long_unbreakable_lines_with_continuation(self):
1333n/a eq = self.ndiffAssertEqual
1334n/a msg = Message()
1335n/a t = """\
1336n/aiVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
1337n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp"""
1338n/a msg['Face-1'] = t
1339n/a msg['Face-2'] = Header(t, header_name='Face-2')
1340n/a msg['Face-3'] = ' ' + t
1341n/a # XXX This splitting is all wrong. It the first value line should be
1342n/a # snug against the field name or the space after the header not there.
1343n/a eq(msg.as_string(maxheaderlen=78), """\
1344n/aFace-1:\x20
1345n/a iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
1346n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
1347n/aFace-2:\x20
1348n/a iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
1349n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
1350n/aFace-3:\x20
1351n/a iVBORw0KGgoAAAANSUhEUgAAADAAAAAwBAMAAAClLOS0AAAAGFBMVEUAAAAkHiJeRUIcGBi9
1352n/a locQDQ4zJykFBAXJfWDjAAACYUlEQVR4nF2TQY/jIAyFc6lydlG5x8Nyp1Y69wj1PN2I5gzp
1353n/a
1354n/a""")
1355n/a
1356n/a def test_another_long_multiline_header(self):
1357n/a eq = self.ndiffAssertEqual
1358n/a m = ('Received: from siimage.com '
1359n/a '([172.25.1.3]) by zima.siliconimage.com with '
1360n/a 'Microsoft SMTPSVC(5.0.2195.4905); '
1361n/a 'Wed, 16 Oct 2002 07:41:11 -0700')
1362n/a msg = email.message_from_string(m)
1363n/a eq(msg.as_string(maxheaderlen=78), '''\
1364n/aReceived: from siimage.com ([172.25.1.3]) by zima.siliconimage.com with
1365n/a Microsoft SMTPSVC(5.0.2195.4905); Wed, 16 Oct 2002 07:41:11 -0700
1366n/a
1367n/a''')
1368n/a
1369n/a def test_long_lines_with_different_header(self):
1370n/a eq = self.ndiffAssertEqual
1371n/a h = ('List-Unsubscribe: '
1372n/a '<http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,'
1373n/a ' <mailto:spamassassin-talk-request@lists.sourceforge.net'
1374n/a '?subject=unsubscribe>')
1375n/a msg = Message()
1376n/a msg['List'] = h
1377n/a msg['List'] = Header(h, header_name='List')
1378n/a eq(msg.as_string(maxheaderlen=78), """\
1379n/aList: List-Unsubscribe:
1380n/a <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
1381n/a <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
1382n/aList: List-Unsubscribe:
1383n/a <http://lists.sourceforge.net/lists/listinfo/spamassassin-talk>,
1384n/a <mailto:spamassassin-talk-request@lists.sourceforge.net?subject=unsubscribe>
1385n/a
1386n/a""")
1387n/a
1388n/a def test_long_rfc2047_header_with_embedded_fws(self):
1389n/a h = Header(textwrap.dedent("""\
1390n/a We're going to pretend this header is in a non-ascii character set
1391n/a \tto see if line wrapping with encoded words and embedded
1392n/a folding white space works"""),
1393n/a charset='utf-8',
1394n/a header_name='Test')
1395n/a self.assertEqual(h.encode()+'\n', textwrap.dedent("""\
1396n/a =?utf-8?q?We=27re_going_to_pretend_this_header_is_in_a_non-ascii_chara?=
1397n/a =?utf-8?q?cter_set?=
1398n/a =?utf-8?q?_to_see_if_line_wrapping_with_encoded_words_and_embedded?=
1399n/a =?utf-8?q?_folding_white_space_works?=""")+'\n')
1400n/a
1401n/a
1402n/a
1403n/a# Test mangling of "From " lines in the body of a message
1404n/aclass TestFromMangling(unittest.TestCase):
1405n/a def setUp(self):
1406n/a self.msg = Message()
1407n/a self.msg['From'] = 'aaa@bbb.org'
1408n/a self.msg.set_payload("""\
1409n/aFrom the desk of A.A.A.:
1410n/aBlah blah blah
1411n/a""")
1412n/a
1413n/a def test_mangled_from(self):
1414n/a s = StringIO()
1415n/a g = Generator(s, mangle_from_=True)
1416n/a g.flatten(self.msg)
1417n/a self.assertEqual(s.getvalue(), """\
1418n/aFrom: aaa@bbb.org
1419n/a
1420n/a>From the desk of A.A.A.:
1421n/aBlah blah blah
1422n/a""")
1423n/a
1424n/a def test_dont_mangle_from(self):
1425n/a s = StringIO()
1426n/a g = Generator(s, mangle_from_=False)
1427n/a g.flatten(self.msg)
1428n/a self.assertEqual(s.getvalue(), """\
1429n/aFrom: aaa@bbb.org
1430n/a
1431n/aFrom the desk of A.A.A.:
1432n/aBlah blah blah
1433n/a""")
1434n/a
1435n/a def test_mangle_from_in_preamble_and_epilog(self):
1436n/a s = StringIO()
1437n/a g = Generator(s, mangle_from_=True)
1438n/a msg = email.message_from_string(textwrap.dedent("""\
1439n/a From: foo@bar.com
1440n/a Mime-Version: 1.0
1441n/a Content-Type: multipart/mixed; boundary=XXX
1442n/a
1443n/a From somewhere unknown
1444n/a
1445n/a --XXX
1446n/a Content-Type: text/plain
1447n/a
1448n/a foo
1449n/a
1450n/a --XXX--
1451n/a
1452n/a From somewhere unknowable
1453n/a """))
1454n/a g.flatten(msg)
1455n/a self.assertEqual(len([1 for x in s.getvalue().split('\n')
1456n/a if x.startswith('>From ')]), 2)
1457n/a
1458n/a def test_mangled_from_with_bad_bytes(self):
1459n/a source = textwrap.dedent("""\
1460n/a Content-Type: text/plain; charset="utf-8"
1461n/a MIME-Version: 1.0
1462n/a Content-Transfer-Encoding: 8bit
1463n/a From: aaa@bbb.org
1464n/a
1465n/a """).encode('utf-8')
1466n/a msg = email.message_from_bytes(source + b'From R\xc3\xb6lli\n')
1467n/a b = BytesIO()
1468n/a g = BytesGenerator(b, mangle_from_=True)
1469n/a g.flatten(msg)
1470n/a self.assertEqual(b.getvalue(), source + b'>From R\xc3\xb6lli\n')
1471n/a
1472n/a
1473n/a# Test the basic MIMEAudio class
1474n/aclass TestMIMEAudio(unittest.TestCase):
1475n/a def setUp(self):
1476n/a with openfile('audiotest.au', 'rb') as fp:
1477n/a self._audiodata = fp.read()
1478n/a self._au = MIMEAudio(self._audiodata)
1479n/a
1480n/a def test_guess_minor_type(self):
1481n/a self.assertEqual(self._au.get_content_type(), 'audio/basic')
1482n/a
1483n/a def test_encoding(self):
1484n/a payload = self._au.get_payload()
1485n/a self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
1486n/a self._audiodata)
1487n/a
1488n/a def test_checkSetMinor(self):
1489n/a au = MIMEAudio(self._audiodata, 'fish')
1490n/a self.assertEqual(au.get_content_type(), 'audio/fish')
1491n/a
1492n/a def test_add_header(self):
1493n/a eq = self.assertEqual
1494n/a self._au.add_header('Content-Disposition', 'attachment',
1495n/a filename='audiotest.au')
1496n/a eq(self._au['content-disposition'],
1497n/a 'attachment; filename="audiotest.au"')
1498n/a eq(self._au.get_params(header='content-disposition'),
1499n/a [('attachment', ''), ('filename', 'audiotest.au')])
1500n/a eq(self._au.get_param('filename', header='content-disposition'),
1501n/a 'audiotest.au')
1502n/a missing = []
1503n/a eq(self._au.get_param('attachment', header='content-disposition'), '')
1504n/a self.assertIs(self._au.get_param('foo', failobj=missing,
1505n/a header='content-disposition'), missing)
1506n/a # Try some missing stuff
1507n/a self.assertIs(self._au.get_param('foobar', missing), missing)
1508n/a self.assertIs(self._au.get_param('attachment', missing,
1509n/a header='foobar'), missing)
1510n/a
1511n/a
1512n/a
1513n/a# Test the basic MIMEImage class
1514n/aclass TestMIMEImage(unittest.TestCase):
1515n/a def setUp(self):
1516n/a with openfile('PyBanner048.gif', 'rb') as fp:
1517n/a self._imgdata = fp.read()
1518n/a self._im = MIMEImage(self._imgdata)
1519n/a
1520n/a def test_guess_minor_type(self):
1521n/a self.assertEqual(self._im.get_content_type(), 'image/gif')
1522n/a
1523n/a def test_encoding(self):
1524n/a payload = self._im.get_payload()
1525n/a self.assertEqual(base64.decodebytes(bytes(payload, 'ascii')),
1526n/a self._imgdata)
1527n/a
1528n/a def test_checkSetMinor(self):
1529n/a im = MIMEImage(self._imgdata, 'fish')
1530n/a self.assertEqual(im.get_content_type(), 'image/fish')
1531n/a
1532n/a def test_add_header(self):
1533n/a eq = self.assertEqual
1534n/a self._im.add_header('Content-Disposition', 'attachment',
1535n/a filename='dingusfish.gif')
1536n/a eq(self._im['content-disposition'],
1537n/a 'attachment; filename="dingusfish.gif"')
1538n/a eq(self._im.get_params(header='content-disposition'),
1539n/a [('attachment', ''), ('filename', 'dingusfish.gif')])
1540n/a eq(self._im.get_param('filename', header='content-disposition'),
1541n/a 'dingusfish.gif')
1542n/a missing = []
1543n/a eq(self._im.get_param('attachment', header='content-disposition'), '')
1544n/a self.assertIs(self._im.get_param('foo', failobj=missing,
1545n/a header='content-disposition'), missing)
1546n/a # Try some missing stuff
1547n/a self.assertIs(self._im.get_param('foobar', missing), missing)
1548n/a self.assertIs(self._im.get_param('attachment', missing,
1549n/a header='foobar'), missing)
1550n/a
1551n/a
1552n/a
1553n/a# Test the basic MIMEApplication class
1554n/aclass TestMIMEApplication(unittest.TestCase):
1555n/a def test_headers(self):
1556n/a eq = self.assertEqual
1557n/a msg = MIMEApplication(b'\xfa\xfb\xfc\xfd\xfe\xff')
1558n/a eq(msg.get_content_type(), 'application/octet-stream')
1559n/a eq(msg['content-transfer-encoding'], 'base64')
1560n/a
1561n/a def test_body(self):
1562n/a eq = self.assertEqual
1563n/a bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff'
1564n/a msg = MIMEApplication(bytesdata)
1565n/a # whitespace in the cte encoded block is RFC-irrelevant.
1566n/a eq(msg.get_payload().strip(), '+vv8/f7/')
1567n/a eq(msg.get_payload(decode=True), bytesdata)
1568n/a
1569n/a def test_binary_body_with_encode_7or8bit(self):
1570n/a # Issue 17171.
1571n/a bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff'
1572n/a msg = MIMEApplication(bytesdata, _encoder=encoders.encode_7or8bit)
1573n/a # Treated as a string, this will be invalid code points.
1574n/a self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata))
1575n/a self.assertEqual(msg.get_payload(decode=True), bytesdata)
1576n/a self.assertEqual(msg['Content-Transfer-Encoding'], '8bit')
1577n/a s = BytesIO()
1578n/a g = BytesGenerator(s)
1579n/a g.flatten(msg)
1580n/a wireform = s.getvalue()
1581n/a msg2 = email.message_from_bytes(wireform)
1582n/a self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata))
1583n/a self.assertEqual(msg2.get_payload(decode=True), bytesdata)
1584n/a self.assertEqual(msg2['Content-Transfer-Encoding'], '8bit')
1585n/a
1586n/a def test_binary_body_with_encode_noop(self):
1587n/a # Issue 16564: This does not produce an RFC valid message, since to be
1588n/a # valid it should have a CTE of binary. But the below works in
1589n/a # Python2, and is documented as working this way.
1590n/a bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff'
1591n/a msg = MIMEApplication(bytesdata, _encoder=encoders.encode_noop)
1592n/a # Treated as a string, this will be invalid code points.
1593n/a self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata))
1594n/a self.assertEqual(msg.get_payload(decode=True), bytesdata)
1595n/a s = BytesIO()
1596n/a g = BytesGenerator(s)
1597n/a g.flatten(msg)
1598n/a wireform = s.getvalue()
1599n/a msg2 = email.message_from_bytes(wireform)
1600n/a self.assertEqual(msg.get_payload(), '\uFFFD' * len(bytesdata))
1601n/a self.assertEqual(msg2.get_payload(decode=True), bytesdata)
1602n/a
1603n/a def test_binary_body_with_unicode_linend_encode_noop(self):
1604n/a # Issue 19003: This is a variation on #16564.
1605n/a bytesdata = b'\x0b\xfa\xfb\xfc\xfd\xfe\xff'
1606n/a msg = MIMEApplication(bytesdata, _encoder=encoders.encode_noop)
1607n/a self.assertEqual(msg.get_payload(decode=True), bytesdata)
1608n/a s = BytesIO()
1609n/a g = BytesGenerator(s)
1610n/a g.flatten(msg)
1611n/a wireform = s.getvalue()
1612n/a msg2 = email.message_from_bytes(wireform)
1613n/a self.assertEqual(msg2.get_payload(decode=True), bytesdata)
1614n/a
1615n/a def test_binary_body_with_encode_quopri(self):
1616n/a # Issue 14360.
1617n/a bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff '
1618n/a msg = MIMEApplication(bytesdata, _encoder=encoders.encode_quopri)
1619n/a self.assertEqual(msg.get_payload(), '=FA=FB=FC=FD=FE=FF=20')
1620n/a self.assertEqual(msg.get_payload(decode=True), bytesdata)
1621n/a self.assertEqual(msg['Content-Transfer-Encoding'], 'quoted-printable')
1622n/a s = BytesIO()
1623n/a g = BytesGenerator(s)
1624n/a g.flatten(msg)
1625n/a wireform = s.getvalue()
1626n/a msg2 = email.message_from_bytes(wireform)
1627n/a self.assertEqual(msg.get_payload(), '=FA=FB=FC=FD=FE=FF=20')
1628n/a self.assertEqual(msg2.get_payload(decode=True), bytesdata)
1629n/a self.assertEqual(msg2['Content-Transfer-Encoding'], 'quoted-printable')
1630n/a
1631n/a def test_binary_body_with_encode_base64(self):
1632n/a bytesdata = b'\xfa\xfb\xfc\xfd\xfe\xff'
1633n/a msg = MIMEApplication(bytesdata, _encoder=encoders.encode_base64)
1634n/a self.assertEqual(msg.get_payload(), '+vv8/f7/\n')
1635n/a self.assertEqual(msg.get_payload(decode=True), bytesdata)
1636n/a s = BytesIO()
1637n/a g = BytesGenerator(s)
1638n/a g.flatten(msg)
1639n/a wireform = s.getvalue()
1640n/a msg2 = email.message_from_bytes(wireform)
1641n/a self.assertEqual(msg.get_payload(), '+vv8/f7/\n')
1642n/a self.assertEqual(msg2.get_payload(decode=True), bytesdata)
1643n/a
1644n/a
1645n/a# Test the basic MIMEText class
1646n/aclass TestMIMEText(unittest.TestCase):
1647n/a def setUp(self):
1648n/a self._msg = MIMEText('hello there')
1649n/a
1650n/a def test_types(self):
1651n/a eq = self.assertEqual
1652n/a eq(self._msg.get_content_type(), 'text/plain')
1653n/a eq(self._msg.get_param('charset'), 'us-ascii')
1654n/a missing = []
1655n/a self.assertIs(self._msg.get_param('foobar', missing), missing)
1656n/a self.assertIs(self._msg.get_param('charset', missing, header='foobar'),
1657n/a missing)
1658n/a
1659n/a def test_payload(self):
1660n/a self.assertEqual(self._msg.get_payload(), 'hello there')
1661n/a self.assertFalse(self._msg.is_multipart())
1662n/a
1663n/a def test_charset(self):
1664n/a eq = self.assertEqual
1665n/a msg = MIMEText('hello there', _charset='us-ascii')
1666n/a eq(msg.get_charset().input_charset, 'us-ascii')
1667n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1668n/a # Also accept a Charset instance
1669n/a charset = Charset('utf-8')
1670n/a charset.body_encoding = None
1671n/a msg = MIMEText('hello there', _charset=charset)
1672n/a eq(msg.get_charset().input_charset, 'utf-8')
1673n/a eq(msg['content-type'], 'text/plain; charset="utf-8"')
1674n/a eq(msg.get_payload(), 'hello there')
1675n/a
1676n/a def test_7bit_input(self):
1677n/a eq = self.assertEqual
1678n/a msg = MIMEText('hello there', _charset='us-ascii')
1679n/a eq(msg.get_charset().input_charset, 'us-ascii')
1680n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1681n/a
1682n/a def test_7bit_input_no_charset(self):
1683n/a eq = self.assertEqual
1684n/a msg = MIMEText('hello there')
1685n/a eq(msg.get_charset(), 'us-ascii')
1686n/a eq(msg['content-type'], 'text/plain; charset="us-ascii"')
1687n/a self.assertIn('hello there', msg.as_string())
1688n/a
1689n/a def test_utf8_input(self):
1690n/a teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1691n/a eq = self.assertEqual
1692n/a msg = MIMEText(teststr, _charset='utf-8')
1693n/a eq(msg.get_charset().output_charset, 'utf-8')
1694n/a eq(msg['content-type'], 'text/plain; charset="utf-8"')
1695n/a eq(msg.get_payload(decode=True), teststr.encode('utf-8'))
1696n/a
1697n/a @unittest.skip("can't fix because of backward compat in email5, "
1698n/a "will fix in email6")
1699n/a def test_utf8_input_no_charset(self):
1700n/a teststr = '\u043a\u0438\u0440\u0438\u043b\u0438\u0446\u0430'
1701n/a self.assertRaises(UnicodeEncodeError, MIMEText, teststr)
1702n/a
1703n/a
1704n/a
1705n/a# Test complicated multipart/* messages
1706n/aclass TestMultipart(TestEmailBase):
1707n/a def setUp(self):
1708n/a with openfile('PyBanner048.gif', 'rb') as fp:
1709n/a data = fp.read()
1710n/a container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
1711n/a image = MIMEImage(data, name='dingusfish.gif')
1712n/a image.add_header('content-disposition', 'attachment',
1713n/a filename='dingusfish.gif')
1714n/a intro = MIMEText('''\
1715n/aHi there,
1716n/a
1717n/aThis is the dingus fish.
1718n/a''')
1719n/a container.attach(intro)
1720n/a container.attach(image)
1721n/a container['From'] = 'Barry <barry@digicool.com>'
1722n/a container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
1723n/a container['Subject'] = 'Here is your dingus fish'
1724n/a
1725n/a now = 987809702.54848599
1726n/a timetuple = time.localtime(now)
1727n/a if timetuple[-1] == 0:
1728n/a tzsecs = time.timezone
1729n/a else:
1730n/a tzsecs = time.altzone
1731n/a if tzsecs > 0:
1732n/a sign = '-'
1733n/a else:
1734n/a sign = '+'
1735n/a tzoffset = ' %s%04d' % (sign, tzsecs / 36)
1736n/a container['Date'] = time.strftime(
1737n/a '%a, %d %b %Y %H:%M:%S',
1738n/a time.localtime(now)) + tzoffset
1739n/a self._msg = container
1740n/a self._im = image
1741n/a self._txt = intro
1742n/a
1743n/a def test_hierarchy(self):
1744n/a # convenience
1745n/a eq = self.assertEqual
1746n/a raises = self.assertRaises
1747n/a # tests
1748n/a m = self._msg
1749n/a self.assertTrue(m.is_multipart())
1750n/a eq(m.get_content_type(), 'multipart/mixed')
1751n/a eq(len(m.get_payload()), 2)
1752n/a raises(IndexError, m.get_payload, 2)
1753n/a m0 = m.get_payload(0)
1754n/a m1 = m.get_payload(1)
1755n/a self.assertIs(m0, self._txt)
1756n/a self.assertIs(m1, self._im)
1757n/a eq(m.get_payload(), [m0, m1])
1758n/a self.assertFalse(m0.is_multipart())
1759n/a self.assertFalse(m1.is_multipart())
1760n/a
1761n/a def test_empty_multipart_idempotent(self):
1762n/a text = """\
1763n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1764n/aMIME-Version: 1.0
1765n/aSubject: A subject
1766n/aTo: aperson@dom.ain
1767n/aFrom: bperson@dom.ain
1768n/a
1769n/a
1770n/a--BOUNDARY
1771n/a
1772n/a
1773n/a--BOUNDARY--
1774n/a"""
1775n/a msg = Parser().parsestr(text)
1776n/a self.ndiffAssertEqual(text, msg.as_string())
1777n/a
1778n/a def test_no_parts_in_a_multipart_with_none_epilogue(self):
1779n/a outer = MIMEBase('multipart', 'mixed')
1780n/a outer['Subject'] = 'A subject'
1781n/a outer['To'] = 'aperson@dom.ain'
1782n/a outer['From'] = 'bperson@dom.ain'
1783n/a outer.set_boundary('BOUNDARY')
1784n/a self.ndiffAssertEqual(outer.as_string(), '''\
1785n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1786n/aMIME-Version: 1.0
1787n/aSubject: A subject
1788n/aTo: aperson@dom.ain
1789n/aFrom: bperson@dom.ain
1790n/a
1791n/a--BOUNDARY
1792n/a
1793n/a--BOUNDARY--
1794n/a''')
1795n/a
1796n/a def test_no_parts_in_a_multipart_with_empty_epilogue(self):
1797n/a outer = MIMEBase('multipart', 'mixed')
1798n/a outer['Subject'] = 'A subject'
1799n/a outer['To'] = 'aperson@dom.ain'
1800n/a outer['From'] = 'bperson@dom.ain'
1801n/a outer.preamble = ''
1802n/a outer.epilogue = ''
1803n/a outer.set_boundary('BOUNDARY')
1804n/a self.ndiffAssertEqual(outer.as_string(), '''\
1805n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1806n/aMIME-Version: 1.0
1807n/aSubject: A subject
1808n/aTo: aperson@dom.ain
1809n/aFrom: bperson@dom.ain
1810n/a
1811n/a
1812n/a--BOUNDARY
1813n/a
1814n/a--BOUNDARY--
1815n/a''')
1816n/a
1817n/a def test_one_part_in_a_multipart(self):
1818n/a eq = self.ndiffAssertEqual
1819n/a outer = MIMEBase('multipart', 'mixed')
1820n/a outer['Subject'] = 'A subject'
1821n/a outer['To'] = 'aperson@dom.ain'
1822n/a outer['From'] = 'bperson@dom.ain'
1823n/a outer.set_boundary('BOUNDARY')
1824n/a msg = MIMEText('hello world')
1825n/a outer.attach(msg)
1826n/a eq(outer.as_string(), '''\
1827n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1828n/aMIME-Version: 1.0
1829n/aSubject: A subject
1830n/aTo: aperson@dom.ain
1831n/aFrom: bperson@dom.ain
1832n/a
1833n/a--BOUNDARY
1834n/aContent-Type: text/plain; charset="us-ascii"
1835n/aMIME-Version: 1.0
1836n/aContent-Transfer-Encoding: 7bit
1837n/a
1838n/ahello world
1839n/a--BOUNDARY--
1840n/a''')
1841n/a
1842n/a def test_seq_parts_in_a_multipart_with_empty_preamble(self):
1843n/a eq = self.ndiffAssertEqual
1844n/a outer = MIMEBase('multipart', 'mixed')
1845n/a outer['Subject'] = 'A subject'
1846n/a outer['To'] = 'aperson@dom.ain'
1847n/a outer['From'] = 'bperson@dom.ain'
1848n/a outer.preamble = ''
1849n/a msg = MIMEText('hello world')
1850n/a outer.attach(msg)
1851n/a outer.set_boundary('BOUNDARY')
1852n/a eq(outer.as_string(), '''\
1853n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1854n/aMIME-Version: 1.0
1855n/aSubject: A subject
1856n/aTo: aperson@dom.ain
1857n/aFrom: bperson@dom.ain
1858n/a
1859n/a
1860n/a--BOUNDARY
1861n/aContent-Type: text/plain; charset="us-ascii"
1862n/aMIME-Version: 1.0
1863n/aContent-Transfer-Encoding: 7bit
1864n/a
1865n/ahello world
1866n/a--BOUNDARY--
1867n/a''')
1868n/a
1869n/a
1870n/a def test_seq_parts_in_a_multipart_with_none_preamble(self):
1871n/a eq = self.ndiffAssertEqual
1872n/a outer = MIMEBase('multipart', 'mixed')
1873n/a outer['Subject'] = 'A subject'
1874n/a outer['To'] = 'aperson@dom.ain'
1875n/a outer['From'] = 'bperson@dom.ain'
1876n/a outer.preamble = None
1877n/a msg = MIMEText('hello world')
1878n/a outer.attach(msg)
1879n/a outer.set_boundary('BOUNDARY')
1880n/a eq(outer.as_string(), '''\
1881n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1882n/aMIME-Version: 1.0
1883n/aSubject: A subject
1884n/aTo: aperson@dom.ain
1885n/aFrom: bperson@dom.ain
1886n/a
1887n/a--BOUNDARY
1888n/aContent-Type: text/plain; charset="us-ascii"
1889n/aMIME-Version: 1.0
1890n/aContent-Transfer-Encoding: 7bit
1891n/a
1892n/ahello world
1893n/a--BOUNDARY--
1894n/a''')
1895n/a
1896n/a
1897n/a def test_seq_parts_in_a_multipart_with_none_epilogue(self):
1898n/a eq = self.ndiffAssertEqual
1899n/a outer = MIMEBase('multipart', 'mixed')
1900n/a outer['Subject'] = 'A subject'
1901n/a outer['To'] = 'aperson@dom.ain'
1902n/a outer['From'] = 'bperson@dom.ain'
1903n/a outer.epilogue = None
1904n/a msg = MIMEText('hello world')
1905n/a outer.attach(msg)
1906n/a outer.set_boundary('BOUNDARY')
1907n/a eq(outer.as_string(), '''\
1908n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1909n/aMIME-Version: 1.0
1910n/aSubject: A subject
1911n/aTo: aperson@dom.ain
1912n/aFrom: bperson@dom.ain
1913n/a
1914n/a--BOUNDARY
1915n/aContent-Type: text/plain; charset="us-ascii"
1916n/aMIME-Version: 1.0
1917n/aContent-Transfer-Encoding: 7bit
1918n/a
1919n/ahello world
1920n/a--BOUNDARY--
1921n/a''')
1922n/a
1923n/a
1924n/a def test_seq_parts_in_a_multipart_with_empty_epilogue(self):
1925n/a eq = self.ndiffAssertEqual
1926n/a outer = MIMEBase('multipart', 'mixed')
1927n/a outer['Subject'] = 'A subject'
1928n/a outer['To'] = 'aperson@dom.ain'
1929n/a outer['From'] = 'bperson@dom.ain'
1930n/a outer.epilogue = ''
1931n/a msg = MIMEText('hello world')
1932n/a outer.attach(msg)
1933n/a outer.set_boundary('BOUNDARY')
1934n/a eq(outer.as_string(), '''\
1935n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1936n/aMIME-Version: 1.0
1937n/aSubject: A subject
1938n/aTo: aperson@dom.ain
1939n/aFrom: bperson@dom.ain
1940n/a
1941n/a--BOUNDARY
1942n/aContent-Type: text/plain; charset="us-ascii"
1943n/aMIME-Version: 1.0
1944n/aContent-Transfer-Encoding: 7bit
1945n/a
1946n/ahello world
1947n/a--BOUNDARY--
1948n/a''')
1949n/a
1950n/a
1951n/a def test_seq_parts_in_a_multipart_with_nl_epilogue(self):
1952n/a eq = self.ndiffAssertEqual
1953n/a outer = MIMEBase('multipart', 'mixed')
1954n/a outer['Subject'] = 'A subject'
1955n/a outer['To'] = 'aperson@dom.ain'
1956n/a outer['From'] = 'bperson@dom.ain'
1957n/a outer.epilogue = '\n'
1958n/a msg = MIMEText('hello world')
1959n/a outer.attach(msg)
1960n/a outer.set_boundary('BOUNDARY')
1961n/a eq(outer.as_string(), '''\
1962n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
1963n/aMIME-Version: 1.0
1964n/aSubject: A subject
1965n/aTo: aperson@dom.ain
1966n/aFrom: bperson@dom.ain
1967n/a
1968n/a--BOUNDARY
1969n/aContent-Type: text/plain; charset="us-ascii"
1970n/aMIME-Version: 1.0
1971n/aContent-Transfer-Encoding: 7bit
1972n/a
1973n/ahello world
1974n/a--BOUNDARY--
1975n/a
1976n/a''')
1977n/a
1978n/a def test_message_external_body(self):
1979n/a eq = self.assertEqual
1980n/a msg = self._msgobj('msg_36.txt')
1981n/a eq(len(msg.get_payload()), 2)
1982n/a msg1 = msg.get_payload(1)
1983n/a eq(msg1.get_content_type(), 'multipart/alternative')
1984n/a eq(len(msg1.get_payload()), 2)
1985n/a for subpart in msg1.get_payload():
1986n/a eq(subpart.get_content_type(), 'message/external-body')
1987n/a eq(len(subpart.get_payload()), 1)
1988n/a subsubpart = subpart.get_payload(0)
1989n/a eq(subsubpart.get_content_type(), 'text/plain')
1990n/a
1991n/a def test_double_boundary(self):
1992n/a # msg_37.txt is a multipart that contains two dash-boundary's in a
1993n/a # row. Our interpretation of RFC 2046 calls for ignoring the second
1994n/a # and subsequent boundaries.
1995n/a msg = self._msgobj('msg_37.txt')
1996n/a self.assertEqual(len(msg.get_payload()), 3)
1997n/a
1998n/a def test_nested_inner_contains_outer_boundary(self):
1999n/a eq = self.ndiffAssertEqual
2000n/a # msg_38.txt has an inner part that contains outer boundaries. My
2001n/a # interpretation of RFC 2046 (based on sections 5.1 and 5.1.2) say
2002n/a # these are illegal and should be interpreted as unterminated inner
2003n/a # parts.
2004n/a msg = self._msgobj('msg_38.txt')
2005n/a sfp = StringIO()
2006n/a iterators._structure(msg, sfp)
2007n/a eq(sfp.getvalue(), """\
2008n/amultipart/mixed
2009n/a multipart/mixed
2010n/a multipart/alternative
2011n/a text/plain
2012n/a text/plain
2013n/a text/plain
2014n/a text/plain
2015n/a""")
2016n/a
2017n/a def test_nested_with_same_boundary(self):
2018n/a eq = self.ndiffAssertEqual
2019n/a # msg 39.txt is similarly evil in that it's got inner parts that use
2020n/a # the same boundary as outer parts. Again, I believe the way this is
2021n/a # parsed is closest to the spirit of RFC 2046
2022n/a msg = self._msgobj('msg_39.txt')
2023n/a sfp = StringIO()
2024n/a iterators._structure(msg, sfp)
2025n/a eq(sfp.getvalue(), """\
2026n/amultipart/mixed
2027n/a multipart/mixed
2028n/a multipart/alternative
2029n/a application/octet-stream
2030n/a application/octet-stream
2031n/a text/plain
2032n/a""")
2033n/a
2034n/a def test_boundary_in_non_multipart(self):
2035n/a msg = self._msgobj('msg_40.txt')
2036n/a self.assertEqual(msg.as_string(), '''\
2037n/aMIME-Version: 1.0
2038n/aContent-Type: text/html; boundary="--961284236552522269"
2039n/a
2040n/a----961284236552522269
2041n/aContent-Type: text/html;
2042n/aContent-Transfer-Encoding: 7Bit
2043n/a
2044n/a<html></html>
2045n/a
2046n/a----961284236552522269--
2047n/a''')
2048n/a
2049n/a def test_boundary_with_leading_space(self):
2050n/a eq = self.assertEqual
2051n/a msg = email.message_from_string('''\
2052n/aMIME-Version: 1.0
2053n/aContent-Type: multipart/mixed; boundary=" XXXX"
2054n/a
2055n/a-- XXXX
2056n/aContent-Type: text/plain
2057n/a
2058n/a
2059n/a-- XXXX
2060n/aContent-Type: text/plain
2061n/a
2062n/a-- XXXX--
2063n/a''')
2064n/a self.assertTrue(msg.is_multipart())
2065n/a eq(msg.get_boundary(), ' XXXX')
2066n/a eq(len(msg.get_payload()), 2)
2067n/a
2068n/a def test_boundary_without_trailing_newline(self):
2069n/a m = Parser().parsestr("""\
2070n/aContent-Type: multipart/mixed; boundary="===============0012394164=="
2071n/aMIME-Version: 1.0
2072n/a
2073n/a--===============0012394164==
2074n/aContent-Type: image/file1.jpg
2075n/aMIME-Version: 1.0
2076n/aContent-Transfer-Encoding: base64
2077n/a
2078n/aYXNkZg==
2079n/a--===============0012394164==--""")
2080n/a self.assertEqual(m.get_payload(0).get_payload(), 'YXNkZg==')
2081n/a
2082n/a def test_mimebase_default_policy(self):
2083n/a m = MIMEBase('multipart', 'mixed')
2084n/a self.assertIs(m.policy, email.policy.compat32)
2085n/a
2086n/a def test_mimebase_custom_policy(self):
2087n/a m = MIMEBase('multipart', 'mixed', policy=email.policy.default)
2088n/a self.assertIs(m.policy, email.policy.default)
2089n/a
2090n/a# Test some badly formatted messages
2091n/aclass TestNonConformant(TestEmailBase):
2092n/a
2093n/a def test_parse_missing_minor_type(self):
2094n/a eq = self.assertEqual
2095n/a msg = self._msgobj('msg_14.txt')
2096n/a eq(msg.get_content_type(), 'text/plain')
2097n/a eq(msg.get_content_maintype(), 'text')
2098n/a eq(msg.get_content_subtype(), 'plain')
2099n/a
2100n/a # test_defect_handling
2101n/a def test_same_boundary_inner_outer(self):
2102n/a msg = self._msgobj('msg_15.txt')
2103n/a # XXX We can probably eventually do better
2104n/a inner = msg.get_payload(0)
2105n/a self.assertTrue(hasattr(inner, 'defects'))
2106n/a self.assertEqual(len(inner.defects), 1)
2107n/a self.assertIsInstance(inner.defects[0],
2108n/a errors.StartBoundaryNotFoundDefect)
2109n/a
2110n/a # test_defect_handling
2111n/a def test_multipart_no_boundary(self):
2112n/a msg = self._msgobj('msg_25.txt')
2113n/a self.assertIsInstance(msg.get_payload(), str)
2114n/a self.assertEqual(len(msg.defects), 2)
2115n/a self.assertIsInstance(msg.defects[0],
2116n/a errors.NoBoundaryInMultipartDefect)
2117n/a self.assertIsInstance(msg.defects[1],
2118n/a errors.MultipartInvariantViolationDefect)
2119n/a
2120n/a multipart_msg = textwrap.dedent("""\
2121n/a Date: Wed, 14 Nov 2007 12:56:23 GMT
2122n/a From: foo@bar.invalid
2123n/a To: foo@bar.invalid
2124n/a Subject: Content-Transfer-Encoding: base64 and multipart
2125n/a MIME-Version: 1.0
2126n/a Content-Type: multipart/mixed;
2127n/a boundary="===============3344438784458119861=="{}
2128n/a
2129n/a --===============3344438784458119861==
2130n/a Content-Type: text/plain
2131n/a
2132n/a Test message
2133n/a
2134n/a --===============3344438784458119861==
2135n/a Content-Type: application/octet-stream
2136n/a Content-Transfer-Encoding: base64
2137n/a
2138n/a YWJj
2139n/a
2140n/a --===============3344438784458119861==--
2141n/a """)
2142n/a
2143n/a # test_defect_handling
2144n/a def test_multipart_invalid_cte(self):
2145n/a msg = self._str_msg(
2146n/a self.multipart_msg.format("\nContent-Transfer-Encoding: base64"))
2147n/a self.assertEqual(len(msg.defects), 1)
2148n/a self.assertIsInstance(msg.defects[0],
2149n/a errors.InvalidMultipartContentTransferEncodingDefect)
2150n/a
2151n/a # test_defect_handling
2152n/a def test_multipart_no_cte_no_defect(self):
2153n/a msg = self._str_msg(self.multipart_msg.format(''))
2154n/a self.assertEqual(len(msg.defects), 0)
2155n/a
2156n/a # test_defect_handling
2157n/a def test_multipart_valid_cte_no_defect(self):
2158n/a for cte in ('7bit', '8bit', 'BINary'):
2159n/a msg = self._str_msg(
2160n/a self.multipart_msg.format(
2161n/a "\nContent-Transfer-Encoding: {}".format(cte)))
2162n/a self.assertEqual(len(msg.defects), 0)
2163n/a
2164n/a # test_headerregistry.TestContentTyopeHeader invalid_1 and invalid_2.
2165n/a def test_invalid_content_type(self):
2166n/a eq = self.assertEqual
2167n/a neq = self.ndiffAssertEqual
2168n/a msg = Message()
2169n/a # RFC 2045, $5.2 says invalid yields text/plain
2170n/a msg['Content-Type'] = 'text'
2171n/a eq(msg.get_content_maintype(), 'text')
2172n/a eq(msg.get_content_subtype(), 'plain')
2173n/a eq(msg.get_content_type(), 'text/plain')
2174n/a # Clear the old value and try something /really/ invalid
2175n/a del msg['content-type']
2176n/a msg['Content-Type'] = 'foo'
2177n/a eq(msg.get_content_maintype(), 'text')
2178n/a eq(msg.get_content_subtype(), 'plain')
2179n/a eq(msg.get_content_type(), 'text/plain')
2180n/a # Still, make sure that the message is idempotently generated
2181n/a s = StringIO()
2182n/a g = Generator(s)
2183n/a g.flatten(msg)
2184n/a neq(s.getvalue(), 'Content-Type: foo\n\n')
2185n/a
2186n/a def test_no_start_boundary(self):
2187n/a eq = self.ndiffAssertEqual
2188n/a msg = self._msgobj('msg_31.txt')
2189n/a eq(msg.get_payload(), """\
2190n/a--BOUNDARY
2191n/aContent-Type: text/plain
2192n/a
2193n/amessage 1
2194n/a
2195n/a--BOUNDARY
2196n/aContent-Type: text/plain
2197n/a
2198n/amessage 2
2199n/a
2200n/a--BOUNDARY--
2201n/a""")
2202n/a
2203n/a def test_no_separating_blank_line(self):
2204n/a eq = self.ndiffAssertEqual
2205n/a msg = self._msgobj('msg_35.txt')
2206n/a eq(msg.as_string(), """\
2207n/aFrom: aperson@dom.ain
2208n/aTo: bperson@dom.ain
2209n/aSubject: here's something interesting
2210n/a
2211n/acounter to RFC 2822, there's no separating newline here
2212n/a""")
2213n/a
2214n/a # test_defect_handling
2215n/a def test_lying_multipart(self):
2216n/a msg = self._msgobj('msg_41.txt')
2217n/a self.assertTrue(hasattr(msg, 'defects'))
2218n/a self.assertEqual(len(msg.defects), 2)
2219n/a self.assertIsInstance(msg.defects[0],
2220n/a errors.NoBoundaryInMultipartDefect)
2221n/a self.assertIsInstance(msg.defects[1],
2222n/a errors.MultipartInvariantViolationDefect)
2223n/a
2224n/a # test_defect_handling
2225n/a def test_missing_start_boundary(self):
2226n/a outer = self._msgobj('msg_42.txt')
2227n/a # The message structure is:
2228n/a #
2229n/a # multipart/mixed
2230n/a # text/plain
2231n/a # message/rfc822
2232n/a # multipart/mixed [*]
2233n/a #
2234n/a # [*] This message is missing its start boundary
2235n/a bad = outer.get_payload(1).get_payload(0)
2236n/a self.assertEqual(len(bad.defects), 1)
2237n/a self.assertIsInstance(bad.defects[0],
2238n/a errors.StartBoundaryNotFoundDefect)
2239n/a
2240n/a # test_defect_handling
2241n/a def test_first_line_is_continuation_header(self):
2242n/a eq = self.assertEqual
2243n/a m = ' Line 1\nSubject: test\n\nbody'
2244n/a msg = email.message_from_string(m)
2245n/a eq(msg.keys(), ['Subject'])
2246n/a eq(msg.get_payload(), 'body')
2247n/a eq(len(msg.defects), 1)
2248n/a self.assertDefectsEqual(msg.defects,
2249n/a [errors.FirstHeaderLineIsContinuationDefect])
2250n/a eq(msg.defects[0].line, ' Line 1\n')
2251n/a
2252n/a # test_defect_handling
2253n/a def test_missing_header_body_separator(self):
2254n/a # Our heuristic if we see a line that doesn't look like a header (no
2255n/a # leading whitespace but no ':') is to assume that the blank line that
2256n/a # separates the header from the body is missing, and to stop parsing
2257n/a # headers and start parsing the body.
2258n/a msg = self._str_msg('Subject: test\nnot a header\nTo: abc\n\nb\n')
2259n/a self.assertEqual(msg.keys(), ['Subject'])
2260n/a self.assertEqual(msg.get_payload(), 'not a header\nTo: abc\n\nb\n')
2261n/a self.assertDefectsEqual(msg.defects,
2262n/a [errors.MissingHeaderBodySeparatorDefect])
2263n/a
2264n/a
2265n/a# Test RFC 2047 header encoding and decoding
2266n/aclass TestRFC2047(TestEmailBase):
2267n/a def test_rfc2047_multiline(self):
2268n/a eq = self.assertEqual
2269n/a s = """Re: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz
2270n/a foo bar =?mac-iceland?q?r=8Aksm=9Arg=8Cs?="""
2271n/a dh = decode_header(s)
2272n/a eq(dh, [
2273n/a (b'Re: ', None),
2274n/a (b'r\x8aksm\x9arg\x8cs', 'mac-iceland'),
2275n/a (b' baz foo bar ', None),
2276n/a (b'r\x8aksm\x9arg\x8cs', 'mac-iceland')])
2277n/a header = make_header(dh)
2278n/a eq(str(header),
2279n/a 'Re: r\xe4ksm\xf6rg\xe5s baz foo bar r\xe4ksm\xf6rg\xe5s')
2280n/a self.ndiffAssertEqual(header.encode(maxlinelen=76), """\
2281n/aRe: =?mac-iceland?q?r=8Aksm=9Arg=8Cs?= baz foo bar =?mac-iceland?q?r=8Aksm?=
2282n/a =?mac-iceland?q?=9Arg=8Cs?=""")
2283n/a
2284n/a def test_whitespace_keeper_unicode(self):
2285n/a eq = self.assertEqual
2286n/a s = '=?ISO-8859-1?Q?Andr=E9?= Pirard <pirard@dom.ain>'
2287n/a dh = decode_header(s)
2288n/a eq(dh, [(b'Andr\xe9', 'iso-8859-1'),
2289n/a (b' Pirard <pirard@dom.ain>', None)])
2290n/a header = str(make_header(dh))
2291n/a eq(header, 'Andr\xe9 Pirard <pirard@dom.ain>')
2292n/a
2293n/a def test_whitespace_keeper_unicode_2(self):
2294n/a eq = self.assertEqual
2295n/a s = 'The =?iso-8859-1?b?cXVpY2sgYnJvd24gZm94?= jumped over the =?iso-8859-1?b?bGF6eSBkb2c=?='
2296n/a dh = decode_header(s)
2297n/a eq(dh, [(b'The ', None), (b'quick brown fox', 'iso-8859-1'),
2298n/a (b' jumped over the ', None), (b'lazy dog', 'iso-8859-1')])
2299n/a hu = str(make_header(dh))
2300n/a eq(hu, 'The quick brown fox jumped over the lazy dog')
2301n/a
2302n/a def test_rfc2047_missing_whitespace(self):
2303n/a s = 'Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord'
2304n/a dh = decode_header(s)
2305n/a self.assertEqual(dh, [(b'Sm', None), (b'\xf6', 'iso-8859-1'),
2306n/a (b'rg', None), (b'\xe5', 'iso-8859-1'),
2307n/a (b'sbord', None)])
2308n/a
2309n/a def test_rfc2047_with_whitespace(self):
2310n/a s = 'Sm =?ISO-8859-1?B?9g==?= rg =?ISO-8859-1?B?5Q==?= sbord'
2311n/a dh = decode_header(s)
2312n/a self.assertEqual(dh, [(b'Sm ', None), (b'\xf6', 'iso-8859-1'),
2313n/a (b' rg ', None), (b'\xe5', 'iso-8859-1'),
2314n/a (b' sbord', None)])
2315n/a
2316n/a def test_rfc2047_B_bad_padding(self):
2317n/a s = '=?iso-8859-1?B?%s?='
2318n/a data = [ # only test complete bytes
2319n/a ('dm==', b'v'), ('dm=', b'v'), ('dm', b'v'),
2320n/a ('dmk=', b'vi'), ('dmk', b'vi')
2321n/a ]
2322n/a for q, a in data:
2323n/a dh = decode_header(s % q)
2324n/a self.assertEqual(dh, [(a, 'iso-8859-1')])
2325n/a
2326n/a def test_rfc2047_Q_invalid_digits(self):
2327n/a # issue 10004.
2328n/a s = '=?iso-8859-1?Q?andr=e9=zz?='
2329n/a self.assertEqual(decode_header(s),
2330n/a [(b'andr\xe9=zz', 'iso-8859-1')])
2331n/a
2332n/a def test_rfc2047_rfc2047_1(self):
2333n/a # 1st testcase at end of rfc2047
2334n/a s = '(=?ISO-8859-1?Q?a?=)'
2335n/a self.assertEqual(decode_header(s),
2336n/a [(b'(', None), (b'a', 'iso-8859-1'), (b')', None)])
2337n/a
2338n/a def test_rfc2047_rfc2047_2(self):
2339n/a # 2nd testcase at end of rfc2047
2340n/a s = '(=?ISO-8859-1?Q?a?= b)'
2341n/a self.assertEqual(decode_header(s),
2342n/a [(b'(', None), (b'a', 'iso-8859-1'), (b' b)', None)])
2343n/a
2344n/a def test_rfc2047_rfc2047_3(self):
2345n/a # 3rd testcase at end of rfc2047
2346n/a s = '(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)'
2347n/a self.assertEqual(decode_header(s),
2348n/a [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)])
2349n/a
2350n/a def test_rfc2047_rfc2047_4(self):
2351n/a # 4th testcase at end of rfc2047
2352n/a s = '(=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=)'
2353n/a self.assertEqual(decode_header(s),
2354n/a [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)])
2355n/a
2356n/a def test_rfc2047_rfc2047_5a(self):
2357n/a # 5th testcase at end of rfc2047 newline is \r\n
2358n/a s = '(=?ISO-8859-1?Q?a?=\r\n =?ISO-8859-1?Q?b?=)'
2359n/a self.assertEqual(decode_header(s),
2360n/a [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)])
2361n/a
2362n/a def test_rfc2047_rfc2047_5b(self):
2363n/a # 5th testcase at end of rfc2047 newline is \n
2364n/a s = '(=?ISO-8859-1?Q?a?=\n =?ISO-8859-1?Q?b?=)'
2365n/a self.assertEqual(decode_header(s),
2366n/a [(b'(', None), (b'ab', 'iso-8859-1'), (b')', None)])
2367n/a
2368n/a def test_rfc2047_rfc2047_6(self):
2369n/a # 6th testcase at end of rfc2047
2370n/a s = '(=?ISO-8859-1?Q?a_b?=)'
2371n/a self.assertEqual(decode_header(s),
2372n/a [(b'(', None), (b'a b', 'iso-8859-1'), (b')', None)])
2373n/a
2374n/a def test_rfc2047_rfc2047_7(self):
2375n/a # 7th testcase at end of rfc2047
2376n/a s = '(=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=)'
2377n/a self.assertEqual(decode_header(s),
2378n/a [(b'(', None), (b'a', 'iso-8859-1'), (b' b', 'iso-8859-2'),
2379n/a (b')', None)])
2380n/a self.assertEqual(make_header(decode_header(s)).encode(), s.lower())
2381n/a self.assertEqual(str(make_header(decode_header(s))), '(a b)')
2382n/a
2383n/a def test_multiline_header(self):
2384n/a s = '=?windows-1252?q?=22M=FCller_T=22?=\r\n <T.Mueller@xxx.com>'
2385n/a self.assertEqual(decode_header(s),
2386n/a [(b'"M\xfcller T"', 'windows-1252'),
2387n/a (b'<T.Mueller@xxx.com>', None)])
2388n/a self.assertEqual(make_header(decode_header(s)).encode(),
2389n/a ''.join(s.splitlines()))
2390n/a self.assertEqual(str(make_header(decode_header(s))),
2391n/a '"Müller T" <T.Mueller@xxx.com>')
2392n/a
2393n/a
2394n/a# Test the MIMEMessage class
2395n/aclass TestMIMEMessage(TestEmailBase):
2396n/a def setUp(self):
2397n/a with openfile('msg_11.txt') as fp:
2398n/a self._text = fp.read()
2399n/a
2400n/a def test_type_error(self):
2401n/a self.assertRaises(TypeError, MIMEMessage, 'a plain string')
2402n/a
2403n/a def test_valid_argument(self):
2404n/a eq = self.assertEqual
2405n/a subject = 'A sub-message'
2406n/a m = Message()
2407n/a m['Subject'] = subject
2408n/a r = MIMEMessage(m)
2409n/a eq(r.get_content_type(), 'message/rfc822')
2410n/a payload = r.get_payload()
2411n/a self.assertIsInstance(payload, list)
2412n/a eq(len(payload), 1)
2413n/a subpart = payload[0]
2414n/a self.assertIs(subpart, m)
2415n/a eq(subpart['subject'], subject)
2416n/a
2417n/a def test_bad_multipart(self):
2418n/a msg1 = Message()
2419n/a msg1['Subject'] = 'subpart 1'
2420n/a msg2 = Message()
2421n/a msg2['Subject'] = 'subpart 2'
2422n/a r = MIMEMessage(msg1)
2423n/a self.assertRaises(errors.MultipartConversionError, r.attach, msg2)
2424n/a
2425n/a def test_generate(self):
2426n/a # First craft the message to be encapsulated
2427n/a m = Message()
2428n/a m['Subject'] = 'An enclosed message'
2429n/a m.set_payload('Here is the body of the message.\n')
2430n/a r = MIMEMessage(m)
2431n/a r['Subject'] = 'The enclosing message'
2432n/a s = StringIO()
2433n/a g = Generator(s)
2434n/a g.flatten(r)
2435n/a self.assertEqual(s.getvalue(), """\
2436n/aContent-Type: message/rfc822
2437n/aMIME-Version: 1.0
2438n/aSubject: The enclosing message
2439n/a
2440n/aSubject: An enclosed message
2441n/a
2442n/aHere is the body of the message.
2443n/a""")
2444n/a
2445n/a def test_parse_message_rfc822(self):
2446n/a eq = self.assertEqual
2447n/a msg = self._msgobj('msg_11.txt')
2448n/a eq(msg.get_content_type(), 'message/rfc822')
2449n/a payload = msg.get_payload()
2450n/a self.assertIsInstance(payload, list)
2451n/a eq(len(payload), 1)
2452n/a submsg = payload[0]
2453n/a self.assertIsInstance(submsg, Message)
2454n/a eq(submsg['subject'], 'An enclosed message')
2455n/a eq(submsg.get_payload(), 'Here is the body of the message.\n')
2456n/a
2457n/a def test_dsn(self):
2458n/a eq = self.assertEqual
2459n/a # msg 16 is a Delivery Status Notification, see RFC 1894
2460n/a msg = self._msgobj('msg_16.txt')
2461n/a eq(msg.get_content_type(), 'multipart/report')
2462n/a self.assertTrue(msg.is_multipart())
2463n/a eq(len(msg.get_payload()), 3)
2464n/a # Subpart 1 is a text/plain, human readable section
2465n/a subpart = msg.get_payload(0)
2466n/a eq(subpart.get_content_type(), 'text/plain')
2467n/a eq(subpart.get_payload(), """\
2468n/aThis report relates to a message you sent with the following header fields:
2469n/a
2470n/a Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
2471n/a Date: Sun, 23 Sep 2001 20:10:55 -0700
2472n/a From: "Ian T. Henry" <henryi@oxy.edu>
2473n/a To: SoCal Raves <scr@socal-raves.org>
2474n/a Subject: [scr] yeah for Ians!!
2475n/a
2476n/aYour message cannot be delivered to the following recipients:
2477n/a
2478n/a Recipient address: jangel1@cougar.noc.ucla.edu
2479n/a Reason: recipient reached disk quota
2480n/a
2481n/a""")
2482n/a # Subpart 2 contains the machine parsable DSN information. It
2483n/a # consists of two blocks of headers, represented by two nested Message
2484n/a # objects.
2485n/a subpart = msg.get_payload(1)
2486n/a eq(subpart.get_content_type(), 'message/delivery-status')
2487n/a eq(len(subpart.get_payload()), 2)
2488n/a # message/delivery-status should treat each block as a bunch of
2489n/a # headers, i.e. a bunch of Message objects.
2490n/a dsn1 = subpart.get_payload(0)
2491n/a self.assertIsInstance(dsn1, Message)
2492n/a eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
2493n/a eq(dsn1.get_param('dns', header='reporting-mta'), '')
2494n/a # Try a missing one <wink>
2495n/a eq(dsn1.get_param('nsd', header='reporting-mta'), None)
2496n/a dsn2 = subpart.get_payload(1)
2497n/a self.assertIsInstance(dsn2, Message)
2498n/a eq(dsn2['action'], 'failed')
2499n/a eq(dsn2.get_params(header='original-recipient'),
2500n/a [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
2501n/a eq(dsn2.get_param('rfc822', header='final-recipient'), '')
2502n/a # Subpart 3 is the original message
2503n/a subpart = msg.get_payload(2)
2504n/a eq(subpart.get_content_type(), 'message/rfc822')
2505n/a payload = subpart.get_payload()
2506n/a self.assertIsInstance(payload, list)
2507n/a eq(len(payload), 1)
2508n/a subsubpart = payload[0]
2509n/a self.assertIsInstance(subsubpart, Message)
2510n/a eq(subsubpart.get_content_type(), 'text/plain')
2511n/a eq(subsubpart['message-id'],
2512n/a '<002001c144a6$8752e060$56104586@oxy.edu>')
2513n/a
2514n/a def test_epilogue(self):
2515n/a eq = self.ndiffAssertEqual
2516n/a with openfile('msg_21.txt') as fp:
2517n/a text = fp.read()
2518n/a msg = Message()
2519n/a msg['From'] = 'aperson@dom.ain'
2520n/a msg['To'] = 'bperson@dom.ain'
2521n/a msg['Subject'] = 'Test'
2522n/a msg.preamble = 'MIME message'
2523n/a msg.epilogue = 'End of MIME message\n'
2524n/a msg1 = MIMEText('One')
2525n/a msg2 = MIMEText('Two')
2526n/a msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
2527n/a msg.attach(msg1)
2528n/a msg.attach(msg2)
2529n/a sfp = StringIO()
2530n/a g = Generator(sfp)
2531n/a g.flatten(msg)
2532n/a eq(sfp.getvalue(), text)
2533n/a
2534n/a def test_no_nl_preamble(self):
2535n/a eq = self.ndiffAssertEqual
2536n/a msg = Message()
2537n/a msg['From'] = 'aperson@dom.ain'
2538n/a msg['To'] = 'bperson@dom.ain'
2539n/a msg['Subject'] = 'Test'
2540n/a msg.preamble = 'MIME message'
2541n/a msg.epilogue = ''
2542n/a msg1 = MIMEText('One')
2543n/a msg2 = MIMEText('Two')
2544n/a msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
2545n/a msg.attach(msg1)
2546n/a msg.attach(msg2)
2547n/a eq(msg.as_string(), """\
2548n/aFrom: aperson@dom.ain
2549n/aTo: bperson@dom.ain
2550n/aSubject: Test
2551n/aContent-Type: multipart/mixed; boundary="BOUNDARY"
2552n/a
2553n/aMIME message
2554n/a--BOUNDARY
2555n/aContent-Type: text/plain; charset="us-ascii"
2556n/aMIME-Version: 1.0
2557n/aContent-Transfer-Encoding: 7bit
2558n/a
2559n/aOne
2560n/a--BOUNDARY
2561n/aContent-Type: text/plain; charset="us-ascii"
2562n/aMIME-Version: 1.0
2563n/aContent-Transfer-Encoding: 7bit
2564n/a
2565n/aTwo
2566n/a--BOUNDARY--
2567n/a""")
2568n/a
2569n/a def test_default_type(self):
2570n/a eq = self.assertEqual
2571n/a with openfile('msg_30.txt') as fp:
2572n/a msg = email.message_from_file(fp)
2573n/a container1 = msg.get_payload(0)
2574n/a eq(container1.get_default_type(), 'message/rfc822')
2575n/a eq(container1.get_content_type(), 'message/rfc822')
2576n/a container2 = msg.get_payload(1)
2577n/a eq(container2.get_default_type(), 'message/rfc822')
2578n/a eq(container2.get_content_type(), 'message/rfc822')
2579n/a container1a = container1.get_payload(0)
2580n/a eq(container1a.get_default_type(), 'text/plain')
2581n/a eq(container1a.get_content_type(), 'text/plain')
2582n/a container2a = container2.get_payload(0)
2583n/a eq(container2a.get_default_type(), 'text/plain')
2584n/a eq(container2a.get_content_type(), 'text/plain')
2585n/a
2586n/a def test_default_type_with_explicit_container_type(self):
2587n/a eq = self.assertEqual
2588n/a with openfile('msg_28.txt') as fp:
2589n/a msg = email.message_from_file(fp)
2590n/a container1 = msg.get_payload(0)
2591n/a eq(container1.get_default_type(), 'message/rfc822')
2592n/a eq(container1.get_content_type(), 'message/rfc822')
2593n/a container2 = msg.get_payload(1)
2594n/a eq(container2.get_default_type(), 'message/rfc822')
2595n/a eq(container2.get_content_type(), 'message/rfc822')
2596n/a container1a = container1.get_payload(0)
2597n/a eq(container1a.get_default_type(), 'text/plain')
2598n/a eq(container1a.get_content_type(), 'text/plain')
2599n/a container2a = container2.get_payload(0)
2600n/a eq(container2a.get_default_type(), 'text/plain')
2601n/a eq(container2a.get_content_type(), 'text/plain')
2602n/a
2603n/a def test_default_type_non_parsed(self):
2604n/a eq = self.assertEqual
2605n/a neq = self.ndiffAssertEqual
2606n/a # Set up container
2607n/a container = MIMEMultipart('digest', 'BOUNDARY')
2608n/a container.epilogue = ''
2609n/a # Set up subparts
2610n/a subpart1a = MIMEText('message 1\n')
2611n/a subpart2a = MIMEText('message 2\n')
2612n/a subpart1 = MIMEMessage(subpart1a)
2613n/a subpart2 = MIMEMessage(subpart2a)
2614n/a container.attach(subpart1)
2615n/a container.attach(subpart2)
2616n/a eq(subpart1.get_content_type(), 'message/rfc822')
2617n/a eq(subpart1.get_default_type(), 'message/rfc822')
2618n/a eq(subpart2.get_content_type(), 'message/rfc822')
2619n/a eq(subpart2.get_default_type(), 'message/rfc822')
2620n/a neq(container.as_string(0), '''\
2621n/aContent-Type: multipart/digest; boundary="BOUNDARY"
2622n/aMIME-Version: 1.0
2623n/a
2624n/a--BOUNDARY
2625n/aContent-Type: message/rfc822
2626n/aMIME-Version: 1.0
2627n/a
2628n/aContent-Type: text/plain; charset="us-ascii"
2629n/aMIME-Version: 1.0
2630n/aContent-Transfer-Encoding: 7bit
2631n/a
2632n/amessage 1
2633n/a
2634n/a--BOUNDARY
2635n/aContent-Type: message/rfc822
2636n/aMIME-Version: 1.0
2637n/a
2638n/aContent-Type: text/plain; charset="us-ascii"
2639n/aMIME-Version: 1.0
2640n/aContent-Transfer-Encoding: 7bit
2641n/a
2642n/amessage 2
2643n/a
2644n/a--BOUNDARY--
2645n/a''')
2646n/a del subpart1['content-type']
2647n/a del subpart1['mime-version']
2648n/a del subpart2['content-type']
2649n/a del subpart2['mime-version']
2650n/a eq(subpart1.get_content_type(), 'message/rfc822')
2651n/a eq(subpart1.get_default_type(), 'message/rfc822')
2652n/a eq(subpart2.get_content_type(), 'message/rfc822')
2653n/a eq(subpart2.get_default_type(), 'message/rfc822')
2654n/a neq(container.as_string(0), '''\
2655n/aContent-Type: multipart/digest; boundary="BOUNDARY"
2656n/aMIME-Version: 1.0
2657n/a
2658n/a--BOUNDARY
2659n/a
2660n/aContent-Type: text/plain; charset="us-ascii"
2661n/aMIME-Version: 1.0
2662n/aContent-Transfer-Encoding: 7bit
2663n/a
2664n/amessage 1
2665n/a
2666n/a--BOUNDARY
2667n/a
2668n/aContent-Type: text/plain; charset="us-ascii"
2669n/aMIME-Version: 1.0
2670n/aContent-Transfer-Encoding: 7bit
2671n/a
2672n/amessage 2
2673n/a
2674n/a--BOUNDARY--
2675n/a''')
2676n/a
2677n/a def test_mime_attachments_in_constructor(self):
2678n/a eq = self.assertEqual
2679n/a text1 = MIMEText('')
2680n/a text2 = MIMEText('')
2681n/a msg = MIMEMultipart(_subparts=(text1, text2))
2682n/a eq(len(msg.get_payload()), 2)
2683n/a eq(msg.get_payload(0), text1)
2684n/a eq(msg.get_payload(1), text2)
2685n/a
2686n/a def test_default_multipart_constructor(self):
2687n/a msg = MIMEMultipart()
2688n/a self.assertTrue(msg.is_multipart())
2689n/a
2690n/a def test_multipart_default_policy(self):
2691n/a msg = MIMEMultipart()
2692n/a msg['To'] = 'a@b.com'
2693n/a msg['To'] = 'c@d.com'
2694n/a self.assertEqual(msg.get_all('to'), ['a@b.com', 'c@d.com'])
2695n/a
2696n/a def test_multipart_custom_policy(self):
2697n/a msg = MIMEMultipart(policy=email.policy.default)
2698n/a msg['To'] = 'a@b.com'
2699n/a with self.assertRaises(ValueError) as cm:
2700n/a msg['To'] = 'c@d.com'
2701n/a self.assertEqual(str(cm.exception),
2702n/a 'There may be at most 1 To headers in a message')
2703n/a
2704n/a# A general test of parser->model->generator idempotency. IOW, read a message
2705n/a# in, parse it into a message object tree, then without touching the tree,
2706n/a# regenerate the plain text. The original text and the transformed text
2707n/a# should be identical. Note: that we ignore the Unix-From since that may
2708n/a# contain a changed date.
2709n/aclass TestIdempotent(TestEmailBase):
2710n/a
2711n/a linesep = '\n'
2712n/a
2713n/a def _msgobj(self, filename):
2714n/a with openfile(filename) as fp:
2715n/a data = fp.read()
2716n/a msg = email.message_from_string(data)
2717n/a return msg, data
2718n/a
2719n/a def _idempotent(self, msg, text, unixfrom=False):
2720n/a eq = self.ndiffAssertEqual
2721n/a s = StringIO()
2722n/a g = Generator(s, maxheaderlen=0)
2723n/a g.flatten(msg, unixfrom=unixfrom)
2724n/a eq(text, s.getvalue())
2725n/a
2726n/a def test_parse_text_message(self):
2727n/a eq = self.assertEqual
2728n/a msg, text = self._msgobj('msg_01.txt')
2729n/a eq(msg.get_content_type(), 'text/plain')
2730n/a eq(msg.get_content_maintype(), 'text')
2731n/a eq(msg.get_content_subtype(), 'plain')
2732n/a eq(msg.get_params()[1], ('charset', 'us-ascii'))
2733n/a eq(msg.get_param('charset'), 'us-ascii')
2734n/a eq(msg.preamble, None)
2735n/a eq(msg.epilogue, None)
2736n/a self._idempotent(msg, text)
2737n/a
2738n/a def test_parse_untyped_message(self):
2739n/a eq = self.assertEqual
2740n/a msg, text = self._msgobj('msg_03.txt')
2741n/a eq(msg.get_content_type(), 'text/plain')
2742n/a eq(msg.get_params(), None)
2743n/a eq(msg.get_param('charset'), None)
2744n/a self._idempotent(msg, text)
2745n/a
2746n/a def test_simple_multipart(self):
2747n/a msg, text = self._msgobj('msg_04.txt')
2748n/a self._idempotent(msg, text)
2749n/a
2750n/a def test_MIME_digest(self):
2751n/a msg, text = self._msgobj('msg_02.txt')
2752n/a self._idempotent(msg, text)
2753n/a
2754n/a def test_long_header(self):
2755n/a msg, text = self._msgobj('msg_27.txt')
2756n/a self._idempotent(msg, text)
2757n/a
2758n/a def test_MIME_digest_with_part_headers(self):
2759n/a msg, text = self._msgobj('msg_28.txt')
2760n/a self._idempotent(msg, text)
2761n/a
2762n/a def test_mixed_with_image(self):
2763n/a msg, text = self._msgobj('msg_06.txt')
2764n/a self._idempotent(msg, text)
2765n/a
2766n/a def test_multipart_report(self):
2767n/a msg, text = self._msgobj('msg_05.txt')
2768n/a self._idempotent(msg, text)
2769n/a
2770n/a def test_dsn(self):
2771n/a msg, text = self._msgobj('msg_16.txt')
2772n/a self._idempotent(msg, text)
2773n/a
2774n/a def test_preamble_epilogue(self):
2775n/a msg, text = self._msgobj('msg_21.txt')
2776n/a self._idempotent(msg, text)
2777n/a
2778n/a def test_multipart_one_part(self):
2779n/a msg, text = self._msgobj('msg_23.txt')
2780n/a self._idempotent(msg, text)
2781n/a
2782n/a def test_multipart_no_parts(self):
2783n/a msg, text = self._msgobj('msg_24.txt')
2784n/a self._idempotent(msg, text)
2785n/a
2786n/a def test_no_start_boundary(self):
2787n/a msg, text = self._msgobj('msg_31.txt')
2788n/a self._idempotent(msg, text)
2789n/a
2790n/a def test_rfc2231_charset(self):
2791n/a msg, text = self._msgobj('msg_32.txt')
2792n/a self._idempotent(msg, text)
2793n/a
2794n/a def test_more_rfc2231_parameters(self):
2795n/a msg, text = self._msgobj('msg_33.txt')
2796n/a self._idempotent(msg, text)
2797n/a
2798n/a def test_text_plain_in_a_multipart_digest(self):
2799n/a msg, text = self._msgobj('msg_34.txt')
2800n/a self._idempotent(msg, text)
2801n/a
2802n/a def test_nested_multipart_mixeds(self):
2803n/a msg, text = self._msgobj('msg_12a.txt')
2804n/a self._idempotent(msg, text)
2805n/a
2806n/a def test_message_external_body_idempotent(self):
2807n/a msg, text = self._msgobj('msg_36.txt')
2808n/a self._idempotent(msg, text)
2809n/a
2810n/a def test_message_delivery_status(self):
2811n/a msg, text = self._msgobj('msg_43.txt')
2812n/a self._idempotent(msg, text, unixfrom=True)
2813n/a
2814n/a def test_message_signed_idempotent(self):
2815n/a msg, text = self._msgobj('msg_45.txt')
2816n/a self._idempotent(msg, text)
2817n/a
2818n/a def test_content_type(self):
2819n/a eq = self.assertEqual
2820n/a # Get a message object and reset the seek pointer for other tests
2821n/a msg, text = self._msgobj('msg_05.txt')
2822n/a eq(msg.get_content_type(), 'multipart/report')
2823n/a # Test the Content-Type: parameters
2824n/a params = {}
2825n/a for pk, pv in msg.get_params():
2826n/a params[pk] = pv
2827n/a eq(params['report-type'], 'delivery-status')
2828n/a eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
2829n/a eq(msg.preamble, 'This is a MIME-encapsulated message.' + self.linesep)
2830n/a eq(msg.epilogue, self.linesep)
2831n/a eq(len(msg.get_payload()), 3)
2832n/a # Make sure the subparts are what we expect
2833n/a msg1 = msg.get_payload(0)
2834n/a eq(msg1.get_content_type(), 'text/plain')
2835n/a eq(msg1.get_payload(), 'Yadda yadda yadda' + self.linesep)
2836n/a msg2 = msg.get_payload(1)
2837n/a eq(msg2.get_content_type(), 'text/plain')
2838n/a eq(msg2.get_payload(), 'Yadda yadda yadda' + self.linesep)
2839n/a msg3 = msg.get_payload(2)
2840n/a eq(msg3.get_content_type(), 'message/rfc822')
2841n/a self.assertIsInstance(msg3, Message)
2842n/a payload = msg3.get_payload()
2843n/a self.assertIsInstance(payload, list)
2844n/a eq(len(payload), 1)
2845n/a msg4 = payload[0]
2846n/a self.assertIsInstance(msg4, Message)
2847n/a eq(msg4.get_payload(), 'Yadda yadda yadda' + self.linesep)
2848n/a
2849n/a def test_parser(self):
2850n/a eq = self.assertEqual
2851n/a msg, text = self._msgobj('msg_06.txt')
2852n/a # Check some of the outer headers
2853n/a eq(msg.get_content_type(), 'message/rfc822')
2854n/a # Make sure the payload is a list of exactly one sub-Message, and that
2855n/a # that submessage has a type of text/plain
2856n/a payload = msg.get_payload()
2857n/a self.assertIsInstance(payload, list)
2858n/a eq(len(payload), 1)
2859n/a msg1 = payload[0]
2860n/a self.assertIsInstance(msg1, Message)
2861n/a eq(msg1.get_content_type(), 'text/plain')
2862n/a self.assertIsInstance(msg1.get_payload(), str)
2863n/a eq(msg1.get_payload(), self.linesep)
2864n/a
2865n/a
2866n/a
2867n/a# Test various other bits of the package's functionality
2868n/aclass TestMiscellaneous(TestEmailBase):
2869n/a def test_message_from_string(self):
2870n/a with openfile('msg_01.txt') as fp:
2871n/a text = fp.read()
2872n/a msg = email.message_from_string(text)
2873n/a s = StringIO()
2874n/a # Don't wrap/continue long headers since we're trying to test
2875n/a # idempotency.
2876n/a g = Generator(s, maxheaderlen=0)
2877n/a g.flatten(msg)
2878n/a self.assertEqual(text, s.getvalue())
2879n/a
2880n/a def test_message_from_file(self):
2881n/a with openfile('msg_01.txt') as fp:
2882n/a text = fp.read()
2883n/a fp.seek(0)
2884n/a msg = email.message_from_file(fp)
2885n/a s = StringIO()
2886n/a # Don't wrap/continue long headers since we're trying to test
2887n/a # idempotency.
2888n/a g = Generator(s, maxheaderlen=0)
2889n/a g.flatten(msg)
2890n/a self.assertEqual(text, s.getvalue())
2891n/a
2892n/a def test_message_from_string_with_class(self):
2893n/a with openfile('msg_01.txt') as fp:
2894n/a text = fp.read()
2895n/a
2896n/a # Create a subclass
2897n/a class MyMessage(Message):
2898n/a pass
2899n/a
2900n/a msg = email.message_from_string(text, MyMessage)
2901n/a self.assertIsInstance(msg, MyMessage)
2902n/a # Try something more complicated
2903n/a with openfile('msg_02.txt') as fp:
2904n/a text = fp.read()
2905n/a msg = email.message_from_string(text, MyMessage)
2906n/a for subpart in msg.walk():
2907n/a self.assertIsInstance(subpart, MyMessage)
2908n/a
2909n/a def test_message_from_file_with_class(self):
2910n/a # Create a subclass
2911n/a class MyMessage(Message):
2912n/a pass
2913n/a
2914n/a with openfile('msg_01.txt') as fp:
2915n/a msg = email.message_from_file(fp, MyMessage)
2916n/a self.assertIsInstance(msg, MyMessage)
2917n/a # Try something more complicated
2918n/a with openfile('msg_02.txt') as fp:
2919n/a msg = email.message_from_file(fp, MyMessage)
2920n/a for subpart in msg.walk():
2921n/a self.assertIsInstance(subpart, MyMessage)
2922n/a
2923n/a def test_custom_message_does_not_require_arguments(self):
2924n/a class MyMessage(Message):
2925n/a def __init__(self):
2926n/a super().__init__()
2927n/a msg = self._str_msg("Subject: test\n\ntest", MyMessage)
2928n/a self.assertIsInstance(msg, MyMessage)
2929n/a
2930n/a def test__all__(self):
2931n/a module = __import__('email')
2932n/a self.assertEqual(sorted(module.__all__), [
2933n/a 'base64mime', 'charset', 'encoders', 'errors', 'feedparser',
2934n/a 'generator', 'header', 'iterators', 'message',
2935n/a 'message_from_binary_file', 'message_from_bytes',
2936n/a 'message_from_file', 'message_from_string', 'mime', 'parser',
2937n/a 'quoprimime', 'utils',
2938n/a ])
2939n/a
2940n/a def test_formatdate(self):
2941n/a now = time.time()
2942n/a self.assertEqual(utils.parsedate(utils.formatdate(now))[:6],
2943n/a time.gmtime(now)[:6])
2944n/a
2945n/a def test_formatdate_localtime(self):
2946n/a now = time.time()
2947n/a self.assertEqual(
2948n/a utils.parsedate(utils.formatdate(now, localtime=True))[:6],
2949n/a time.localtime(now)[:6])
2950n/a
2951n/a def test_formatdate_usegmt(self):
2952n/a now = time.time()
2953n/a self.assertEqual(
2954n/a utils.formatdate(now, localtime=False),
2955n/a time.strftime('%a, %d %b %Y %H:%M:%S -0000', time.gmtime(now)))
2956n/a self.assertEqual(
2957n/a utils.formatdate(now, localtime=False, usegmt=True),
2958n/a time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(now)))
2959n/a
2960n/a # parsedate and parsedate_tz will become deprecated interfaces someday
2961n/a def test_parsedate_returns_None_for_invalid_strings(self):
2962n/a self.assertIsNone(utils.parsedate(''))
2963n/a self.assertIsNone(utils.parsedate_tz(''))
2964n/a self.assertIsNone(utils.parsedate('0'))
2965n/a self.assertIsNone(utils.parsedate_tz('0'))
2966n/a self.assertIsNone(utils.parsedate('A Complete Waste of Time'))
2967n/a self.assertIsNone(utils.parsedate_tz('A Complete Waste of Time'))
2968n/a # Not a part of the spec but, but this has historically worked:
2969n/a self.assertIsNone(utils.parsedate(None))
2970n/a self.assertIsNone(utils.parsedate_tz(None))
2971n/a
2972n/a def test_parsedate_compact(self):
2973n/a # The FWS after the comma is optional
2974n/a self.assertEqual(utils.parsedate('Wed,3 Apr 2002 14:58:26 +0800'),
2975n/a utils.parsedate('Wed, 3 Apr 2002 14:58:26 +0800'))
2976n/a
2977n/a def test_parsedate_no_dayofweek(self):
2978n/a eq = self.assertEqual
2979n/a eq(utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'),
2980n/a (2003, 2, 25, 13, 47, 26, 0, 1, -1, -28800))
2981n/a
2982n/a def test_parsedate_compact_no_dayofweek(self):
2983n/a eq = self.assertEqual
2984n/a eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
2985n/a (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
2986n/a
2987n/a def test_parsedate_no_space_before_positive_offset(self):
2988n/a self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26+0800'),
2989n/a (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
2990n/a
2991n/a def test_parsedate_no_space_before_negative_offset(self):
2992n/a # Issue 1155362: we already handled '+' for this case.
2993n/a self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26-0800'),
2994n/a (2002, 4, 3, 14, 58, 26, 0, 1, -1, -28800))
2995n/a
2996n/a
2997n/a def test_parsedate_accepts_time_with_dots(self):
2998n/a eq = self.assertEqual
2999n/a eq(utils.parsedate_tz('5 Feb 2003 13.47.26 -0800'),
3000n/a (2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
3001n/a eq(utils.parsedate_tz('5 Feb 2003 13.47 -0800'),
3002n/a (2003, 2, 5, 13, 47, 0, 0, 1, -1, -28800))
3003n/a
3004n/a def test_parsedate_acceptable_to_time_functions(self):
3005n/a eq = self.assertEqual
3006n/a timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
3007n/a t = int(time.mktime(timetup))
3008n/a eq(time.localtime(t)[:6], timetup[:6])
3009n/a eq(int(time.strftime('%Y', timetup)), 2003)
3010n/a timetup = utils.parsedate_tz('5 Feb 2003 13:47:26 -0800')
3011n/a t = int(time.mktime(timetup[:9]))
3012n/a eq(time.localtime(t)[:6], timetup[:6])
3013n/a eq(int(time.strftime('%Y', timetup[:9])), 2003)
3014n/a
3015n/a def test_mktime_tz(self):
3016n/a self.assertEqual(utils.mktime_tz((1970, 1, 1, 0, 0, 0,
3017n/a -1, -1, -1, 0)), 0)
3018n/a self.assertEqual(utils.mktime_tz((1970, 1, 1, 0, 0, 0,
3019n/a -1, -1, -1, 1234)), -1234)
3020n/a
3021n/a def test_parsedate_y2k(self):
3022n/a """Test for parsing a date with a two-digit year.
3023n/a
3024n/a Parsing a date with a two-digit year should return the correct
3025n/a four-digit year. RFC822 allows two-digit years, but RFC2822 (which
3026n/a obsoletes RFC822) requires four-digit years.
3027n/a
3028n/a """
3029n/a self.assertEqual(utils.parsedate_tz('25 Feb 03 13:47:26 -0800'),
3030n/a utils.parsedate_tz('25 Feb 2003 13:47:26 -0800'))
3031n/a self.assertEqual(utils.parsedate_tz('25 Feb 71 13:47:26 -0800'),
3032n/a utils.parsedate_tz('25 Feb 1971 13:47:26 -0800'))
3033n/a
3034n/a def test_parseaddr_empty(self):
3035n/a self.assertEqual(utils.parseaddr('<>'), ('', ''))
3036n/a self.assertEqual(utils.formataddr(utils.parseaddr('<>')), '')
3037n/a
3038n/a def test_noquote_dump(self):
3039n/a self.assertEqual(
3040n/a utils.formataddr(('A Silly Person', 'person@dom.ain')),
3041n/a 'A Silly Person <person@dom.ain>')
3042n/a
3043n/a def test_escape_dump(self):
3044n/a self.assertEqual(
3045n/a utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
3046n/a r'"A (Very) Silly Person" <person@dom.ain>')
3047n/a self.assertEqual(
3048n/a utils.parseaddr(r'"A \(Very\) Silly Person" <person@dom.ain>'),
3049n/a ('A (Very) Silly Person', 'person@dom.ain'))
3050n/a a = r'A \(Special\) Person'
3051n/a b = 'person@dom.ain'
3052n/a self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
3053n/a
3054n/a def test_escape_backslashes(self):
3055n/a self.assertEqual(
3056n/a utils.formataddr((r'Arthur \Backslash\ Foobar', 'person@dom.ain')),
3057n/a r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>')
3058n/a a = r'Arthur \Backslash\ Foobar'
3059n/a b = 'person@dom.ain'
3060n/a self.assertEqual(utils.parseaddr(utils.formataddr((a, b))), (a, b))
3061n/a
3062n/a def test_quotes_unicode_names(self):
3063n/a # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
3064n/a name = "H\u00e4ns W\u00fcrst"
3065n/a addr = 'person@dom.ain'
3066n/a utf8_base64 = "=?utf-8?b?SMOkbnMgV8O8cnN0?= <person@dom.ain>"
3067n/a latin1_quopri = "=?iso-8859-1?q?H=E4ns_W=FCrst?= <person@dom.ain>"
3068n/a self.assertEqual(utils.formataddr((name, addr)), utf8_base64)
3069n/a self.assertEqual(utils.formataddr((name, addr), 'iso-8859-1'),
3070n/a latin1_quopri)
3071n/a
3072n/a def test_accepts_any_charset_like_object(self):
3073n/a # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
3074n/a name = "H\u00e4ns W\u00fcrst"
3075n/a addr = 'person@dom.ain'
3076n/a utf8_base64 = "=?utf-8?b?SMOkbnMgV8O8cnN0?= <person@dom.ain>"
3077n/a foobar = "FOOBAR"
3078n/a class CharsetMock:
3079n/a def header_encode(self, string):
3080n/a return foobar
3081n/a mock = CharsetMock()
3082n/a mock_expected = "%s <%s>" % (foobar, addr)
3083n/a self.assertEqual(utils.formataddr((name, addr), mock), mock_expected)
3084n/a self.assertEqual(utils.formataddr((name, addr), Charset('utf-8')),
3085n/a utf8_base64)
3086n/a
3087n/a def test_invalid_charset_like_object_raises_error(self):
3088n/a # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
3089n/a name = "H\u00e4ns W\u00fcrst"
3090n/a addr = 'person@dom.ain'
3091n/a # An object without a header_encode method:
3092n/a bad_charset = object()
3093n/a self.assertRaises(AttributeError, utils.formataddr, (name, addr),
3094n/a bad_charset)
3095n/a
3096n/a def test_unicode_address_raises_error(self):
3097n/a # issue 1690608. email.utils.formataddr() should be rfc2047 aware.
3098n/a addr = 'pers\u00f6n@dom.in'
3099n/a self.assertRaises(UnicodeError, utils.formataddr, (None, addr))
3100n/a self.assertRaises(UnicodeError, utils.formataddr, ("Name", addr))
3101n/a
3102n/a def test_name_with_dot(self):
3103n/a x = 'John X. Doe <jxd@example.com>'
3104n/a y = '"John X. Doe" <jxd@example.com>'
3105n/a a, b = ('John X. Doe', 'jxd@example.com')
3106n/a self.assertEqual(utils.parseaddr(x), (a, b))
3107n/a self.assertEqual(utils.parseaddr(y), (a, b))
3108n/a # formataddr() quotes the name if there's a dot in it
3109n/a self.assertEqual(utils.formataddr((a, b)), y)
3110n/a
3111n/a def test_parseaddr_preserves_quoted_pairs_in_addresses(self):
3112n/a # issue 10005. Note that in the third test the second pair of
3113n/a # backslashes is not actually a quoted pair because it is not inside a
3114n/a # comment or quoted string: the address being parsed has a quoted
3115n/a # string containing a quoted backslash, followed by 'example' and two
3116n/a # backslashes, followed by another quoted string containing a space and
3117n/a # the word 'example'. parseaddr copies those two backslashes
3118n/a # literally. Per rfc5322 this is not technically correct since a \ may
3119n/a # not appear in an address outside of a quoted string. It is probably
3120n/a # a sensible Postel interpretation, though.
3121n/a eq = self.assertEqual
3122n/a eq(utils.parseaddr('""example" example"@example.com'),
3123n/a ('', '""example" example"@example.com'))
3124n/a eq(utils.parseaddr('"\\"example\\" example"@example.com'),
3125n/a ('', '"\\"example\\" example"@example.com'))
3126n/a eq(utils.parseaddr('"\\\\"example\\\\" example"@example.com'),
3127n/a ('', '"\\\\"example\\\\" example"@example.com'))
3128n/a
3129n/a def test_parseaddr_preserves_spaces_in_local_part(self):
3130n/a # issue 9286. A normal RFC5322 local part should not contain any
3131n/a # folding white space, but legacy local parts can (they are a sequence
3132n/a # of atoms, not dotatoms). On the other hand we strip whitespace from
3133n/a # before the @ and around dots, on the assumption that the whitespace
3134n/a # around the punctuation is a mistake in what would otherwise be
3135n/a # an RFC5322 local part. Leading whitespace is, usual, stripped as well.
3136n/a self.assertEqual(('', "merwok wok@xample.com"),
3137n/a utils.parseaddr("merwok wok@xample.com"))
3138n/a self.assertEqual(('', "merwok wok@xample.com"),
3139n/a utils.parseaddr("merwok wok@xample.com"))
3140n/a self.assertEqual(('', "merwok wok@xample.com"),
3141n/a utils.parseaddr(" merwok wok @xample.com"))
3142n/a self.assertEqual(('', 'merwok"wok" wok@xample.com'),
3143n/a utils.parseaddr('merwok"wok" wok@xample.com'))
3144n/a self.assertEqual(('', 'merwok.wok.wok@xample.com'),
3145n/a utils.parseaddr('merwok. wok . wok@xample.com'))
3146n/a
3147n/a def test_formataddr_does_not_quote_parens_in_quoted_string(self):
3148n/a addr = ("'foo@example.com' (foo@example.com)",
3149n/a 'foo@example.com')
3150n/a addrstr = ('"\'foo@example.com\' '
3151n/a '(foo@example.com)" <foo@example.com>')
3152n/a self.assertEqual(utils.parseaddr(addrstr), addr)
3153n/a self.assertEqual(utils.formataddr(addr), addrstr)
3154n/a
3155n/a
3156n/a def test_multiline_from_comment(self):
3157n/a x = """\
3158n/aFoo
3159n/a\tBar <foo@example.com>"""
3160n/a self.assertEqual(utils.parseaddr(x), ('Foo Bar', 'foo@example.com'))
3161n/a
3162n/a def test_quote_dump(self):
3163n/a self.assertEqual(
3164n/a utils.formataddr(('A Silly; Person', 'person@dom.ain')),
3165n/a r'"A Silly; Person" <person@dom.ain>')
3166n/a
3167n/a def test_charset_richcomparisons(self):
3168n/a eq = self.assertEqual
3169n/a ne = self.assertNotEqual
3170n/a cset1 = Charset()
3171n/a cset2 = Charset()
3172n/a eq(cset1, 'us-ascii')
3173n/a eq(cset1, 'US-ASCII')
3174n/a eq(cset1, 'Us-AsCiI')
3175n/a eq('us-ascii', cset1)
3176n/a eq('US-ASCII', cset1)
3177n/a eq('Us-AsCiI', cset1)
3178n/a ne(cset1, 'usascii')
3179n/a ne(cset1, 'USASCII')
3180n/a ne(cset1, 'UsAsCiI')
3181n/a ne('usascii', cset1)
3182n/a ne('USASCII', cset1)
3183n/a ne('UsAsCiI', cset1)
3184n/a eq(cset1, cset2)
3185n/a eq(cset2, cset1)
3186n/a
3187n/a def test_getaddresses(self):
3188n/a eq = self.assertEqual
3189n/a eq(utils.getaddresses(['aperson@dom.ain (Al Person)',
3190n/a 'Bud Person <bperson@dom.ain>']),
3191n/a [('Al Person', 'aperson@dom.ain'),
3192n/a ('Bud Person', 'bperson@dom.ain')])
3193n/a
3194n/a def test_getaddresses_nasty(self):
3195n/a eq = self.assertEqual
3196n/a eq(utils.getaddresses(['foo: ;']), [('', '')])
3197n/a eq(utils.getaddresses(
3198n/a ['[]*-- =~$']),
3199n/a [('', ''), ('', ''), ('', '*--')])
3200n/a eq(utils.getaddresses(
3201n/a ['foo: ;', '"Jason R. Mastaler" <jason@dom.ain>']),
3202n/a [('', ''), ('Jason R. Mastaler', 'jason@dom.ain')])
3203n/a
3204n/a def test_getaddresses_embedded_comment(self):
3205n/a """Test proper handling of a nested comment"""
3206n/a eq = self.assertEqual
3207n/a addrs = utils.getaddresses(['User ((nested comment)) <foo@bar.com>'])
3208n/a eq(addrs[0][1], 'foo@bar.com')
3209n/a
3210n/a def test_make_msgid_collisions(self):
3211n/a # Test make_msgid uniqueness, even with multiple threads
3212n/a class MsgidsThread(Thread):
3213n/a def run(self):
3214n/a # generate msgids for 3 seconds
3215n/a self.msgids = []
3216n/a append = self.msgids.append
3217n/a make_msgid = utils.make_msgid
3218n/a clock = time.monotonic
3219n/a tfin = clock() + 3.0
3220n/a while clock() < tfin:
3221n/a append(make_msgid(domain='testdomain-string'))
3222n/a
3223n/a threads = [MsgidsThread() for i in range(5)]
3224n/a with start_threads(threads):
3225n/a pass
3226n/a all_ids = sum([t.msgids for t in threads], [])
3227n/a self.assertEqual(len(set(all_ids)), len(all_ids))
3228n/a
3229n/a def test_utils_quote_unquote(self):
3230n/a eq = self.assertEqual
3231n/a msg = Message()
3232n/a msg.add_header('content-disposition', 'attachment',
3233n/a filename='foo\\wacky"name')
3234n/a eq(msg.get_filename(), 'foo\\wacky"name')
3235n/a
3236n/a def test_get_body_encoding_with_bogus_charset(self):
3237n/a charset = Charset('not a charset')
3238n/a self.assertEqual(charset.get_body_encoding(), 'base64')
3239n/a
3240n/a def test_get_body_encoding_with_uppercase_charset(self):
3241n/a eq = self.assertEqual
3242n/a msg = Message()
3243n/a msg['Content-Type'] = 'text/plain; charset=UTF-8'
3244n/a eq(msg['content-type'], 'text/plain; charset=UTF-8')
3245n/a charsets = msg.get_charsets()
3246n/a eq(len(charsets), 1)
3247n/a eq(charsets[0], 'utf-8')
3248n/a charset = Charset(charsets[0])
3249n/a eq(charset.get_body_encoding(), 'base64')
3250n/a msg.set_payload(b'hello world', charset=charset)
3251n/a eq(msg.get_payload(), 'aGVsbG8gd29ybGQ=\n')
3252n/a eq(msg.get_payload(decode=True), b'hello world')
3253n/a eq(msg['content-transfer-encoding'], 'base64')
3254n/a # Try another one
3255n/a msg = Message()
3256n/a msg['Content-Type'] = 'text/plain; charset="US-ASCII"'
3257n/a charsets = msg.get_charsets()
3258n/a eq(len(charsets), 1)
3259n/a eq(charsets[0], 'us-ascii')
3260n/a charset = Charset(charsets[0])
3261n/a eq(charset.get_body_encoding(), encoders.encode_7or8bit)
3262n/a msg.set_payload('hello world', charset=charset)
3263n/a eq(msg.get_payload(), 'hello world')
3264n/a eq(msg['content-transfer-encoding'], '7bit')
3265n/a
3266n/a def test_charsets_case_insensitive(self):
3267n/a lc = Charset('us-ascii')
3268n/a uc = Charset('US-ASCII')
3269n/a self.assertEqual(lc.get_body_encoding(), uc.get_body_encoding())
3270n/a
3271n/a def test_partial_falls_inside_message_delivery_status(self):
3272n/a eq = self.ndiffAssertEqual
3273n/a # The Parser interface provides chunks of data to FeedParser in 8192
3274n/a # byte gulps. SF bug #1076485 found one of those chunks inside
3275n/a # message/delivery-status header block, which triggered an
3276n/a # unreadline() of NeedMoreData.
3277n/a msg = self._msgobj('msg_43.txt')
3278n/a sfp = StringIO()
3279n/a iterators._structure(msg, sfp)
3280n/a eq(sfp.getvalue(), """\
3281n/amultipart/report
3282n/a text/plain
3283n/a message/delivery-status
3284n/a text/plain
3285n/a text/plain
3286n/a text/plain
3287n/a text/plain
3288n/a text/plain
3289n/a text/plain
3290n/a text/plain
3291n/a text/plain
3292n/a text/plain
3293n/a text/plain
3294n/a text/plain
3295n/a text/plain
3296n/a text/plain
3297n/a text/plain
3298n/a text/plain
3299n/a text/plain
3300n/a text/plain
3301n/a text/plain
3302n/a text/plain
3303n/a text/plain
3304n/a text/plain
3305n/a text/plain
3306n/a text/plain
3307n/a text/plain
3308n/a text/plain
3309n/a text/plain
3310n/a text/rfc822-headers
3311n/a""")
3312n/a
3313n/a def test_make_msgid_domain(self):
3314n/a self.assertEqual(
3315n/a email.utils.make_msgid(domain='testdomain-string')[-19:],
3316n/a '@testdomain-string>')
3317n/a
3318n/a def test_make_msgid_idstring(self):
3319n/a self.assertEqual(
3320n/a email.utils.make_msgid(idstring='test-idstring',
3321n/a domain='testdomain-string')[-33:],
3322n/a '.test-idstring@testdomain-string>')
3323n/a
3324n/a def test_make_msgid_default_domain(self):
3325n/a self.assertTrue(
3326n/a email.utils.make_msgid().endswith(
3327n/a '@' + getfqdn() + '>'))
3328n/a
3329n/a def test_Generator_linend(self):
3330n/a # Issue 14645.
3331n/a with openfile('msg_26.txt', newline='\n') as f:
3332n/a msgtxt = f.read()
3333n/a msgtxt_nl = msgtxt.replace('\r\n', '\n')
3334n/a msg = email.message_from_string(msgtxt)
3335n/a s = StringIO()
3336n/a g = email.generator.Generator(s)
3337n/a g.flatten(msg)
3338n/a self.assertEqual(s.getvalue(), msgtxt_nl)
3339n/a
3340n/a def test_BytesGenerator_linend(self):
3341n/a # Issue 14645.
3342n/a with openfile('msg_26.txt', newline='\n') as f:
3343n/a msgtxt = f.read()
3344n/a msgtxt_nl = msgtxt.replace('\r\n', '\n')
3345n/a msg = email.message_from_string(msgtxt_nl)
3346n/a s = BytesIO()
3347n/a g = email.generator.BytesGenerator(s)
3348n/a g.flatten(msg, linesep='\r\n')
3349n/a self.assertEqual(s.getvalue().decode('ascii'), msgtxt)
3350n/a
3351n/a def test_BytesGenerator_linend_with_non_ascii(self):
3352n/a # Issue 14645.
3353n/a with openfile('msg_26.txt', 'rb') as f:
3354n/a msgtxt = f.read()
3355n/a msgtxt = msgtxt.replace(b'with attachment', b'fo\xf6')
3356n/a msgtxt_nl = msgtxt.replace(b'\r\n', b'\n')
3357n/a msg = email.message_from_bytes(msgtxt_nl)
3358n/a s = BytesIO()
3359n/a g = email.generator.BytesGenerator(s)
3360n/a g.flatten(msg, linesep='\r\n')
3361n/a self.assertEqual(s.getvalue(), msgtxt)
3362n/a
3363n/a def test_mime_classes_policy_argument(self):
3364n/a with openfile('audiotest.au', 'rb') as fp:
3365n/a audiodata = fp.read()
3366n/a with openfile('PyBanner048.gif', 'rb') as fp:
3367n/a bindata = fp.read()
3368n/a classes = [
3369n/a (MIMEApplication, ('',)),
3370n/a (MIMEAudio, (audiodata,)),
3371n/a (MIMEImage, (bindata,)),
3372n/a (MIMEMessage, (Message(),)),
3373n/a (MIMENonMultipart, ('multipart', 'mixed')),
3374n/a (MIMEText, ('',)),
3375n/a ]
3376n/a for cls, constructor in classes:
3377n/a with self.subTest(cls=cls.__name__, policy='compat32'):
3378n/a m = cls(*constructor)
3379n/a self.assertIs(m.policy, email.policy.compat32)
3380n/a with self.subTest(cls=cls.__name__, policy='default'):
3381n/a m = cls(*constructor, policy=email.policy.default)
3382n/a self.assertIs(m.policy, email.policy.default)
3383n/a
3384n/a
3385n/a# Test the iterator/generators
3386n/aclass TestIterators(TestEmailBase):
3387n/a def test_body_line_iterator(self):
3388n/a eq = self.assertEqual
3389n/a neq = self.ndiffAssertEqual
3390n/a # First a simple non-multipart message
3391n/a msg = self._msgobj('msg_01.txt')
3392n/a it = iterators.body_line_iterator(msg)
3393n/a lines = list(it)
3394n/a eq(len(lines), 6)
3395n/a neq(EMPTYSTRING.join(lines), msg.get_payload())
3396n/a # Now a more complicated multipart
3397n/a msg = self._msgobj('msg_02.txt')
3398n/a it = iterators.body_line_iterator(msg)
3399n/a lines = list(it)
3400n/a eq(len(lines), 43)
3401n/a with openfile('msg_19.txt') as fp:
3402n/a neq(EMPTYSTRING.join(lines), fp.read())
3403n/a
3404n/a def test_typed_subpart_iterator(self):
3405n/a eq = self.assertEqual
3406n/a msg = self._msgobj('msg_04.txt')
3407n/a it = iterators.typed_subpart_iterator(msg, 'text')
3408n/a lines = []
3409n/a subparts = 0
3410n/a for subpart in it:
3411n/a subparts += 1
3412n/a lines.append(subpart.get_payload())
3413n/a eq(subparts, 2)
3414n/a eq(EMPTYSTRING.join(lines), """\
3415n/aa simple kind of mirror
3416n/ato reflect upon our own
3417n/aa simple kind of mirror
3418n/ato reflect upon our own
3419n/a""")
3420n/a
3421n/a def test_typed_subpart_iterator_default_type(self):
3422n/a eq = self.assertEqual
3423n/a msg = self._msgobj('msg_03.txt')
3424n/a it = iterators.typed_subpart_iterator(msg, 'text', 'plain')
3425n/a lines = []
3426n/a subparts = 0
3427n/a for subpart in it:
3428n/a subparts += 1
3429n/a lines.append(subpart.get_payload())
3430n/a eq(subparts, 1)
3431n/a eq(EMPTYSTRING.join(lines), """\
3432n/a
3433n/aHi,
3434n/a
3435n/aDo you like this message?
3436n/a
3437n/a-Me
3438n/a""")
3439n/a
3440n/a def test_pushCR_LF(self):
3441n/a '''FeedParser BufferedSubFile.push() assumed it received complete
3442n/a line endings. A CR ending one push() followed by a LF starting
3443n/a the next push() added an empty line.
3444n/a '''
3445n/a imt = [
3446n/a ("a\r \n", 2),
3447n/a ("b", 0),
3448n/a ("c\n", 1),
3449n/a ("", 0),
3450n/a ("d\r\n", 1),
3451n/a ("e\r", 0),
3452n/a ("\nf", 1),
3453n/a ("\r\n", 1),
3454n/a ]
3455n/a from email.feedparser import BufferedSubFile, NeedMoreData
3456n/a bsf = BufferedSubFile()
3457n/a om = []
3458n/a nt = 0
3459n/a for il, n in imt:
3460n/a bsf.push(il)
3461n/a nt += n
3462n/a n1 = 0
3463n/a for ol in iter(bsf.readline, NeedMoreData):
3464n/a om.append(ol)
3465n/a n1 += 1
3466n/a self.assertEqual(n, n1)
3467n/a self.assertEqual(len(om), nt)
3468n/a self.assertEqual(''.join([il for il, n in imt]), ''.join(om))
3469n/a
3470n/a def test_push_random(self):
3471n/a from email.feedparser import BufferedSubFile, NeedMoreData
3472n/a
3473n/a n = 10000
3474n/a chunksize = 5
3475n/a chars = 'abcd \t\r\n'
3476n/a
3477n/a s = ''.join(choice(chars) for i in range(n)) + '\n'
3478n/a target = s.splitlines(True)
3479n/a
3480n/a bsf = BufferedSubFile()
3481n/a lines = []
3482n/a for i in range(0, len(s), chunksize):
3483n/a chunk = s[i:i+chunksize]
3484n/a bsf.push(chunk)
3485n/a lines.extend(iter(bsf.readline, NeedMoreData))
3486n/a self.assertEqual(lines, target)
3487n/a
3488n/a
3489n/aclass TestFeedParsers(TestEmailBase):
3490n/a
3491n/a def parse(self, chunks):
3492n/a feedparser = FeedParser()
3493n/a for chunk in chunks:
3494n/a feedparser.feed(chunk)
3495n/a return feedparser.close()
3496n/a
3497n/a def test_empty_header_name_handled(self):
3498n/a # Issue 19996
3499n/a msg = self.parse("First: val\n: bad\nSecond: val")
3500n/a self.assertEqual(msg['First'], 'val')
3501n/a self.assertEqual(msg['Second'], 'val')
3502n/a
3503n/a def test_newlines(self):
3504n/a m = self.parse(['a:\nb:\rc:\r\nd:\n'])
3505n/a self.assertEqual(m.keys(), ['a', 'b', 'c', 'd'])
3506n/a m = self.parse(['a:\nb:\rc:\r\nd:'])
3507n/a self.assertEqual(m.keys(), ['a', 'b', 'c', 'd'])
3508n/a m = self.parse(['a:\rb', 'c:\n'])
3509n/a self.assertEqual(m.keys(), ['a', 'bc'])
3510n/a m = self.parse(['a:\r', 'b:\n'])
3511n/a self.assertEqual(m.keys(), ['a', 'b'])
3512n/a m = self.parse(['a:\r', '\nb:\n'])
3513n/a self.assertEqual(m.keys(), ['a', 'b'])
3514n/a
3515n/a # Only CR and LF should break header fields
3516n/a m = self.parse(['a:\x85b:\u2028c:\n'])
3517n/a self.assertEqual(m.items(), [('a', '\x85b:\u2028c:')])
3518n/a m = self.parse(['a:\r', 'b:\x85', 'c:\n'])
3519n/a self.assertEqual(m.items(), [('a', ''), ('b', '\x85c:')])
3520n/a
3521n/a def test_long_lines(self):
3522n/a # Expected peak memory use on 32-bit platform: 6*N*M bytes.
3523n/a M, N = 1000, 20000
3524n/a m = self.parse(['a:b\n\n'] + ['x'*M] * N)
3525n/a self.assertEqual(m.items(), [('a', 'b')])
3526n/a self.assertEqual(m.get_payload(), 'x'*M*N)
3527n/a m = self.parse(['a:b\r\r'] + ['x'*M] * N)
3528n/a self.assertEqual(m.items(), [('a', 'b')])
3529n/a self.assertEqual(m.get_payload(), 'x'*M*N)
3530n/a m = self.parse(['a:b\r\r'] + ['x'*M+'\x85'] * N)
3531n/a self.assertEqual(m.items(), [('a', 'b')])
3532n/a self.assertEqual(m.get_payload(), ('x'*M+'\x85')*N)
3533n/a m = self.parse(['a:\r', 'b: '] + ['x'*M] * N)
3534n/a self.assertEqual(m.items(), [('a', ''), ('b', 'x'*M*N)])
3535n/a
3536n/a
3537n/aclass TestParsers(TestEmailBase):
3538n/a
3539n/a def test_header_parser(self):
3540n/a eq = self.assertEqual
3541n/a # Parse only the headers of a complex multipart MIME document
3542n/a with openfile('msg_02.txt') as fp:
3543n/a msg = HeaderParser().parse(fp)
3544n/a eq(msg['from'], 'ppp-request@zzz.org')
3545n/a eq(msg['to'], 'ppp@zzz.org')
3546n/a eq(msg.get_content_type(), 'multipart/mixed')
3547n/a self.assertFalse(msg.is_multipart())
3548n/a self.assertIsInstance(msg.get_payload(), str)
3549n/a
3550n/a def test_bytes_header_parser(self):
3551n/a eq = self.assertEqual
3552n/a # Parse only the headers of a complex multipart MIME document
3553n/a with openfile('msg_02.txt', 'rb') as fp:
3554n/a msg = email.parser.BytesHeaderParser().parse(fp)
3555n/a eq(msg['from'], 'ppp-request@zzz.org')
3556n/a eq(msg['to'], 'ppp@zzz.org')
3557n/a eq(msg.get_content_type(), 'multipart/mixed')
3558n/a self.assertFalse(msg.is_multipart())
3559n/a self.assertIsInstance(msg.get_payload(), str)
3560n/a self.assertIsInstance(msg.get_payload(decode=True), bytes)
3561n/a
3562n/a def test_bytes_parser_does_not_close_file(self):
3563n/a with openfile('msg_02.txt', 'rb') as fp:
3564n/a email.parser.BytesParser().parse(fp)
3565n/a self.assertFalse(fp.closed)
3566n/a
3567n/a def test_bytes_parser_on_exception_does_not_close_file(self):
3568n/a with openfile('msg_15.txt', 'rb') as fp:
3569n/a bytesParser = email.parser.BytesParser
3570n/a self.assertRaises(email.errors.StartBoundaryNotFoundDefect,
3571n/a bytesParser(policy=email.policy.strict).parse,
3572n/a fp)
3573n/a self.assertFalse(fp.closed)
3574n/a
3575n/a def test_parser_does_not_close_file(self):
3576n/a with openfile('msg_02.txt', 'r') as fp:
3577n/a email.parser.Parser().parse(fp)
3578n/a self.assertFalse(fp.closed)
3579n/a
3580n/a def test_parser_on_exception_does_not_close_file(self):
3581n/a with openfile('msg_15.txt', 'r') as fp:
3582n/a parser = email.parser.Parser
3583n/a self.assertRaises(email.errors.StartBoundaryNotFoundDefect,
3584n/a parser(policy=email.policy.strict).parse, fp)
3585n/a self.assertFalse(fp.closed)
3586n/a
3587n/a def test_whitespace_continuation(self):
3588n/a eq = self.assertEqual
3589n/a # This message contains a line after the Subject: header that has only
3590n/a # whitespace, but it is not empty!
3591n/a msg = email.message_from_string("""\
3592n/aFrom: aperson@dom.ain
3593n/aTo: bperson@dom.ain
3594n/aSubject: the next line has a space on it
3595n/a\x20
3596n/aDate: Mon, 8 Apr 2002 15:09:19 -0400
3597n/aMessage-ID: spam
3598n/a
3599n/aHere's the message body
3600n/a""")
3601n/a eq(msg['subject'], 'the next line has a space on it\n ')
3602n/a eq(msg['message-id'], 'spam')
3603n/a eq(msg.get_payload(), "Here's the message body\n")
3604n/a
3605n/a def test_whitespace_continuation_last_header(self):
3606n/a eq = self.assertEqual
3607n/a # Like the previous test, but the subject line is the last
3608n/a # header.
3609n/a msg = email.message_from_string("""\
3610n/aFrom: aperson@dom.ain
3611n/aTo: bperson@dom.ain
3612n/aDate: Mon, 8 Apr 2002 15:09:19 -0400
3613n/aMessage-ID: spam
3614n/aSubject: the next line has a space on it
3615n/a\x20
3616n/a
3617n/aHere's the message body
3618n/a""")
3619n/a eq(msg['subject'], 'the next line has a space on it\n ')
3620n/a eq(msg['message-id'], 'spam')
3621n/a eq(msg.get_payload(), "Here's the message body\n")
3622n/a
3623n/a def test_crlf_separation(self):
3624n/a eq = self.assertEqual
3625n/a with openfile('msg_26.txt', newline='\n') as fp:
3626n/a msg = Parser().parse(fp)
3627n/a eq(len(msg.get_payload()), 2)
3628n/a part1 = msg.get_payload(0)
3629n/a eq(part1.get_content_type(), 'text/plain')
3630n/a eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
3631n/a part2 = msg.get_payload(1)
3632n/a eq(part2.get_content_type(), 'application/riscos')
3633n/a
3634n/a def test_crlf_flatten(self):
3635n/a # Using newline='\n' preserves the crlfs in this input file.
3636n/a with openfile('msg_26.txt', newline='\n') as fp:
3637n/a text = fp.read()
3638n/a msg = email.message_from_string(text)
3639n/a s = StringIO()
3640n/a g = Generator(s)
3641n/a g.flatten(msg, linesep='\r\n')
3642n/a self.assertEqual(s.getvalue(), text)
3643n/a
3644n/a maxDiff = None
3645n/a
3646n/a def test_multipart_digest_with_extra_mime_headers(self):
3647n/a eq = self.assertEqual
3648n/a neq = self.ndiffAssertEqual
3649n/a with openfile('msg_28.txt') as fp:
3650n/a msg = email.message_from_file(fp)
3651n/a # Structure is:
3652n/a # multipart/digest
3653n/a # message/rfc822
3654n/a # text/plain
3655n/a # message/rfc822
3656n/a # text/plain
3657n/a eq(msg.is_multipart(), 1)
3658n/a eq(len(msg.get_payload()), 2)
3659n/a part1 = msg.get_payload(0)
3660n/a eq(part1.get_content_type(), 'message/rfc822')
3661n/a eq(part1.is_multipart(), 1)
3662n/a eq(len(part1.get_payload()), 1)
3663n/a part1a = part1.get_payload(0)
3664n/a eq(part1a.is_multipart(), 0)
3665n/a eq(part1a.get_content_type(), 'text/plain')
3666n/a neq(part1a.get_payload(), 'message 1\n')
3667n/a # next message/rfc822
3668n/a part2 = msg.get_payload(1)
3669n/a eq(part2.get_content_type(), 'message/rfc822')
3670n/a eq(part2.is_multipart(), 1)
3671n/a eq(len(part2.get_payload()), 1)
3672n/a part2a = part2.get_payload(0)
3673n/a eq(part2a.is_multipart(), 0)
3674n/a eq(part2a.get_content_type(), 'text/plain')
3675n/a neq(part2a.get_payload(), 'message 2\n')
3676n/a
3677n/a def test_three_lines(self):
3678n/a # A bug report by Andrew McNamara
3679n/a lines = ['From: Andrew Person <aperson@dom.ain',
3680n/a 'Subject: Test',
3681n/a 'Date: Tue, 20 Aug 2002 16:43:45 +1000']
3682n/a msg = email.message_from_string(NL.join(lines))
3683n/a self.assertEqual(msg['date'], 'Tue, 20 Aug 2002 16:43:45 +1000')
3684n/a
3685n/a def test_strip_line_feed_and_carriage_return_in_headers(self):
3686n/a eq = self.assertEqual
3687n/a # For [ 1002475 ] email message parser doesn't handle \r\n correctly
3688n/a value1 = 'text'
3689n/a value2 = 'more text'
3690n/a m = 'Header: %s\r\nNext-Header: %s\r\n\r\nBody\r\n\r\n' % (
3691n/a value1, value2)
3692n/a msg = email.message_from_string(m)
3693n/a eq(msg.get('Header'), value1)
3694n/a eq(msg.get('Next-Header'), value2)
3695n/a
3696n/a def test_rfc2822_header_syntax(self):
3697n/a eq = self.assertEqual
3698n/a m = '>From: foo\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
3699n/a msg = email.message_from_string(m)
3700n/a eq(len(msg), 3)
3701n/a eq(sorted(field for field in msg), ['!"#QUX;~', '>From', 'From'])
3702n/a eq(msg.get_payload(), 'body')
3703n/a
3704n/a def test_rfc2822_space_not_allowed_in_header(self):
3705n/a eq = self.assertEqual
3706n/a m = '>From foo@example.com 11:25:53\nFrom: bar\n!"#QUX;~: zoo\n\nbody'
3707n/a msg = email.message_from_string(m)
3708n/a eq(len(msg.keys()), 0)
3709n/a
3710n/a def test_rfc2822_one_character_header(self):
3711n/a eq = self.assertEqual
3712n/a m = 'A: first header\nB: second header\nCC: third header\n\nbody'
3713n/a msg = email.message_from_string(m)
3714n/a headers = msg.keys()
3715n/a headers.sort()
3716n/a eq(headers, ['A', 'B', 'CC'])
3717n/a eq(msg.get_payload(), 'body')
3718n/a
3719n/a def test_CRLFLF_at_end_of_part(self):
3720n/a # issue 5610: feedparser should not eat two chars from body part ending
3721n/a # with "\r\n\n".
3722n/a m = (
3723n/a "From: foo@bar.com\n"
3724n/a "To: baz\n"
3725n/a "Mime-Version: 1.0\n"
3726n/a "Content-Type: multipart/mixed; boundary=BOUNDARY\n"
3727n/a "\n"
3728n/a "--BOUNDARY\n"
3729n/a "Content-Type: text/plain\n"
3730n/a "\n"
3731n/a "body ending with CRLF newline\r\n"
3732n/a "\n"
3733n/a "--BOUNDARY--\n"
3734n/a )
3735n/a msg = email.message_from_string(m)
3736n/a self.assertTrue(msg.get_payload(0).get_payload().endswith('\r\n'))
3737n/a
3738n/a
3739n/aclass Test8BitBytesHandling(TestEmailBase):
3740n/a # In Python3 all input is string, but that doesn't work if the actual input
3741n/a # uses an 8bit transfer encoding. To hack around that, in email 5.1 we
3742n/a # decode byte streams using the surrogateescape error handler, and
3743n/a # reconvert to binary at appropriate places if we detect surrogates. This
3744n/a # doesn't allow us to transform headers with 8bit bytes (they get munged),
3745n/a # but it does allow us to parse and preserve them, and to decode body
3746n/a # parts that use an 8bit CTE.
3747n/a
3748n/a bodytest_msg = textwrap.dedent("""\
3749n/a From: foo@bar.com
3750n/a To: baz
3751n/a Mime-Version: 1.0
3752n/a Content-Type: text/plain; charset={charset}
3753n/a Content-Transfer-Encoding: {cte}
3754n/a
3755n/a {bodyline}
3756n/a """)
3757n/a
3758n/a def test_known_8bit_CTE(self):
3759n/a m = self.bodytest_msg.format(charset='utf-8',
3760n/a cte='8bit',
3761n/a bodyline='pöstal').encode('utf-8')
3762n/a msg = email.message_from_bytes(m)
3763n/a self.assertEqual(msg.get_payload(), "pöstal\n")
3764n/a self.assertEqual(msg.get_payload(decode=True),
3765n/a "pöstal\n".encode('utf-8'))
3766n/a
3767n/a def test_unknown_8bit_CTE(self):
3768n/a m = self.bodytest_msg.format(charset='notavalidcharset',
3769n/a cte='8bit',
3770n/a bodyline='pöstal').encode('utf-8')
3771n/a msg = email.message_from_bytes(m)
3772n/a self.assertEqual(msg.get_payload(), "p\uFFFD\uFFFDstal\n")
3773n/a self.assertEqual(msg.get_payload(decode=True),
3774n/a "pöstal\n".encode('utf-8'))
3775n/a
3776n/a def test_8bit_in_quopri_body(self):
3777n/a # This is non-RFC compliant data...without 'decode' the library code
3778n/a # decodes the body using the charset from the headers, and because the
3779n/a # source byte really is utf-8 this works. This is likely to fail
3780n/a # against real dirty data (ie: produce mojibake), but the data is
3781n/a # invalid anyway so it is as good a guess as any. But this means that
3782n/a # this test just confirms the current behavior; that behavior is not
3783n/a # necessarily the best possible behavior. With 'decode' it is
3784n/a # returning the raw bytes, so that test should be of correct behavior,
3785n/a # or at least produce the same result that email4 did.
3786n/a m = self.bodytest_msg.format(charset='utf-8',
3787n/a cte='quoted-printable',
3788n/a bodyline='p=C3=B6stál').encode('utf-8')
3789n/a msg = email.message_from_bytes(m)
3790n/a self.assertEqual(msg.get_payload(), 'p=C3=B6stál\n')
3791n/a self.assertEqual(msg.get_payload(decode=True),
3792n/a 'pöstál\n'.encode('utf-8'))
3793n/a
3794n/a def test_invalid_8bit_in_non_8bit_cte_uses_replace(self):
3795n/a # This is similar to the previous test, but proves that if the 8bit
3796n/a # byte is undecodeable in the specified charset, it gets replaced
3797n/a # by the unicode 'unknown' character. Again, this may or may not
3798n/a # be the ideal behavior. Note that if decode=False none of the
3799n/a # decoders will get involved, so this is the only test we need
3800n/a # for this behavior.
3801n/a m = self.bodytest_msg.format(charset='ascii',
3802n/a cte='quoted-printable',
3803n/a bodyline='p=C3=B6stál').encode('utf-8')
3804n/a msg = email.message_from_bytes(m)
3805n/a self.assertEqual(msg.get_payload(), 'p=C3=B6st\uFFFD\uFFFDl\n')
3806n/a self.assertEqual(msg.get_payload(decode=True),
3807n/a 'pöstál\n'.encode('utf-8'))
3808n/a
3809n/a # test_defect_handling:test_invalid_chars_in_base64_payload
3810n/a def test_8bit_in_base64_body(self):
3811n/a # If we get 8bit bytes in a base64 body, we can just ignore them
3812n/a # as being outside the base64 alphabet and decode anyway. But
3813n/a # we register a defect.
3814n/a m = self.bodytest_msg.format(charset='utf-8',
3815n/a cte='base64',
3816n/a bodyline='cMO2c3RhbAá=').encode('utf-8')
3817n/a msg = email.message_from_bytes(m)
3818n/a self.assertEqual(msg.get_payload(decode=True),
3819n/a 'pöstal'.encode('utf-8'))
3820n/a self.assertIsInstance(msg.defects[0],
3821n/a errors.InvalidBase64CharactersDefect)
3822n/a
3823n/a def test_8bit_in_uuencode_body(self):
3824n/a # Sticking an 8bit byte in a uuencode block makes it undecodable by
3825n/a # normal means, so the block is returned undecoded, but as bytes.
3826n/a m = self.bodytest_msg.format(charset='utf-8',
3827n/a cte='uuencode',
3828n/a bodyline='<,.V<W1A; á ').encode('utf-8')
3829n/a msg = email.message_from_bytes(m)
3830n/a self.assertEqual(msg.get_payload(decode=True),
3831n/a '<,.V<W1A; á \n'.encode('utf-8'))
3832n/a
3833n/a
3834n/a headertest_headers = (
3835n/a ('From: foo@bar.com', ('From', 'foo@bar.com')),
3836n/a ('To: báz', ('To', '=?unknown-8bit?q?b=C3=A1z?=')),
3837n/a ('Subject: Maintenant je vous présente mon collègue, le pouf célèbre\n'
3838n/a '\tJean de Baddie',
3839n/a ('Subject', '=?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
3840n/a 'coll=C3=A8gue=2C_le_pouf_c=C3=A9l=C3=A8bre?=\n'
3841n/a ' =?unknown-8bit?q?_Jean_de_Baddie?=')),
3842n/a ('From: göst', ('From', '=?unknown-8bit?b?Z8O2c3Q=?=')),
3843n/a )
3844n/a headertest_msg = ('\n'.join([src for (src, _) in headertest_headers]) +
3845n/a '\nYes, they are flying.\n').encode('utf-8')
3846n/a
3847n/a def test_get_8bit_header(self):
3848n/a msg = email.message_from_bytes(self.headertest_msg)
3849n/a self.assertEqual(str(msg.get('to')), 'b\uFFFD\uFFFDz')
3850n/a self.assertEqual(str(msg['to']), 'b\uFFFD\uFFFDz')
3851n/a
3852n/a def test_print_8bit_headers(self):
3853n/a msg = email.message_from_bytes(self.headertest_msg)
3854n/a self.assertEqual(str(msg),
3855n/a textwrap.dedent("""\
3856n/a From: {}
3857n/a To: {}
3858n/a Subject: {}
3859n/a From: {}
3860n/a
3861n/a Yes, they are flying.
3862n/a """).format(*[expected[1] for (_, expected) in
3863n/a self.headertest_headers]))
3864n/a
3865n/a def test_values_with_8bit_headers(self):
3866n/a msg = email.message_from_bytes(self.headertest_msg)
3867n/a self.assertListEqual([str(x) for x in msg.values()],
3868n/a ['foo@bar.com',
3869n/a 'b\uFFFD\uFFFDz',
3870n/a 'Maintenant je vous pr\uFFFD\uFFFDsente mon '
3871n/a 'coll\uFFFD\uFFFDgue, le pouf '
3872n/a 'c\uFFFD\uFFFDl\uFFFD\uFFFDbre\n'
3873n/a '\tJean de Baddie',
3874n/a "g\uFFFD\uFFFDst"])
3875n/a
3876n/a def test_items_with_8bit_headers(self):
3877n/a msg = email.message_from_bytes(self.headertest_msg)
3878n/a self.assertListEqual([(str(x), str(y)) for (x, y) in msg.items()],
3879n/a [('From', 'foo@bar.com'),
3880n/a ('To', 'b\uFFFD\uFFFDz'),
3881n/a ('Subject', 'Maintenant je vous '
3882n/a 'pr\uFFFD\uFFFDsente '
3883n/a 'mon coll\uFFFD\uFFFDgue, le pouf '
3884n/a 'c\uFFFD\uFFFDl\uFFFD\uFFFDbre\n'
3885n/a '\tJean de Baddie'),
3886n/a ('From', 'g\uFFFD\uFFFDst')])
3887n/a
3888n/a def test_get_all_with_8bit_headers(self):
3889n/a msg = email.message_from_bytes(self.headertest_msg)
3890n/a self.assertListEqual([str(x) for x in msg.get_all('from')],
3891n/a ['foo@bar.com',
3892n/a 'g\uFFFD\uFFFDst'])
3893n/a
3894n/a def test_get_content_type_with_8bit(self):
3895n/a msg = email.message_from_bytes(textwrap.dedent("""\
3896n/a Content-Type: text/pl\xA7in; charset=utf-8
3897n/a """).encode('latin-1'))
3898n/a self.assertEqual(msg.get_content_type(), "text/pl\uFFFDin")
3899n/a self.assertEqual(msg.get_content_maintype(), "text")
3900n/a self.assertEqual(msg.get_content_subtype(), "pl\uFFFDin")
3901n/a
3902n/a # test_headerregistry.TestContentTypeHeader.non_ascii_in_params
3903n/a def test_get_params_with_8bit(self):
3904n/a msg = email.message_from_bytes(
3905n/a 'X-Header: foo=\xa7ne; b\xa7r=two; baz=three\n'.encode('latin-1'))
3906n/a self.assertEqual(msg.get_params(header='x-header'),
3907n/a [('foo', '\uFFFDne'), ('b\uFFFDr', 'two'), ('baz', 'three')])
3908n/a self.assertEqual(msg.get_param('Foo', header='x-header'), '\uFFFdne')
3909n/a # XXX: someday you might be able to get 'b\xa7r', for now you can't.
3910n/a self.assertEqual(msg.get_param('b\xa7r', header='x-header'), None)
3911n/a
3912n/a # test_headerregistry.TestContentTypeHeader.non_ascii_in_rfc2231_value
3913n/a def test_get_rfc2231_params_with_8bit(self):
3914n/a msg = email.message_from_bytes(textwrap.dedent("""\
3915n/a Content-Type: text/plain; charset=us-ascii;
3916n/a title*=us-ascii'en'This%20is%20not%20f\xa7n"""
3917n/a ).encode('latin-1'))
3918n/a self.assertEqual(msg.get_param('title'),
3919n/a ('us-ascii', 'en', 'This is not f\uFFFDn'))
3920n/a
3921n/a def test_set_rfc2231_params_with_8bit(self):
3922n/a msg = email.message_from_bytes(textwrap.dedent("""\
3923n/a Content-Type: text/plain; charset=us-ascii;
3924n/a title*=us-ascii'en'This%20is%20not%20f\xa7n"""
3925n/a ).encode('latin-1'))
3926n/a msg.set_param('title', 'test')
3927n/a self.assertEqual(msg.get_param('title'), 'test')
3928n/a
3929n/a def test_del_rfc2231_params_with_8bit(self):
3930n/a msg = email.message_from_bytes(textwrap.dedent("""\
3931n/a Content-Type: text/plain; charset=us-ascii;
3932n/a title*=us-ascii'en'This%20is%20not%20f\xa7n"""
3933n/a ).encode('latin-1'))
3934n/a msg.del_param('title')
3935n/a self.assertEqual(msg.get_param('title'), None)
3936n/a self.assertEqual(msg.get_content_maintype(), 'text')
3937n/a
3938n/a def test_get_payload_with_8bit_cte_header(self):
3939n/a msg = email.message_from_bytes(textwrap.dedent("""\
3940n/a Content-Transfer-Encoding: b\xa7se64
3941n/a Content-Type: text/plain; charset=latin-1
3942n/a
3943n/a payload
3944n/a """).encode('latin-1'))
3945n/a self.assertEqual(msg.get_payload(), 'payload\n')
3946n/a self.assertEqual(msg.get_payload(decode=True), b'payload\n')
3947n/a
3948n/a non_latin_bin_msg = textwrap.dedent("""\
3949n/a From: foo@bar.com
3950n/a To: báz
3951n/a Subject: Maintenant je vous présente mon collègue, le pouf célèbre
3952n/a \tJean de Baddie
3953n/a Mime-Version: 1.0
3954n/a Content-Type: text/plain; charset="utf-8"
3955n/a Content-Transfer-Encoding: 8bit
3956n/a
3957n/a Да, они летят.
3958n/a """).encode('utf-8')
3959n/a
3960n/a def test_bytes_generator(self):
3961n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
3962n/a out = BytesIO()
3963n/a email.generator.BytesGenerator(out).flatten(msg)
3964n/a self.assertEqual(out.getvalue(), self.non_latin_bin_msg)
3965n/a
3966n/a def test_bytes_generator_handles_None_body(self):
3967n/a #Issue 11019
3968n/a msg = email.message.Message()
3969n/a out = BytesIO()
3970n/a email.generator.BytesGenerator(out).flatten(msg)
3971n/a self.assertEqual(out.getvalue(), b"\n")
3972n/a
3973n/a non_latin_bin_msg_as7bit_wrapped = textwrap.dedent("""\
3974n/a From: foo@bar.com
3975n/a To: =?unknown-8bit?q?b=C3=A1z?=
3976n/a Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_coll=C3=A8gue?=
3977n/a =?unknown-8bit?q?=2C_le_pouf_c=C3=A9l=C3=A8bre?=
3978n/a =?unknown-8bit?q?_Jean_de_Baddie?=
3979n/a Mime-Version: 1.0
3980n/a Content-Type: text/plain; charset="utf-8"
3981n/a Content-Transfer-Encoding: base64
3982n/a
3983n/a 0JTQsCwg0L7QvdC4INC70LXRgtGP0YIuCg==
3984n/a """)
3985n/a
3986n/a def test_generator_handles_8bit(self):
3987n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
3988n/a out = StringIO()
3989n/a email.generator.Generator(out).flatten(msg)
3990n/a self.assertEqual(out.getvalue(), self.non_latin_bin_msg_as7bit_wrapped)
3991n/a
3992n/a def test_str_generator_should_not_mutate_msg_when_handling_8bit(self):
3993n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
3994n/a out = BytesIO()
3995n/a BytesGenerator(out).flatten(msg)
3996n/a orig_value = out.getvalue()
3997n/a Generator(StringIO()).flatten(msg) # Should not mutate msg!
3998n/a out = BytesIO()
3999n/a BytesGenerator(out).flatten(msg)
4000n/a self.assertEqual(out.getvalue(), orig_value)
4001n/a
4002n/a def test_bytes_generator_with_unix_from(self):
4003n/a # The unixfrom contains a current date, so we can't check it
4004n/a # literally. Just make sure the first word is 'From' and the
4005n/a # rest of the message matches the input.
4006n/a msg = email.message_from_bytes(self.non_latin_bin_msg)
4007n/a out = BytesIO()
4008n/a email.generator.BytesGenerator(out).flatten(msg, unixfrom=True)
4009n/a lines = out.getvalue().split(b'\n')
4010n/a self.assertEqual(lines[0].split()[0], b'From')
4011n/a self.assertEqual(b'\n'.join(lines[1:]), self.non_latin_bin_msg)
4012n/a
4013n/a non_latin_bin_msg_as7bit = non_latin_bin_msg_as7bit_wrapped.split('\n')
4014n/a non_latin_bin_msg_as7bit[2:4] = [
4015n/a 'Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
4016n/a 'coll=C3=A8gue=2C_le_pouf_c=C3=A9l=C3=A8bre?=']
4017n/a non_latin_bin_msg_as7bit = '\n'.join(non_latin_bin_msg_as7bit)
4018n/a
4019n/a def test_message_from_binary_file(self):
4020n/a fn = 'test.msg'
4021n/a self.addCleanup(unlink, fn)
4022n/a with open(fn, 'wb') as testfile:
4023n/a testfile.write(self.non_latin_bin_msg)
4024n/a with open(fn, 'rb') as testfile:
4025n/a m = email.parser.BytesParser().parse(testfile)
4026n/a self.assertEqual(str(m), self.non_latin_bin_msg_as7bit)
4027n/a
4028n/a latin_bin_msg = textwrap.dedent("""\
4029n/a From: foo@bar.com
4030n/a To: Dinsdale
4031n/a Subject: Nudge nudge, wink, wink
4032n/a Mime-Version: 1.0
4033n/a Content-Type: text/plain; charset="latin-1"
4034n/a Content-Transfer-Encoding: 8bit
4035n/a
4036n/a oh là là, know what I mean, know what I mean?
4037n/a """).encode('latin-1')
4038n/a
4039n/a latin_bin_msg_as7bit = textwrap.dedent("""\
4040n/a From: foo@bar.com
4041n/a To: Dinsdale
4042n/a Subject: Nudge nudge, wink, wink
4043n/a Mime-Version: 1.0
4044n/a Content-Type: text/plain; charset="iso-8859-1"
4045n/a Content-Transfer-Encoding: quoted-printable
4046n/a
4047n/a oh l=E0 l=E0, know what I mean, know what I mean?
4048n/a """)
4049n/a
4050n/a def test_string_generator_reencodes_to_quopri_when_appropriate(self):
4051n/a m = email.message_from_bytes(self.latin_bin_msg)
4052n/a self.assertEqual(str(m), self.latin_bin_msg_as7bit)
4053n/a
4054n/a def test_decoded_generator_emits_unicode_body(self):
4055n/a m = email.message_from_bytes(self.latin_bin_msg)
4056n/a out = StringIO()
4057n/a email.generator.DecodedGenerator(out).flatten(m)
4058n/a #DecodedHeader output contains an extra blank line compared
4059n/a #to the input message. RDM: not sure if this is a bug or not,
4060n/a #but it is not specific to the 8bit->7bit conversion.
4061n/a self.assertEqual(out.getvalue(),
4062n/a self.latin_bin_msg.decode('latin-1')+'\n')
4063n/a
4064n/a def test_bytes_feedparser(self):
4065n/a bfp = email.feedparser.BytesFeedParser()
4066n/a for i in range(0, len(self.latin_bin_msg), 10):
4067n/a bfp.feed(self.latin_bin_msg[i:i+10])
4068n/a m = bfp.close()
4069n/a self.assertEqual(str(m), self.latin_bin_msg_as7bit)
4070n/a
4071n/a def test_crlf_flatten(self):
4072n/a with openfile('msg_26.txt', 'rb') as fp:
4073n/a text = fp.read()
4074n/a msg = email.message_from_bytes(text)
4075n/a s = BytesIO()
4076n/a g = email.generator.BytesGenerator(s)
4077n/a g.flatten(msg, linesep='\r\n')
4078n/a self.assertEqual(s.getvalue(), text)
4079n/a
4080n/a def test_8bit_multipart(self):
4081n/a # Issue 11605
4082n/a source = textwrap.dedent("""\
4083n/a Date: Fri, 18 Mar 2011 17:15:43 +0100
4084n/a To: foo@example.com
4085n/a From: foodwatch-Newsletter <bar@example.com>
4086n/a Subject: Aktuelles zu Japan, Klonfleisch und Smiley-System
4087n/a Message-ID: <76a486bee62b0d200f33dc2ca08220ad@localhost.localdomain>
4088n/a MIME-Version: 1.0
4089n/a Content-Type: multipart/alternative;
4090n/a boundary="b1_76a486bee62b0d200f33dc2ca08220ad"
4091n/a
4092n/a --b1_76a486bee62b0d200f33dc2ca08220ad
4093n/a Content-Type: text/plain; charset="utf-8"
4094n/a Content-Transfer-Encoding: 8bit
4095n/a
4096n/a Guten Tag, ,
4097n/a
4098n/a mit großer Betroffenheit verfolgen auch wir im foodwatch-Team die
4099n/a Nachrichten aus Japan.
4100n/a
4101n/a
4102n/a --b1_76a486bee62b0d200f33dc2ca08220ad
4103n/a Content-Type: text/html; charset="utf-8"
4104n/a Content-Transfer-Encoding: 8bit
4105n/a
4106n/a <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
4107n/a "http://www.w3.org/TR/html4/loose.dtd">
4108n/a <html lang="de">
4109n/a <head>
4110n/a <title>foodwatch - Newsletter</title>
4111n/a </head>
4112n/a <body>
4113n/a <p>mit gro&szlig;er Betroffenheit verfolgen auch wir im foodwatch-Team
4114n/a die Nachrichten aus Japan.</p>
4115n/a </body>
4116n/a </html>
4117n/a --b1_76a486bee62b0d200f33dc2ca08220ad--
4118n/a
4119n/a """).encode('utf-8')
4120n/a msg = email.message_from_bytes(source)
4121n/a s = BytesIO()
4122n/a g = email.generator.BytesGenerator(s)
4123n/a g.flatten(msg)
4124n/a self.assertEqual(s.getvalue(), source)
4125n/a
4126n/a def test_bytes_generator_b_encoding_linesep(self):
4127n/a # Issue 14062: b encoding was tacking on an extra \n.
4128n/a m = Message()
4129n/a # This has enough non-ascii that it should always end up b encoded.
4130n/a m['Subject'] = Header('žluťoučký kůň')
4131n/a s = BytesIO()
4132n/a g = email.generator.BytesGenerator(s)
4133n/a g.flatten(m, linesep='\r\n')
4134n/a self.assertEqual(
4135n/a s.getvalue(),
4136n/a b'Subject: =?utf-8?b?xb5sdcWlb3XEjWvDvSBrxa/FiA==?=\r\n\r\n')
4137n/a
4138n/a def test_generator_b_encoding_linesep(self):
4139n/a # Since this broke in ByteGenerator, test Generator for completeness.
4140n/a m = Message()
4141n/a # This has enough non-ascii that it should always end up b encoded.
4142n/a m['Subject'] = Header('žluťoučký kůň')
4143n/a s = StringIO()
4144n/a g = email.generator.Generator(s)
4145n/a g.flatten(m, linesep='\r\n')
4146n/a self.assertEqual(
4147n/a s.getvalue(),
4148n/a 'Subject: =?utf-8?b?xb5sdcWlb3XEjWvDvSBrxa/FiA==?=\r\n\r\n')
4149n/a
4150n/a maxDiff = None
4151n/a
4152n/a
4153n/aclass BaseTestBytesGeneratorIdempotent:
4154n/a
4155n/a maxDiff = None
4156n/a
4157n/a def _msgobj(self, filename):
4158n/a with openfile(filename, 'rb') as fp:
4159n/a data = fp.read()
4160n/a data = self.normalize_linesep_regex.sub(self.blinesep, data)
4161n/a msg = email.message_from_bytes(data)
4162n/a return msg, data
4163n/a
4164n/a def _idempotent(self, msg, data, unixfrom=False):
4165n/a b = BytesIO()
4166n/a g = email.generator.BytesGenerator(b, maxheaderlen=0)
4167n/a g.flatten(msg, unixfrom=unixfrom, linesep=self.linesep)
4168n/a self.assertEqual(data, b.getvalue())
4169n/a
4170n/a
4171n/aclass TestBytesGeneratorIdempotentNL(BaseTestBytesGeneratorIdempotent,
4172n/a TestIdempotent):
4173n/a linesep = '\n'
4174n/a blinesep = b'\n'
4175n/a normalize_linesep_regex = re.compile(br'\r\n')
4176n/a
4177n/a
4178n/aclass TestBytesGeneratorIdempotentCRLF(BaseTestBytesGeneratorIdempotent,
4179n/a TestIdempotent):
4180n/a linesep = '\r\n'
4181n/a blinesep = b'\r\n'
4182n/a normalize_linesep_regex = re.compile(br'(?<!\r)\n')
4183n/a
4184n/a
4185n/aclass TestBase64(unittest.TestCase):
4186n/a def test_len(self):
4187n/a eq = self.assertEqual
4188n/a eq(base64mime.header_length('hello'),
4189n/a len(base64mime.body_encode(b'hello', eol='')))
4190n/a for size in range(15):
4191n/a if size == 0 : bsize = 0
4192n/a elif size <= 3 : bsize = 4
4193n/a elif size <= 6 : bsize = 8
4194n/a elif size <= 9 : bsize = 12
4195n/a elif size <= 12: bsize = 16
4196n/a else : bsize = 20
4197n/a eq(base64mime.header_length('x' * size), bsize)
4198n/a
4199n/a def test_decode(self):
4200n/a eq = self.assertEqual
4201n/a eq(base64mime.decode(''), b'')
4202n/a eq(base64mime.decode('aGVsbG8='), b'hello')
4203n/a
4204n/a def test_encode(self):
4205n/a eq = self.assertEqual
4206n/a eq(base64mime.body_encode(b''), b'')
4207n/a eq(base64mime.body_encode(b'hello'), 'aGVsbG8=\n')
4208n/a # Test the binary flag
4209n/a eq(base64mime.body_encode(b'hello\n'), 'aGVsbG8K\n')
4210n/a # Test the maxlinelen arg
4211n/a eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40), """\
4212n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
4213n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
4214n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
4215n/aeHh4eCB4eHh4IA==
4216n/a""")
4217n/a # Test the eol argument
4218n/a eq(base64mime.body_encode(b'xxxx ' * 20, maxlinelen=40, eol='\r\n'),
4219n/a """\
4220n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
4221n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
4222n/aeHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
4223n/aeHh4eCB4eHh4IA==\r
4224n/a""")
4225n/a
4226n/a def test_header_encode(self):
4227n/a eq = self.assertEqual
4228n/a he = base64mime.header_encode
4229n/a eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
4230n/a eq(he('hello\r\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
4231n/a eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
4232n/a # Test the charset option
4233n/a eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
4234n/a eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
4235n/a
4236n/a
4237n/a
4238n/aclass TestQuopri(unittest.TestCase):
4239n/a def setUp(self):
4240n/a # Set of characters (as byte integers) that don't need to be encoded
4241n/a # in headers.
4242n/a self.hlit = list(chain(
4243n/a range(ord('a'), ord('z') + 1),
4244n/a range(ord('A'), ord('Z') + 1),
4245n/a range(ord('0'), ord('9') + 1),
4246n/a (c for c in b'!*+-/')))
4247n/a # Set of characters (as byte integers) that do need to be encoded in
4248n/a # headers.
4249n/a self.hnon = [c for c in range(256) if c not in self.hlit]
4250n/a assert len(self.hlit) + len(self.hnon) == 256
4251n/a # Set of characters (as byte integers) that don't need to be encoded
4252n/a # in bodies.
4253n/a self.blit = list(range(ord(' '), ord('~') + 1))
4254n/a self.blit.append(ord('\t'))
4255n/a self.blit.remove(ord('='))
4256n/a # Set of characters (as byte integers) that do need to be encoded in
4257n/a # bodies.
4258n/a self.bnon = [c for c in range(256) if c not in self.blit]
4259n/a assert len(self.blit) + len(self.bnon) == 256
4260n/a
4261n/a def test_quopri_header_check(self):
4262n/a for c in self.hlit:
4263n/a self.assertFalse(quoprimime.header_check(c),
4264n/a 'Should not be header quopri encoded: %s' % chr(c))
4265n/a for c in self.hnon:
4266n/a self.assertTrue(quoprimime.header_check(c),
4267n/a 'Should be header quopri encoded: %s' % chr(c))
4268n/a
4269n/a def test_quopri_body_check(self):
4270n/a for c in self.blit:
4271n/a self.assertFalse(quoprimime.body_check(c),
4272n/a 'Should not be body quopri encoded: %s' % chr(c))
4273n/a for c in self.bnon:
4274n/a self.assertTrue(quoprimime.body_check(c),
4275n/a 'Should be body quopri encoded: %s' % chr(c))
4276n/a
4277n/a def test_header_quopri_len(self):
4278n/a eq = self.assertEqual
4279n/a eq(quoprimime.header_length(b'hello'), 5)
4280n/a # RFC 2047 chrome is not included in header_length().
4281n/a eq(len(quoprimime.header_encode(b'hello', charset='xxx')),
4282n/a quoprimime.header_length(b'hello') +
4283n/a # =?xxx?q?...?= means 10 extra characters
4284n/a 10)
4285n/a eq(quoprimime.header_length(b'h@e@l@l@o@'), 20)
4286n/a # RFC 2047 chrome is not included in header_length().
4287n/a eq(len(quoprimime.header_encode(b'h@e@l@l@o@', charset='xxx')),
4288n/a quoprimime.header_length(b'h@e@l@l@o@') +
4289n/a # =?xxx?q?...?= means 10 extra characters
4290n/a 10)
4291n/a for c in self.hlit:
4292n/a eq(quoprimime.header_length(bytes([c])), 1,
4293n/a 'expected length 1 for %r' % chr(c))
4294n/a for c in self.hnon:
4295n/a # Space is special; it's encoded to _
4296n/a if c == ord(' '):
4297n/a continue
4298n/a eq(quoprimime.header_length(bytes([c])), 3,
4299n/a 'expected length 3 for %r' % chr(c))
4300n/a eq(quoprimime.header_length(b' '), 1)
4301n/a
4302n/a def test_body_quopri_len(self):
4303n/a eq = self.assertEqual
4304n/a for c in self.blit:
4305n/a eq(quoprimime.body_length(bytes([c])), 1)
4306n/a for c in self.bnon:
4307n/a eq(quoprimime.body_length(bytes([c])), 3)
4308n/a
4309n/a def test_quote_unquote_idempotent(self):
4310n/a for x in range(256):
4311n/a c = chr(x)
4312n/a self.assertEqual(quoprimime.unquote(quoprimime.quote(c)), c)
4313n/a
4314n/a def _test_header_encode(self, header, expected_encoded_header, charset=None):
4315n/a if charset is None:
4316n/a encoded_header = quoprimime.header_encode(header)
4317n/a else:
4318n/a encoded_header = quoprimime.header_encode(header, charset)
4319n/a self.assertEqual(encoded_header, expected_encoded_header)
4320n/a
4321n/a def test_header_encode_null(self):
4322n/a self._test_header_encode(b'', '')
4323n/a
4324n/a def test_header_encode_one_word(self):
4325n/a self._test_header_encode(b'hello', '=?iso-8859-1?q?hello?=')
4326n/a
4327n/a def test_header_encode_two_lines(self):
4328n/a self._test_header_encode(b'hello\nworld',
4329n/a '=?iso-8859-1?q?hello=0Aworld?=')
4330n/a
4331n/a def test_header_encode_non_ascii(self):
4332n/a self._test_header_encode(b'hello\xc7there',
4333n/a '=?iso-8859-1?q?hello=C7there?=')
4334n/a
4335n/a def test_header_encode_alt_charset(self):
4336n/a self._test_header_encode(b'hello', '=?iso-8859-2?q?hello?=',
4337n/a charset='iso-8859-2')
4338n/a
4339n/a def _test_header_decode(self, encoded_header, expected_decoded_header):
4340n/a decoded_header = quoprimime.header_decode(encoded_header)
4341n/a self.assertEqual(decoded_header, expected_decoded_header)
4342n/a
4343n/a def test_header_decode_null(self):
4344n/a self._test_header_decode('', '')
4345n/a
4346n/a def test_header_decode_one_word(self):
4347n/a self._test_header_decode('hello', 'hello')
4348n/a
4349n/a def test_header_decode_two_lines(self):
4350n/a self._test_header_decode('hello=0Aworld', 'hello\nworld')
4351n/a
4352n/a def test_header_decode_non_ascii(self):
4353n/a self._test_header_decode('hello=C7there', 'hello\xc7there')
4354n/a
4355n/a def test_header_decode_re_bug_18380(self):
4356n/a # Issue 18380: Call re.sub with a positional argument for flags in the wrong position
4357n/a self.assertEqual(quoprimime.header_decode('=30' * 257), '0' * 257)
4358n/a
4359n/a def _test_decode(self, encoded, expected_decoded, eol=None):
4360n/a if eol is None:
4361n/a decoded = quoprimime.decode(encoded)
4362n/a else:
4363n/a decoded = quoprimime.decode(encoded, eol=eol)
4364n/a self.assertEqual(decoded, expected_decoded)
4365n/a
4366n/a def test_decode_null_word(self):
4367n/a self._test_decode('', '')
4368n/a
4369n/a def test_decode_null_line_null_word(self):
4370n/a self._test_decode('\r\n', '\n')
4371n/a
4372n/a def test_decode_one_word(self):
4373n/a self._test_decode('hello', 'hello')
4374n/a
4375n/a def test_decode_one_word_eol(self):
4376n/a self._test_decode('hello', 'hello', eol='X')
4377n/a
4378n/a def test_decode_one_line(self):
4379n/a self._test_decode('hello\r\n', 'hello\n')
4380n/a
4381n/a def test_decode_one_line_lf(self):
4382n/a self._test_decode('hello\n', 'hello\n')
4383n/a
4384n/a def test_decode_one_line_cr(self):
4385n/a self._test_decode('hello\r', 'hello\n')
4386n/a
4387n/a def test_decode_one_line_nl(self):
4388n/a self._test_decode('hello\n', 'helloX', eol='X')
4389n/a
4390n/a def test_decode_one_line_crnl(self):
4391n/a self._test_decode('hello\r\n', 'helloX', eol='X')
4392n/a
4393n/a def test_decode_one_line_one_word(self):
4394n/a self._test_decode('hello\r\nworld', 'hello\nworld')
4395n/a
4396n/a def test_decode_one_line_one_word_eol(self):
4397n/a self._test_decode('hello\r\nworld', 'helloXworld', eol='X')
4398n/a
4399n/a def test_decode_two_lines(self):
4400n/a self._test_decode('hello\r\nworld\r\n', 'hello\nworld\n')
4401n/a
4402n/a def test_decode_two_lines_eol(self):
4403n/a self._test_decode('hello\r\nworld\r\n', 'helloXworldX', eol='X')
4404n/a
4405n/a def test_decode_one_long_line(self):
4406n/a self._test_decode('Spam' * 250, 'Spam' * 250)
4407n/a
4408n/a def test_decode_one_space(self):
4409n/a self._test_decode(' ', '')
4410n/a
4411n/a def test_decode_multiple_spaces(self):
4412n/a self._test_decode(' ' * 5, '')
4413n/a
4414n/a def test_decode_one_line_trailing_spaces(self):
4415n/a self._test_decode('hello \r\n', 'hello\n')
4416n/a
4417n/a def test_decode_two_lines_trailing_spaces(self):
4418n/a self._test_decode('hello \r\nworld \r\n', 'hello\nworld\n')
4419n/a
4420n/a def test_decode_quoted_word(self):
4421n/a self._test_decode('=22quoted=20words=22', '"quoted words"')
4422n/a
4423n/a def test_decode_uppercase_quoting(self):
4424n/a self._test_decode('ab=CD=EF', 'ab\xcd\xef')
4425n/a
4426n/a def test_decode_lowercase_quoting(self):
4427n/a self._test_decode('ab=cd=ef', 'ab\xcd\xef')
4428n/a
4429n/a def test_decode_soft_line_break(self):
4430n/a self._test_decode('soft line=\r\nbreak', 'soft linebreak')
4431n/a
4432n/a def test_decode_false_quoting(self):
4433n/a self._test_decode('A=1,B=A ==> A+B==2', 'A=1,B=A ==> A+B==2')
4434n/a
4435n/a def _test_encode(self, body, expected_encoded_body, maxlinelen=None, eol=None):
4436n/a kwargs = {}
4437n/a if maxlinelen is None:
4438n/a # Use body_encode's default.
4439n/a maxlinelen = 76
4440n/a else:
4441n/a kwargs['maxlinelen'] = maxlinelen
4442n/a if eol is None:
4443n/a # Use body_encode's default.
4444n/a eol = '\n'
4445n/a else:
4446n/a kwargs['eol'] = eol
4447n/a encoded_body = quoprimime.body_encode(body, **kwargs)
4448n/a self.assertEqual(encoded_body, expected_encoded_body)
4449n/a if eol == '\n' or eol == '\r\n':
4450n/a # We know how to split the result back into lines, so maxlinelen
4451n/a # can be checked.
4452n/a for line in encoded_body.splitlines():
4453n/a self.assertLessEqual(len(line), maxlinelen)
4454n/a
4455n/a def test_encode_null(self):
4456n/a self._test_encode('', '')
4457n/a
4458n/a def test_encode_null_lines(self):
4459n/a self._test_encode('\n\n', '\n\n')
4460n/a
4461n/a def test_encode_one_line(self):
4462n/a self._test_encode('hello\n', 'hello\n')
4463n/a
4464n/a def test_encode_one_line_crlf(self):
4465n/a self._test_encode('hello\r\n', 'hello\n')
4466n/a
4467n/a def test_encode_one_line_eol(self):
4468n/a self._test_encode('hello\n', 'hello\r\n', eol='\r\n')
4469n/a
4470n/a def test_encode_one_line_eol_after_non_ascii(self):
4471n/a # issue 20206; see changeset 0cf700464177 for why the encode/decode.
4472n/a self._test_encode('hello\u03c5\n'.encode('utf-8').decode('latin1'),
4473n/a 'hello=CF=85\r\n', eol='\r\n')
4474n/a
4475n/a def test_encode_one_space(self):
4476n/a self._test_encode(' ', '=20')
4477n/a
4478n/a def test_encode_one_line_one_space(self):
4479n/a self._test_encode(' \n', '=20\n')
4480n/a
4481n/a# XXX: body_encode() expect strings, but uses ord(char) from these strings
4482n/a# to index into a 256-entry list. For code points above 255, this will fail.
4483n/a# Should there be a check for 8-bit only ord() values in body, or at least
4484n/a# a comment about the expected input?
4485n/a
4486n/a def test_encode_two_lines_one_space(self):
4487n/a self._test_encode(' \n \n', '=20\n=20\n')
4488n/a
4489n/a def test_encode_one_word_trailing_spaces(self):
4490n/a self._test_encode('hello ', 'hello =20')
4491n/a
4492n/a def test_encode_one_line_trailing_spaces(self):
4493n/a self._test_encode('hello \n', 'hello =20\n')
4494n/a
4495n/a def test_encode_one_word_trailing_tab(self):
4496n/a self._test_encode('hello \t', 'hello =09')
4497n/a
4498n/a def test_encode_one_line_trailing_tab(self):
4499n/a self._test_encode('hello \t\n', 'hello =09\n')
4500n/a
4501n/a def test_encode_trailing_space_before_maxlinelen(self):
4502n/a self._test_encode('abcd \n1234', 'abcd =\n\n1234', maxlinelen=6)
4503n/a
4504n/a def test_encode_trailing_space_at_maxlinelen(self):
4505n/a self._test_encode('abcd \n1234', 'abcd=\n=20\n1234', maxlinelen=5)
4506n/a
4507n/a def test_encode_trailing_space_beyond_maxlinelen(self):
4508n/a self._test_encode('abcd \n1234', 'abc=\nd=20\n1234', maxlinelen=4)
4509n/a
4510n/a def test_encode_whitespace_lines(self):
4511n/a self._test_encode(' \n' * 5, '=20\n' * 5)
4512n/a
4513n/a def test_encode_quoted_equals(self):
4514n/a self._test_encode('a = b', 'a =3D b')
4515n/a
4516n/a def test_encode_one_long_string(self):
4517n/a self._test_encode('x' * 100, 'x' * 75 + '=\n' + 'x' * 25)
4518n/a
4519n/a def test_encode_one_long_line(self):
4520n/a self._test_encode('x' * 100 + '\n', 'x' * 75 + '=\n' + 'x' * 25 + '\n')
4521n/a
4522n/a def test_encode_one_very_long_line(self):
4523n/a self._test_encode('x' * 200 + '\n',
4524n/a 2 * ('x' * 75 + '=\n') + 'x' * 50 + '\n')
4525n/a
4526n/a def test_encode_shortest_maxlinelen(self):
4527n/a self._test_encode('=' * 5, '=3D=\n' * 4 + '=3D', maxlinelen=4)
4528n/a
4529n/a def test_encode_maxlinelen_too_small(self):
4530n/a self.assertRaises(ValueError, self._test_encode, '', '', maxlinelen=3)
4531n/a
4532n/a def test_encode(self):
4533n/a eq = self.assertEqual
4534n/a eq(quoprimime.body_encode(''), '')
4535n/a eq(quoprimime.body_encode('hello'), 'hello')
4536n/a # Test the binary flag
4537n/a eq(quoprimime.body_encode('hello\r\nworld'), 'hello\nworld')
4538n/a # Test the maxlinelen arg
4539n/a eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40), """\
4540n/axxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
4541n/a xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
4542n/ax xxxx xxxx xxxx xxxx=20""")
4543n/a # Test the eol argument
4544n/a eq(quoprimime.body_encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'),
4545n/a """\
4546n/axxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
4547n/a xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
4548n/ax xxxx xxxx xxxx xxxx=20""")
4549n/a eq(quoprimime.body_encode("""\
4550n/aone line
4551n/a
4552n/atwo line"""), """\
4553n/aone line
4554n/a
4555n/atwo line""")
4556n/a
4557n/a
4558n/a
4559n/a# Test the Charset class
4560n/aclass TestCharset(unittest.TestCase):
4561n/a def tearDown(self):
4562n/a from email import charset as CharsetModule
4563n/a try:
4564n/a del CharsetModule.CHARSETS['fake']
4565n/a except KeyError:
4566n/a pass
4567n/a
4568n/a def test_codec_encodeable(self):
4569n/a eq = self.assertEqual
4570n/a # Make sure us-ascii = no Unicode conversion
4571n/a c = Charset('us-ascii')
4572n/a eq(c.header_encode('Hello World!'), 'Hello World!')
4573n/a # Test 8-bit idempotency with us-ascii
4574n/a s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
4575n/a self.assertRaises(UnicodeError, c.header_encode, s)
4576n/a c = Charset('utf-8')
4577n/a eq(c.header_encode(s), '=?utf-8?b?wqTCosKkwqTCpMKmwqTCqMKkwqo=?=')
4578n/a
4579n/a def test_body_encode(self):
4580n/a eq = self.assertEqual
4581n/a # Try a charset with QP body encoding
4582n/a c = Charset('iso-8859-1')
4583n/a eq('hello w=F6rld', c.body_encode('hello w\xf6rld'))
4584n/a # Try a charset with Base64 body encoding
4585n/a c = Charset('utf-8')
4586n/a eq('aGVsbG8gd29ybGQ=\n', c.body_encode(b'hello world'))
4587n/a # Try a charset with None body encoding
4588n/a c = Charset('us-ascii')
4589n/a eq('hello world', c.body_encode('hello world'))
4590n/a # Try the convert argument, where input codec != output codec
4591n/a c = Charset('euc-jp')
4592n/a # With apologies to Tokio Kikuchi ;)
4593n/a # XXX FIXME
4594n/a## try:
4595n/a## eq('\x1b$B5FCO;~IW\x1b(B',
4596n/a## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7'))
4597n/a## eq('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7',
4598n/a## c.body_encode('\xb5\xc6\xc3\xcf\xbb\xfe\xc9\xd7', False))
4599n/a## except LookupError:
4600n/a## # We probably don't have the Japanese codecs installed
4601n/a## pass
4602n/a # Testing SF bug #625509, which we have to fake, since there are no
4603n/a # built-in encodings where the header encoding is QP but the body
4604n/a # encoding is not.
4605n/a from email import charset as CharsetModule
4606n/a CharsetModule.add_charset('fake', CharsetModule.QP, None, 'utf-8')
4607n/a c = Charset('fake')
4608n/a eq('hello world', c.body_encode('hello world'))
4609n/a
4610n/a def test_unicode_charset_name(self):
4611n/a charset = Charset('us-ascii')
4612n/a self.assertEqual(str(charset), 'us-ascii')
4613n/a self.assertRaises(errors.CharsetError, Charset, 'asc\xffii')
4614n/a
4615n/a
4616n/a
4617n/a# Test multilingual MIME headers.
4618n/aclass TestHeader(TestEmailBase):
4619n/a def test_simple(self):
4620n/a eq = self.ndiffAssertEqual
4621n/a h = Header('Hello World!')
4622n/a eq(h.encode(), 'Hello World!')
4623n/a h.append(' Goodbye World!')
4624n/a eq(h.encode(), 'Hello World! Goodbye World!')
4625n/a
4626n/a def test_simple_surprise(self):
4627n/a eq = self.ndiffAssertEqual
4628n/a h = Header('Hello World!')
4629n/a eq(h.encode(), 'Hello World!')
4630n/a h.append('Goodbye World!')
4631n/a eq(h.encode(), 'Hello World! Goodbye World!')
4632n/a
4633n/a def test_header_needs_no_decoding(self):
4634n/a h = 'no decoding needed'
4635n/a self.assertEqual(decode_header(h), [(h, None)])
4636n/a
4637n/a def test_long(self):
4638n/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.",
4639n/a maxlinelen=76)
4640n/a for l in h.encode(splitchars=' ').split('\n '):
4641n/a self.assertLessEqual(len(l), 76)
4642n/a
4643n/a def test_multilingual(self):
4644n/a eq = self.ndiffAssertEqual
4645n/a g = Charset("iso-8859-1")
4646n/a cz = Charset("iso-8859-2")
4647n/a utf8 = Charset("utf-8")
4648n/a g_head = (b'Die Mieter treten hier ein werden mit einem '
4649n/a b'Foerderband komfortabel den Korridor entlang, '
4650n/a b'an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, '
4651n/a b'gegen die rotierenden Klingen bef\xf6rdert. ')
4652n/a cz_head = (b'Finan\xe8ni metropole se hroutily pod tlakem jejich '
4653n/a b'd\xf9vtipu.. ')
4654n/a utf8_head = ('\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f'
4655n/a '\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00'
4656n/a '\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c'
4657n/a '\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067'
4658n/a '\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das '
4659n/a 'Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder '
4660n/a 'die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066'
4661n/a '\u3044\u307e\u3059\u3002')
4662n/a h = Header(g_head, g)
4663n/a h.append(cz_head, cz)
4664n/a h.append(utf8_head, utf8)
4665n/a enc = h.encode(maxlinelen=76)
4666n/a eq(enc, """\
4667n/a=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_einem_Foerderband_kom?=
4668n/a =?iso-8859-1?q?fortabel_den_Korridor_entlang=2C_an_s=FCdl=FCndischen_Wand?=
4669n/a =?iso-8859-1?q?gem=E4lden_vorbei=2C_gegen_die_rotierenden_Klingen_bef=F6r?=
4670n/a =?iso-8859-1?q?dert=2E_?= =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutily?=
4671n/a =?iso-8859-2?q?_pod_tlakem_jejich_d=F9vtipu=2E=2E_?= =?utf-8?b?5q2j56K6?=
4672n/a =?utf-8?b?44Gr6KiA44GG44Go57+76Kiz44Gv44GV44KM44Gm44GE44G+44Gb44KT44CC?=
4673n/a =?utf-8?b?5LiA6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM44CB44GC44Go44Gv44Gn?=
4674n/a =?utf-8?b?44Gf44KJ44KB44Gn44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGFz?=
4675n/a =?utf-8?b?IE51bnN0dWNrIGdpdCB1bmQgU2xvdGVybWV5ZXI/IEphISBCZWloZXJodW5k?=
4676n/a =?utf-8?b?IGRhcyBPZGVyIGRpZSBGbGlwcGVyd2FsZHQgZ2Vyc3B1dC7jgI3jgajoqIA=?=
4677n/a =?utf-8?b?44Gj44Gm44GE44G+44GZ44CC?=""")
4678n/a decoded = decode_header(enc)
4679n/a eq(len(decoded), 3)
4680n/a eq(decoded[0], (g_head, 'iso-8859-1'))
4681n/a eq(decoded[1], (cz_head, 'iso-8859-2'))
4682n/a eq(decoded[2], (utf8_head.encode('utf-8'), 'utf-8'))
4683n/a ustr = str(h)
4684n/a eq(ustr,
4685n/a (b'Die Mieter treten hier ein werden mit einem Foerderband '
4686n/a b'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
4687n/a b'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
4688n/a b'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
4689n/a b'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
4690n/a b'\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
4691n/a b'\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
4692n/a b'\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
4693n/a b'\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
4694n/a b'\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
4695n/a b'\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
4696n/a b'\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
4697n/a b'\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
4698n/a b'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
4699n/a b'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
4700n/a b'\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82'
4701n/a ).decode('utf-8'))
4702n/a # Test make_header()
4703n/a newh = make_header(decode_header(enc))
4704n/a eq(newh, h)
4705n/a
4706n/a def test_empty_header_encode(self):
4707n/a h = Header()
4708n/a self.assertEqual(h.encode(), '')
4709n/a
4710n/a def test_header_ctor_default_args(self):
4711n/a eq = self.ndiffAssertEqual
4712n/a h = Header()
4713n/a eq(h, '')
4714n/a h.append('foo', Charset('iso-8859-1'))
4715n/a eq(h, 'foo')
4716n/a
4717n/a def test_explicit_maxlinelen(self):
4718n/a eq = self.ndiffAssertEqual
4719n/a hstr = ('A very long line that must get split to something other '
4720n/a 'than at the 76th character boundary to test the non-default '
4721n/a 'behavior')
4722n/a h = Header(hstr)
4723n/a eq(h.encode(), '''\
4724n/aA very long line that must get split to something other than at the 76th
4725n/a character boundary to test the non-default behavior''')
4726n/a eq(str(h), hstr)
4727n/a h = Header(hstr, header_name='Subject')
4728n/a eq(h.encode(), '''\
4729n/aA very long line that must get split to something other than at the
4730n/a 76th character boundary to test the non-default behavior''')
4731n/a eq(str(h), hstr)
4732n/a h = Header(hstr, maxlinelen=1024, header_name='Subject')
4733n/a eq(h.encode(), hstr)
4734n/a eq(str(h), hstr)
4735n/a
4736n/a def test_quopri_splittable(self):
4737n/a eq = self.ndiffAssertEqual
4738n/a h = Header(charset='iso-8859-1', maxlinelen=20)
4739n/a x = 'xxxx ' * 20
4740n/a h.append(x)
4741n/a s = h.encode()
4742n/a eq(s, """\
4743n/a=?iso-8859-1?q?xxx?=
4744n/a =?iso-8859-1?q?x_?=
4745n/a =?iso-8859-1?q?xx?=
4746n/a =?iso-8859-1?q?xx?=
4747n/a =?iso-8859-1?q?_x?=
4748n/a =?iso-8859-1?q?xx?=
4749n/a =?iso-8859-1?q?x_?=
4750n/a =?iso-8859-1?q?xx?=
4751n/a =?iso-8859-1?q?xx?=
4752n/a =?iso-8859-1?q?_x?=
4753n/a =?iso-8859-1?q?xx?=
4754n/a =?iso-8859-1?q?x_?=
4755n/a =?iso-8859-1?q?xx?=
4756n/a =?iso-8859-1?q?xx?=
4757n/a =?iso-8859-1?q?_x?=
4758n/a =?iso-8859-1?q?xx?=
4759n/a =?iso-8859-1?q?x_?=
4760n/a =?iso-8859-1?q?xx?=
4761n/a =?iso-8859-1?q?xx?=
4762n/a =?iso-8859-1?q?_x?=
4763n/a =?iso-8859-1?q?xx?=
4764n/a =?iso-8859-1?q?x_?=
4765n/a =?iso-8859-1?q?xx?=
4766n/a =?iso-8859-1?q?xx?=
4767n/a =?iso-8859-1?q?_x?=
4768n/a =?iso-8859-1?q?xx?=
4769n/a =?iso-8859-1?q?x_?=
4770n/a =?iso-8859-1?q?xx?=
4771n/a =?iso-8859-1?q?xx?=
4772n/a =?iso-8859-1?q?_x?=
4773n/a =?iso-8859-1?q?xx?=
4774n/a =?iso-8859-1?q?x_?=
4775n/a =?iso-8859-1?q?xx?=
4776n/a =?iso-8859-1?q?xx?=
4777n/a =?iso-8859-1?q?_x?=
4778n/a =?iso-8859-1?q?xx?=
4779n/a =?iso-8859-1?q?x_?=
4780n/a =?iso-8859-1?q?xx?=
4781n/a =?iso-8859-1?q?xx?=
4782n/a =?iso-8859-1?q?_x?=
4783n/a =?iso-8859-1?q?xx?=
4784n/a =?iso-8859-1?q?x_?=
4785n/a =?iso-8859-1?q?xx?=
4786n/a =?iso-8859-1?q?xx?=
4787n/a =?iso-8859-1?q?_x?=
4788n/a =?iso-8859-1?q?xx?=
4789n/a =?iso-8859-1?q?x_?=
4790n/a =?iso-8859-1?q?xx?=
4791n/a =?iso-8859-1?q?xx?=
4792n/a =?iso-8859-1?q?_?=""")
4793n/a eq(x, str(make_header(decode_header(s))))
4794n/a h = Header(charset='iso-8859-1', maxlinelen=40)
4795n/a h.append('xxxx ' * 20)
4796n/a s = h.encode()
4797n/a eq(s, """\
4798n/a=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xxx?=
4799n/a =?iso-8859-1?q?x_xxxx_xxxx_xxxx_xxxx_?=
4800n/a =?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
4801n/a =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
4802n/a =?iso-8859-1?q?_xxxx_xxxx_?=""")
4803n/a eq(x, str(make_header(decode_header(s))))
4804n/a
4805n/a def test_base64_splittable(self):
4806n/a eq = self.ndiffAssertEqual
4807n/a h = Header(charset='koi8-r', maxlinelen=20)
4808n/a x = 'xxxx ' * 20
4809n/a h.append(x)
4810n/a s = h.encode()
4811n/a eq(s, """\
4812n/a=?koi8-r?b?eHh4?=
4813n/a =?koi8-r?b?eCB4?=
4814n/a =?koi8-r?b?eHh4?=
4815n/a =?koi8-r?b?IHh4?=
4816n/a =?koi8-r?b?eHgg?=
4817n/a =?koi8-r?b?eHh4?=
4818n/a =?koi8-r?b?eCB4?=
4819n/a =?koi8-r?b?eHh4?=
4820n/a =?koi8-r?b?IHh4?=
4821n/a =?koi8-r?b?eHgg?=
4822n/a =?koi8-r?b?eHh4?=
4823n/a =?koi8-r?b?eCB4?=
4824n/a =?koi8-r?b?eHh4?=
4825n/a =?koi8-r?b?IHh4?=
4826n/a =?koi8-r?b?eHgg?=
4827n/a =?koi8-r?b?eHh4?=
4828n/a =?koi8-r?b?eCB4?=
4829n/a =?koi8-r?b?eHh4?=
4830n/a =?koi8-r?b?IHh4?=
4831n/a =?koi8-r?b?eHgg?=
4832n/a =?koi8-r?b?eHh4?=
4833n/a =?koi8-r?b?eCB4?=
4834n/a =?koi8-r?b?eHh4?=
4835n/a =?koi8-r?b?IHh4?=
4836n/a =?koi8-r?b?eHgg?=
4837n/a =?koi8-r?b?eHh4?=
4838n/a =?koi8-r?b?eCB4?=
4839n/a =?koi8-r?b?eHh4?=
4840n/a =?koi8-r?b?IHh4?=
4841n/a =?koi8-r?b?eHgg?=
4842n/a =?koi8-r?b?eHh4?=
4843n/a =?koi8-r?b?eCB4?=
4844n/a =?koi8-r?b?eHh4?=
4845n/a =?koi8-r?b?IA==?=""")
4846n/a eq(x, str(make_header(decode_header(s))))
4847n/a h = Header(charset='koi8-r', maxlinelen=40)
4848n/a h.append(x)
4849n/a s = h.encode()
4850n/a eq(s, """\
4851n/a=?koi8-r?b?eHh4eCB4eHh4IHh4eHggeHh4?=
4852n/a =?koi8-r?b?eCB4eHh4IHh4eHggeHh4eCB4?=
4853n/a =?koi8-r?b?eHh4IHh4eHggeHh4eCB4eHh4?=
4854n/a =?koi8-r?b?IHh4eHggeHh4eCB4eHh4IHh4?=
4855n/a =?koi8-r?b?eHggeHh4eCB4eHh4IHh4eHgg?=
4856n/a =?koi8-r?b?eHh4eCB4eHh4IA==?=""")
4857n/a eq(x, str(make_header(decode_header(s))))
4858n/a
4859n/a def test_us_ascii_header(self):
4860n/a eq = self.assertEqual
4861n/a s = 'hello'
4862n/a x = decode_header(s)
4863n/a eq(x, [('hello', None)])
4864n/a h = make_header(x)
4865n/a eq(s, h.encode())
4866n/a
4867n/a def test_string_charset(self):
4868n/a eq = self.assertEqual
4869n/a h = Header()
4870n/a h.append('hello', 'iso-8859-1')
4871n/a eq(h, 'hello')
4872n/a
4873n/a## def test_unicode_error(self):
4874n/a## raises = self.assertRaises
4875n/a## raises(UnicodeError, Header, u'[P\xf6stal]', 'us-ascii')
4876n/a## raises(UnicodeError, Header, '[P\xf6stal]', 'us-ascii')
4877n/a## h = Header()
4878n/a## raises(UnicodeError, h.append, u'[P\xf6stal]', 'us-ascii')
4879n/a## raises(UnicodeError, h.append, '[P\xf6stal]', 'us-ascii')
4880n/a## raises(UnicodeError, Header, u'\u83ca\u5730\u6642\u592b', 'iso-8859-1')
4881n/a
4882n/a def test_utf8_shortest(self):
4883n/a eq = self.assertEqual
4884n/a h = Header('p\xf6stal', 'utf-8')
4885n/a eq(h.encode(), '=?utf-8?q?p=C3=B6stal?=')
4886n/a h = Header('\u83ca\u5730\u6642\u592b', 'utf-8')
4887n/a eq(h.encode(), '=?utf-8?b?6I+K5Zyw5pmC5aSr?=')
4888n/a
4889n/a def test_bad_8bit_header(self):
4890n/a raises = self.assertRaises
4891n/a eq = self.assertEqual
4892n/a x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
4893n/a raises(UnicodeError, Header, x)
4894n/a h = Header()
4895n/a raises(UnicodeError, h.append, x)
4896n/a e = x.decode('utf-8', 'replace')
4897n/a eq(str(Header(x, errors='replace')), e)
4898n/a h.append(x, errors='replace')
4899n/a eq(str(h), e)
4900n/a
4901n/a def test_escaped_8bit_header(self):
4902n/a x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
4903n/a e = x.decode('ascii', 'surrogateescape')
4904n/a h = Header(e, charset=email.charset.UNKNOWN8BIT)
4905n/a self.assertEqual(str(h),
4906n/a 'Ynwp4dUEbay Auction Semiar- No Charge \uFFFD Earn Big')
4907n/a self.assertEqual(email.header.decode_header(h), [(x, 'unknown-8bit')])
4908n/a
4909n/a def test_header_handles_binary_unknown8bit(self):
4910n/a x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
4911n/a h = Header(x, charset=email.charset.UNKNOWN8BIT)
4912n/a self.assertEqual(str(h),
4913n/a 'Ynwp4dUEbay Auction Semiar- No Charge \uFFFD Earn Big')
4914n/a self.assertEqual(email.header.decode_header(h), [(x, 'unknown-8bit')])
4915n/a
4916n/a def test_make_header_handles_binary_unknown8bit(self):
4917n/a x = b'Ynwp4dUEbay Auction Semiar- No Charge \x96 Earn Big'
4918n/a h = Header(x, charset=email.charset.UNKNOWN8BIT)
4919n/a h2 = email.header.make_header(email.header.decode_header(h))
4920n/a self.assertEqual(str(h2),
4921n/a 'Ynwp4dUEbay Auction Semiar- No Charge \uFFFD Earn Big')
4922n/a self.assertEqual(email.header.decode_header(h2), [(x, 'unknown-8bit')])
4923n/a
4924n/a def test_modify_returned_list_does_not_change_header(self):
4925n/a h = Header('test')
4926n/a chunks = email.header.decode_header(h)
4927n/a chunks.append(('ascii', 'test2'))
4928n/a self.assertEqual(str(h), 'test')
4929n/a
4930n/a def test_encoded_adjacent_nonencoded(self):
4931n/a eq = self.assertEqual
4932n/a h = Header()
4933n/a h.append('hello', 'iso-8859-1')
4934n/a h.append('world')
4935n/a s = h.encode()
4936n/a eq(s, '=?iso-8859-1?q?hello?= world')
4937n/a h = make_header(decode_header(s))
4938n/a eq(h.encode(), s)
4939n/a
4940n/a def test_whitespace_keeper(self):
4941n/a eq = self.assertEqual
4942n/a s = 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztk=?= =?koi8-r?q?=CA?= zz.'
4943n/a parts = decode_header(s)
4944n/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)])
4945n/a hdr = make_header(parts)
4946n/a eq(hdr.encode(),
4947n/a 'Subject: =?koi8-r?b?8NLP18XSy8EgzsEgxsnOwczYztnK?= zz.')
4948n/a
4949n/a def test_broken_base64_header(self):
4950n/a raises = self.assertRaises
4951n/a s = 'Subject: =?EUC-KR?B?CSixpLDtKSC/7Liuvsax4iC6uLmwMcijIKHaILzSwd/H0SC8+LCjwLsgv7W/+Mj3I ?='
4952n/a raises(errors.HeaderParseError, decode_header, s)
4953n/a
4954n/a def test_shift_jis_charset(self):
4955n/a h = Header('æ–‡', charset='shift_jis')
4956n/a self.assertEqual(h.encode(), '=?iso-2022-jp?b?GyRCSjgbKEI=?=')
4957n/a
4958n/a def test_flatten_header_with_no_value(self):
4959n/a # Issue 11401 (regression from email 4.x) Note that the space after
4960n/a # the header doesn't reflect the input, but this is also the way
4961n/a # email 4.x behaved. At some point it would be nice to fix that.
4962n/a msg = email.message_from_string("EmptyHeader:")
4963n/a self.assertEqual(str(msg), "EmptyHeader: \n\n")
4964n/a
4965n/a def test_encode_preserves_leading_ws_on_value(self):
4966n/a msg = Message()
4967n/a msg['SomeHeader'] = ' value with leading ws'
4968n/a self.assertEqual(str(msg), "SomeHeader: value with leading ws\n\n")
4969n/a
4970n/a
4971n/a
4972n/a# Test RFC 2231 header parameters (en/de)coding
4973n/aclass TestRFC2231(TestEmailBase):
4974n/a
4975n/a # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_with_double_quotes
4976n/a # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_inside_double_quotes
4977n/a def test_get_param(self):
4978n/a eq = self.assertEqual
4979n/a msg = self._msgobj('msg_29.txt')
4980n/a eq(msg.get_param('title'),
4981n/a ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
4982n/a eq(msg.get_param('title', unquote=False),
4983n/a ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
4984n/a
4985n/a def test_set_param(self):
4986n/a eq = self.ndiffAssertEqual
4987n/a msg = Message()
4988n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
4989n/a charset='us-ascii')
4990n/a eq(msg.get_param('title'),
4991n/a ('us-ascii', '', 'This is even more ***fun*** isn\'t it!'))
4992n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
4993n/a charset='us-ascii', language='en')
4994n/a eq(msg.get_param('title'),
4995n/a ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
4996n/a msg = self._msgobj('msg_01.txt')
4997n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
4998n/a charset='us-ascii', language='en')
4999n/a eq(msg.as_string(maxheaderlen=78), """\
5000n/aReturn-Path: <bbb@zzz.org>
5001n/aDelivered-To: bbb@zzz.org
5002n/aReceived: by mail.zzz.org (Postfix, from userid 889)
5003n/a\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
5004n/aMIME-Version: 1.0
5005n/aContent-Transfer-Encoding: 7bit
5006n/aMessage-ID: <15090.61304.110929.45684@aaa.zzz.org>
5007n/aFrom: bbb@ddd.com (John X. Doe)
5008n/aTo: bbb@zzz.org
5009n/aSubject: This is a test message
5010n/aDate: Fri, 4 May 2001 14:05:44 -0400
5011n/aContent-Type: text/plain; charset=us-ascii;
5012n/a title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21
5013n/a
5014n/a
5015n/aHi,
5016n/a
5017n/aDo you like this message?
5018n/a
5019n/a-Me
5020n/a""")
5021n/a
5022n/a def test_set_param_requote(self):
5023n/a msg = Message()
5024n/a msg.set_param('title', 'foo')
5025n/a self.assertEqual(msg['content-type'], 'text/plain; title="foo"')
5026n/a msg.set_param('title', 'bar', requote=False)
5027n/a self.assertEqual(msg['content-type'], 'text/plain; title=bar')
5028n/a # tspecial is still quoted.
5029n/a msg.set_param('title', "(bar)bell", requote=False)
5030n/a self.assertEqual(msg['content-type'], 'text/plain; title="(bar)bell"')
5031n/a
5032n/a def test_del_param(self):
5033n/a eq = self.ndiffAssertEqual
5034n/a msg = self._msgobj('msg_01.txt')
5035n/a msg.set_param('foo', 'bar', charset='us-ascii', language='en')
5036n/a msg.set_param('title', 'This is even more ***fun*** isn\'t it!',
5037n/a charset='us-ascii', language='en')
5038n/a msg.del_param('foo', header='Content-Type')
5039n/a eq(msg.as_string(maxheaderlen=78), """\
5040n/aReturn-Path: <bbb@zzz.org>
5041n/aDelivered-To: bbb@zzz.org
5042n/aReceived: by mail.zzz.org (Postfix, from userid 889)
5043n/a\tid 27CEAD38CC; Fri, 4 May 2001 14:05:44 -0400 (EDT)
5044n/aMIME-Version: 1.0
5045n/aContent-Transfer-Encoding: 7bit
5046n/aMessage-ID: <15090.61304.110929.45684@aaa.zzz.org>
5047n/aFrom: bbb@ddd.com (John X. Doe)
5048n/aTo: bbb@zzz.org
5049n/aSubject: This is a test message
5050n/aDate: Fri, 4 May 2001 14:05:44 -0400
5051n/aContent-Type: text/plain; charset="us-ascii";
5052n/a title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21
5053n/a
5054n/a
5055n/aHi,
5056n/a
5057n/aDo you like this message?
5058n/a
5059n/a-Me
5060n/a""")
5061n/a
5062n/a # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_charset
5063n/a # I changed the charset name, though, because the one in the file isn't
5064n/a # a legal charset name. Should add a test for an illegal charset.
5065n/a def test_rfc2231_get_content_charset(self):
5066n/a eq = self.assertEqual
5067n/a msg = self._msgobj('msg_32.txt')
5068n/a eq(msg.get_content_charset(), 'us-ascii')
5069n/a
5070n/a # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_no_double_quotes
5071n/a def test_rfc2231_parse_rfc_quoting(self):
5072n/a m = textwrap.dedent('''\
5073n/a Content-Disposition: inline;
5074n/a \tfilename*0*=''This%20is%20even%20more%20;
5075n/a \tfilename*1*=%2A%2A%2Afun%2A%2A%2A%20;
5076n/a \tfilename*2="is it not.pdf"
5077n/a
5078n/a ''')
5079n/a msg = email.message_from_string(m)
5080n/a self.assertEqual(msg.get_filename(),
5081n/a 'This is even more ***fun*** is it not.pdf')
5082n/a self.assertEqual(m, msg.as_string())
5083n/a
5084n/a # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_with_double_quotes
5085n/a def test_rfc2231_parse_extra_quoting(self):
5086n/a m = textwrap.dedent('''\
5087n/a Content-Disposition: inline;
5088n/a \tfilename*0*="''This%20is%20even%20more%20";
5089n/a \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
5090n/a \tfilename*2="is it not.pdf"
5091n/a
5092n/a ''')
5093n/a msg = email.message_from_string(m)
5094n/a self.assertEqual(msg.get_filename(),
5095n/a 'This is even more ***fun*** is it not.pdf')
5096n/a self.assertEqual(m, msg.as_string())
5097n/a
5098n/a # test_headerregistry.TestContentTypeHeader.rfc2231_no_language_or_charset
5099n/a # but new test uses *0* because otherwise lang/charset is not valid.
5100n/a # test_headerregistry.TestContentTypeHeader.rfc2231_segmented_normal_values
5101n/a def test_rfc2231_no_language_or_charset(self):
5102n/a m = '''\
5103n/aContent-Transfer-Encoding: 8bit
5104n/aContent-Disposition: inline; filename="file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm"
5105n/aContent-Type: text/html; NAME*0=file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEM; NAME*1=P_nsmail.htm
5106n/a
5107n/a'''
5108n/a msg = email.message_from_string(m)
5109n/a param = msg.get_param('NAME')
5110n/a self.assertNotIsInstance(param, tuple)
5111n/a self.assertEqual(
5112n/a param,
5113n/a 'file____C__DOCUMENTS_20AND_20SETTINGS_FABIEN_LOCAL_20SETTINGS_TEMP_nsmail.htm')
5114n/a
5115n/a # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_no_charset
5116n/a def test_rfc2231_no_language_or_charset_in_filename(self):
5117n/a m = '''\
5118n/aContent-Disposition: inline;
5119n/a\tfilename*0*="''This%20is%20even%20more%20";
5120n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
5121n/a\tfilename*2="is it not.pdf"
5122n/a
5123n/a'''
5124n/a msg = email.message_from_string(m)
5125n/a self.assertEqual(msg.get_filename(),
5126n/a 'This is even more ***fun*** is it not.pdf')
5127n/a
5128n/a # Duplicate of previous test?
5129n/a def test_rfc2231_no_language_or_charset_in_filename_encoded(self):
5130n/a m = '''\
5131n/aContent-Disposition: inline;
5132n/a\tfilename*0*="''This%20is%20even%20more%20";
5133n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
5134n/a\tfilename*2="is it not.pdf"
5135n/a
5136n/a'''
5137n/a msg = email.message_from_string(m)
5138n/a self.assertEqual(msg.get_filename(),
5139n/a 'This is even more ***fun*** is it not.pdf')
5140n/a
5141n/a # test_headerregistry.TestContentTypeHeader.rfc2231_partly_encoded,
5142n/a # but the test below is wrong (the first part should be decoded).
5143n/a def test_rfc2231_partly_encoded(self):
5144n/a m = '''\
5145n/aContent-Disposition: inline;
5146n/a\tfilename*0="''This%20is%20even%20more%20";
5147n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
5148n/a\tfilename*2="is it not.pdf"
5149n/a
5150n/a'''
5151n/a msg = email.message_from_string(m)
5152n/a self.assertEqual(
5153n/a msg.get_filename(),
5154n/a 'This%20is%20even%20more%20***fun*** is it not.pdf')
5155n/a
5156n/a def test_rfc2231_partly_nonencoded(self):
5157n/a m = '''\
5158n/aContent-Disposition: inline;
5159n/a\tfilename*0="This%20is%20even%20more%20";
5160n/a\tfilename*1="%2A%2A%2Afun%2A%2A%2A%20";
5161n/a\tfilename*2="is it not.pdf"
5162n/a
5163n/a'''
5164n/a msg = email.message_from_string(m)
5165n/a self.assertEqual(
5166n/a msg.get_filename(),
5167n/a 'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20is it not.pdf')
5168n/a
5169n/a def test_rfc2231_no_language_or_charset_in_boundary(self):
5170n/a m = '''\
5171n/aContent-Type: multipart/alternative;
5172n/a\tboundary*0*="''This%20is%20even%20more%20";
5173n/a\tboundary*1*="%2A%2A%2Afun%2A%2A%2A%20";
5174n/a\tboundary*2="is it not.pdf"
5175n/a
5176n/a'''
5177n/a msg = email.message_from_string(m)
5178n/a self.assertEqual(msg.get_boundary(),
5179n/a 'This is even more ***fun*** is it not.pdf')
5180n/a
5181n/a def test_rfc2231_no_language_or_charset_in_charset(self):
5182n/a # This is a nonsensical charset value, but tests the code anyway
5183n/a m = '''\
5184n/aContent-Type: text/plain;
5185n/a\tcharset*0*="This%20is%20even%20more%20";
5186n/a\tcharset*1*="%2A%2A%2Afun%2A%2A%2A%20";
5187n/a\tcharset*2="is it not.pdf"
5188n/a
5189n/a'''
5190n/a msg = email.message_from_string(m)
5191n/a self.assertEqual(msg.get_content_charset(),
5192n/a 'this is even more ***fun*** is it not.pdf')
5193n/a
5194n/a # test_headerregistry.TestContentTypeHeader.rfc2231_unknown_charset_treated_as_ascii
5195n/a def test_rfc2231_bad_encoding_in_filename(self):
5196n/a m = '''\
5197n/aContent-Disposition: inline;
5198n/a\tfilename*0*="bogus'xx'This%20is%20even%20more%20";
5199n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
5200n/a\tfilename*2="is it not.pdf"
5201n/a
5202n/a'''
5203n/a msg = email.message_from_string(m)
5204n/a self.assertEqual(msg.get_filename(),
5205n/a 'This is even more ***fun*** is it not.pdf')
5206n/a
5207n/a def test_rfc2231_bad_encoding_in_charset(self):
5208n/a m = """\
5209n/aContent-Type: text/plain; charset*=bogus''utf-8%E2%80%9D
5210n/a
5211n/a"""
5212n/a msg = email.message_from_string(m)
5213n/a # This should return None because non-ascii characters in the charset
5214n/a # are not allowed.
5215n/a self.assertEqual(msg.get_content_charset(), None)
5216n/a
5217n/a def test_rfc2231_bad_character_in_charset(self):
5218n/a m = """\
5219n/aContent-Type: text/plain; charset*=ascii''utf-8%E2%80%9D
5220n/a
5221n/a"""
5222n/a msg = email.message_from_string(m)
5223n/a # This should return None because non-ascii characters in the charset
5224n/a # are not allowed.
5225n/a self.assertEqual(msg.get_content_charset(), None)
5226n/a
5227n/a def test_rfc2231_bad_character_in_filename(self):
5228n/a m = '''\
5229n/aContent-Disposition: inline;
5230n/a\tfilename*0*="ascii'xx'This%20is%20even%20more%20";
5231n/a\tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
5232n/a\tfilename*2*="is it not.pdf%E2"
5233n/a
5234n/a'''
5235n/a msg = email.message_from_string(m)
5236n/a self.assertEqual(msg.get_filename(),
5237n/a 'This is even more ***fun*** is it not.pdf\ufffd')
5238n/a
5239n/a def test_rfc2231_unknown_encoding(self):
5240n/a m = """\
5241n/aContent-Transfer-Encoding: 8bit
5242n/aContent-Disposition: inline; filename*=X-UNKNOWN''myfile.txt
5243n/a
5244n/a"""
5245n/a msg = email.message_from_string(m)
5246n/a self.assertEqual(msg.get_filename(), 'myfile.txt')
5247n/a
5248n/a def test_rfc2231_single_tick_in_filename_extended(self):
5249n/a eq = self.assertEqual
5250n/a m = """\
5251n/aContent-Type: application/x-foo;
5252n/a\tname*0*=\"Frank's\"; name*1*=\" Document\"
5253n/a
5254n/a"""
5255n/a msg = email.message_from_string(m)
5256n/a charset, language, s = msg.get_param('name')
5257n/a eq(charset, None)
5258n/a eq(language, None)
5259n/a eq(s, "Frank's Document")
5260n/a
5261n/a # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_inside_double_quotes
5262n/a def test_rfc2231_single_tick_in_filename(self):
5263n/a m = """\
5264n/aContent-Type: application/x-foo; name*0=\"Frank's\"; name*1=\" Document\"
5265n/a
5266n/a"""
5267n/a msg = email.message_from_string(m)
5268n/a param = msg.get_param('name')
5269n/a self.assertNotIsInstance(param, tuple)
5270n/a self.assertEqual(param, "Frank's Document")
5271n/a
5272n/a def test_rfc2231_missing_tick(self):
5273n/a m = '''\
5274n/aContent-Disposition: inline;
5275n/a\tfilename*0*="'This%20is%20broken";
5276n/a'''
5277n/a msg = email.message_from_string(m)
5278n/a self.assertEqual(
5279n/a msg.get_filename(),
5280n/a "'This is broken")
5281n/a
5282n/a def test_rfc2231_missing_tick_with_encoded_non_ascii(self):
5283n/a m = '''\
5284n/aContent-Disposition: inline;
5285n/a\tfilename*0*="'This%20is%E2broken";
5286n/a'''
5287n/a msg = email.message_from_string(m)
5288n/a self.assertEqual(
5289n/a msg.get_filename(),
5290n/a "'This is\ufffdbroken")
5291n/a
5292n/a # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_in_value_with_charset_and_lang
5293n/a def test_rfc2231_tick_attack_extended(self):
5294n/a eq = self.assertEqual
5295n/a m = """\
5296n/aContent-Type: application/x-foo;
5297n/a\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\"
5298n/a
5299n/a"""
5300n/a msg = email.message_from_string(m)
5301n/a charset, language, s = msg.get_param('name')
5302n/a eq(charset, 'us-ascii')
5303n/a eq(language, 'en-us')
5304n/a eq(s, "Frank's Document")
5305n/a
5306n/a # test_headerregistry.TestContentTypeHeader.rfc2231_single_quote_in_non_encoded_value
5307n/a def test_rfc2231_tick_attack(self):
5308n/a m = """\
5309n/aContent-Type: application/x-foo;
5310n/a\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\"
5311n/a
5312n/a"""
5313n/a msg = email.message_from_string(m)
5314n/a param = msg.get_param('name')
5315n/a self.assertNotIsInstance(param, tuple)
5316n/a self.assertEqual(param, "us-ascii'en-us'Frank's Document")
5317n/a
5318n/a # test_headerregistry.TestContentTypeHeader.rfc2231_single_quotes_inside_quotes
5319n/a def test_rfc2231_no_extended_values(self):
5320n/a eq = self.assertEqual
5321n/a m = """\
5322n/aContent-Type: application/x-foo; name=\"Frank's Document\"
5323n/a
5324n/a"""
5325n/a msg = email.message_from_string(m)
5326n/a eq(msg.get_param('name'), "Frank's Document")
5327n/a
5328n/a # test_headerregistry.TestContentTypeHeader.rfc2231_encoded_then_unencoded_segments
5329n/a def test_rfc2231_encoded_then_unencoded_segments(self):
5330n/a eq = self.assertEqual
5331n/a m = """\
5332n/aContent-Type: application/x-foo;
5333n/a\tname*0*=\"us-ascii'en-us'My\";
5334n/a\tname*1=\" Document\";
5335n/a\tname*2*=\" For You\"
5336n/a
5337n/a"""
5338n/a msg = email.message_from_string(m)
5339n/a charset, language, s = msg.get_param('name')
5340n/a eq(charset, 'us-ascii')
5341n/a eq(language, 'en-us')
5342n/a eq(s, 'My Document For You')
5343n/a
5344n/a # test_headerregistry.TestContentTypeHeader.rfc2231_unencoded_then_encoded_segments
5345n/a # test_headerregistry.TestContentTypeHeader.rfc2231_quoted_unencoded_then_encoded_segments
5346n/a def test_rfc2231_unencoded_then_encoded_segments(self):
5347n/a eq = self.assertEqual
5348n/a m = """\
5349n/aContent-Type: application/x-foo;
5350n/a\tname*0=\"us-ascii'en-us'My\";
5351n/a\tname*1*=\" Document\";
5352n/a\tname*2*=\" For You\"
5353n/a
5354n/a"""
5355n/a msg = email.message_from_string(m)
5356n/a charset, language, s = msg.get_param('name')
5357n/a eq(charset, 'us-ascii')
5358n/a eq(language, 'en-us')
5359n/a eq(s, 'My Document For You')
5360n/a
5361n/a
5362n/a
5363n/a# Tests to ensure that signed parts of an email are completely preserved, as
5364n/a# required by RFC1847 section 2.1. Note that these are incomplete, because the
5365n/a# email package does not currently always preserve the body. See issue 1670765.
5366n/aclass TestSigned(TestEmailBase):
5367n/a
5368n/a def _msg_and_obj(self, filename):
5369n/a with openfile(filename) as fp:
5370n/a original = fp.read()
5371n/a msg = email.message_from_string(original)
5372n/a return original, msg
5373n/a
5374n/a def _signed_parts_eq(self, original, result):
5375n/a # Extract the first mime part of each message
5376n/a import re
5377n/a repart = re.compile(r'^--([^\n]+)\n(.*?)\n--\1$', re.S | re.M)
5378n/a inpart = repart.search(original).group(2)
5379n/a outpart = repart.search(result).group(2)
5380n/a self.assertEqual(outpart, inpart)
5381n/a
5382n/a def test_long_headers_as_string(self):
5383n/a original, msg = self._msg_and_obj('msg_45.txt')
5384n/a result = msg.as_string()
5385n/a self._signed_parts_eq(original, result)
5386n/a
5387n/a def test_long_headers_as_string_maxheaderlen(self):
5388n/a original, msg = self._msg_and_obj('msg_45.txt')
5389n/a result = msg.as_string(maxheaderlen=60)
5390n/a self._signed_parts_eq(original, result)
5391n/a
5392n/a def test_long_headers_flatten(self):
5393n/a original, msg = self._msg_and_obj('msg_45.txt')
5394n/a fp = StringIO()
5395n/a Generator(fp).flatten(msg)
5396n/a result = fp.getvalue()
5397n/a self._signed_parts_eq(original, result)
5398n/a
5399n/a
5400n/a
5401n/aif __name__ == '__main__':
5402n/a unittest.main()