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

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

#countcontent
1n/aimport datetime
2n/aimport textwrap
3n/aimport unittest
4n/afrom email import errors
5n/afrom email import policy
6n/afrom email.message import Message
7n/afrom test.test_email import TestEmailBase, parameterize
8n/afrom email import headerregistry
9n/afrom email.headerregistry import Address, Group
10n/a
11n/a
12n/aDITTO = object()
13n/a
14n/a
15n/aclass TestHeaderRegistry(TestEmailBase):
16n/a
17n/a def test_arbitrary_name_unstructured(self):
18n/a factory = headerregistry.HeaderRegistry()
19n/a h = factory('foobar', 'test')
20n/a self.assertIsInstance(h, headerregistry.BaseHeader)
21n/a self.assertIsInstance(h, headerregistry.UnstructuredHeader)
22n/a
23n/a def test_name_case_ignored(self):
24n/a factory = headerregistry.HeaderRegistry()
25n/a # Whitebox check that test is valid
26n/a self.assertNotIn('Subject', factory.registry)
27n/a h = factory('Subject', 'test')
28n/a self.assertIsInstance(h, headerregistry.BaseHeader)
29n/a self.assertIsInstance(h, headerregistry.UniqueUnstructuredHeader)
30n/a
31n/a class FooBase:
32n/a def __init__(self, *args, **kw):
33n/a pass
34n/a
35n/a def test_override_default_base_class(self):
36n/a factory = headerregistry.HeaderRegistry(base_class=self.FooBase)
37n/a h = factory('foobar', 'test')
38n/a self.assertIsInstance(h, self.FooBase)
39n/a self.assertIsInstance(h, headerregistry.UnstructuredHeader)
40n/a
41n/a class FooDefault:
42n/a parse = headerregistry.UnstructuredHeader.parse
43n/a
44n/a def test_override_default_class(self):
45n/a factory = headerregistry.HeaderRegistry(default_class=self.FooDefault)
46n/a h = factory('foobar', 'test')
47n/a self.assertIsInstance(h, headerregistry.BaseHeader)
48n/a self.assertIsInstance(h, self.FooDefault)
49n/a
50n/a def test_override_default_class_only_overrides_default(self):
51n/a factory = headerregistry.HeaderRegistry(default_class=self.FooDefault)
52n/a h = factory('subject', 'test')
53n/a self.assertIsInstance(h, headerregistry.BaseHeader)
54n/a self.assertIsInstance(h, headerregistry.UniqueUnstructuredHeader)
55n/a
56n/a def test_dont_use_default_map(self):
57n/a factory = headerregistry.HeaderRegistry(use_default_map=False)
58n/a h = factory('subject', 'test')
59n/a self.assertIsInstance(h, headerregistry.BaseHeader)
60n/a self.assertIsInstance(h, headerregistry.UnstructuredHeader)
61n/a
62n/a def test_map_to_type(self):
63n/a factory = headerregistry.HeaderRegistry()
64n/a h1 = factory('foobar', 'test')
65n/a factory.map_to_type('foobar', headerregistry.UniqueUnstructuredHeader)
66n/a h2 = factory('foobar', 'test')
67n/a self.assertIsInstance(h1, headerregistry.BaseHeader)
68n/a self.assertIsInstance(h1, headerregistry.UnstructuredHeader)
69n/a self.assertIsInstance(h2, headerregistry.BaseHeader)
70n/a self.assertIsInstance(h2, headerregistry.UniqueUnstructuredHeader)
71n/a
72n/a
73n/aclass TestHeaderBase(TestEmailBase):
74n/a
75n/a factory = headerregistry.HeaderRegistry()
76n/a
77n/a def make_header(self, name, value):
78n/a return self.factory(name, value)
79n/a
80n/a
81n/aclass TestBaseHeaderFeatures(TestHeaderBase):
82n/a
83n/a def test_str(self):
84n/a h = self.make_header('subject', 'this is a test')
85n/a self.assertIsInstance(h, str)
86n/a self.assertEqual(h, 'this is a test')
87n/a self.assertEqual(str(h), 'this is a test')
88n/a
89n/a def test_substr(self):
90n/a h = self.make_header('subject', 'this is a test')
91n/a self.assertEqual(h[5:7], 'is')
92n/a
93n/a def test_has_name(self):
94n/a h = self.make_header('subject', 'this is a test')
95n/a self.assertEqual(h.name, 'subject')
96n/a
97n/a def _test_attr_ro(self, attr):
98n/a h = self.make_header('subject', 'this is a test')
99n/a with self.assertRaises(AttributeError):
100n/a setattr(h, attr, 'foo')
101n/a
102n/a def test_name_read_only(self):
103n/a self._test_attr_ro('name')
104n/a
105n/a def test_defects_read_only(self):
106n/a self._test_attr_ro('defects')
107n/a
108n/a def test_defects_is_tuple(self):
109n/a h = self.make_header('subject', 'this is a test')
110n/a self.assertEqual(len(h.defects), 0)
111n/a self.assertIsInstance(h.defects, tuple)
112n/a # Make sure it is still true when there are defects.
113n/a h = self.make_header('date', '')
114n/a self.assertEqual(len(h.defects), 1)
115n/a self.assertIsInstance(h.defects, tuple)
116n/a
117n/a # XXX: FIXME
118n/a #def test_CR_in_value(self):
119n/a # # XXX: this also re-raises the issue of embedded headers,
120n/a # # need test and solution for that.
121n/a # value = '\r'.join(['this is', ' a test'])
122n/a # h = self.make_header('subject', value)
123n/a # self.assertEqual(h, value)
124n/a # self.assertDefectsEqual(h.defects, [errors.ObsoleteHeaderDefect])
125n/a
126n/a
127n/a@parameterize
128n/aclass TestUnstructuredHeader(TestHeaderBase):
129n/a
130n/a def string_as_value(self,
131n/a source,
132n/a decoded,
133n/a *args):
134n/a l = len(args)
135n/a defects = args[0] if l>0 else []
136n/a header = 'Subject:' + (' ' if source else '')
137n/a folded = header + (args[1] if l>1 else source) + '\n'
138n/a h = self.make_header('Subject', source)
139n/a self.assertEqual(h, decoded)
140n/a self.assertDefectsEqual(h.defects, defects)
141n/a self.assertEqual(h.fold(policy=policy.default), folded)
142n/a
143n/a string_params = {
144n/a
145n/a 'rfc2047_simple_quopri': (
146n/a '=?utf-8?q?this_is_a_test?=',
147n/a 'this is a test',
148n/a [],
149n/a 'this is a test'),
150n/a
151n/a 'rfc2047_gb2312_base64': (
152n/a '=?gb2312?b?1eLKx9bQzsSy4srUo6E=?=',
153n/a '\u8fd9\u662f\u4e2d\u6587\u6d4b\u8bd5\uff01',
154n/a [],
155n/a '=?utf-8?b?6L+Z5piv5Lit5paH5rWL6K+V77yB?='),
156n/a
157n/a 'rfc2047_simple_nonascii_quopri': (
158n/a '=?utf-8?q?=C3=89ric?=',
159n/a 'Éric'),
160n/a
161n/a 'rfc2047_quopri_with_regular_text': (
162n/a 'The =?utf-8?q?=C3=89ric=2C?= Himself',
163n/a 'The Éric, Himself'),
164n/a
165n/a }
166n/a
167n/a
168n/a@parameterize
169n/aclass TestDateHeader(TestHeaderBase):
170n/a
171n/a datestring = 'Sun, 23 Sep 2001 20:10:55 -0700'
172n/a utcoffset = datetime.timedelta(hours=-7)
173n/a tz = datetime.timezone(utcoffset)
174n/a dt = datetime.datetime(2001, 9, 23, 20, 10, 55, tzinfo=tz)
175n/a
176n/a def test_parse_date(self):
177n/a h = self.make_header('date', self.datestring)
178n/a self.assertEqual(h, self.datestring)
179n/a self.assertEqual(h.datetime, self.dt)
180n/a self.assertEqual(h.datetime.utcoffset(), self.utcoffset)
181n/a self.assertEqual(h.defects, ())
182n/a
183n/a def test_set_from_datetime(self):
184n/a h = self.make_header('date', self.dt)
185n/a self.assertEqual(h, self.datestring)
186n/a self.assertEqual(h.datetime, self.dt)
187n/a self.assertEqual(h.defects, ())
188n/a
189n/a def test_date_header_properties(self):
190n/a h = self.make_header('date', self.datestring)
191n/a self.assertIsInstance(h, headerregistry.UniqueDateHeader)
192n/a self.assertEqual(h.max_count, 1)
193n/a self.assertEqual(h.defects, ())
194n/a
195n/a def test_resent_date_header_properties(self):
196n/a h = self.make_header('resent-date', self.datestring)
197n/a self.assertIsInstance(h, headerregistry.DateHeader)
198n/a self.assertEqual(h.max_count, None)
199n/a self.assertEqual(h.defects, ())
200n/a
201n/a def test_no_value_is_defect(self):
202n/a h = self.make_header('date', '')
203n/a self.assertEqual(len(h.defects), 1)
204n/a self.assertIsInstance(h.defects[0], errors.HeaderMissingRequiredValue)
205n/a
206n/a def test_datetime_read_only(self):
207n/a h = self.make_header('date', self.datestring)
208n/a with self.assertRaises(AttributeError):
209n/a h.datetime = 'foo'
210n/a
211n/a def test_set_date_header_from_datetime(self):
212n/a m = Message(policy=policy.default)
213n/a m['Date'] = self.dt
214n/a self.assertEqual(m['Date'], self.datestring)
215n/a self.assertEqual(m['Date'].datetime, self.dt)
216n/a
217n/a
218n/a@parameterize
219n/aclass TestContentTypeHeader(TestHeaderBase):
220n/a
221n/a def content_type_as_value(self,
222n/a source,
223n/a content_type,
224n/a maintype,
225n/a subtype,
226n/a *args):
227n/a l = len(args)
228n/a parmdict = args[0] if l>0 else {}
229n/a defects = args[1] if l>1 else []
230n/a decoded = args[2] if l>2 and args[2] is not DITTO else source
231n/a header = 'Content-Type:' + ' ' if source else ''
232n/a folded = args[3] if l>3 else header + source + '\n'
233n/a h = self.make_header('Content-Type', source)
234n/a self.assertEqual(h.content_type, content_type)
235n/a self.assertEqual(h.maintype, maintype)
236n/a self.assertEqual(h.subtype, subtype)
237n/a self.assertEqual(h.params, parmdict)
238n/a with self.assertRaises(TypeError):
239n/a h.params['abc'] = 'xyz' # params is read-only.
240n/a self.assertDefectsEqual(h.defects, defects)
241n/a self.assertEqual(h, decoded)
242n/a self.assertEqual(h.fold(policy=policy.default), folded)
243n/a
244n/a content_type_params = {
245n/a
246n/a # Examples from RFC 2045.
247n/a
248n/a 'RFC_2045_1': (
249n/a 'text/plain; charset=us-ascii (Plain text)',
250n/a 'text/plain',
251n/a 'text',
252n/a 'plain',
253n/a {'charset': 'us-ascii'},
254n/a [],
255n/a 'text/plain; charset="us-ascii"'),
256n/a
257n/a 'RFC_2045_2': (
258n/a 'text/plain; charset=us-ascii',
259n/a 'text/plain',
260n/a 'text',
261n/a 'plain',
262n/a {'charset': 'us-ascii'},
263n/a [],
264n/a 'text/plain; charset="us-ascii"'),
265n/a
266n/a 'RFC_2045_3': (
267n/a 'text/plain; charset="us-ascii"',
268n/a 'text/plain',
269n/a 'text',
270n/a 'plain',
271n/a {'charset': 'us-ascii'}),
272n/a
273n/a # RFC 2045 5.2 says syntactically invalid values are to be treated as
274n/a # text/plain.
275n/a
276n/a 'no_subtype_in_content_type': (
277n/a 'text/',
278n/a 'text/plain',
279n/a 'text',
280n/a 'plain',
281n/a {},
282n/a [errors.InvalidHeaderDefect]),
283n/a
284n/a 'no_slash_in_content_type': (
285n/a 'foo',
286n/a 'text/plain',
287n/a 'text',
288n/a 'plain',
289n/a {},
290n/a [errors.InvalidHeaderDefect]),
291n/a
292n/a 'junk_text_in_content_type': (
293n/a '<crazy "stuff">',
294n/a 'text/plain',
295n/a 'text',
296n/a 'plain',
297n/a {},
298n/a [errors.InvalidHeaderDefect]),
299n/a
300n/a 'too_many_slashes_in_content_type': (
301n/a 'image/jpeg/foo',
302n/a 'text/plain',
303n/a 'text',
304n/a 'plain',
305n/a {},
306n/a [errors.InvalidHeaderDefect]),
307n/a
308n/a # But unknown names are OK. We could make non-IANA names a defect, but
309n/a # by not doing so we make ourselves future proof. The fact that they
310n/a # are unknown will be detectable by the fact that they don't appear in
311n/a # the mime_registry...and the application is free to extend that list
312n/a # to handle them even if the core library doesn't.
313n/a
314n/a 'unknown_content_type': (
315n/a 'bad/names',
316n/a 'bad/names',
317n/a 'bad',
318n/a 'names'),
319n/a
320n/a # The content type is case insensitive, and CFWS is ignored.
321n/a
322n/a 'mixed_case_content_type': (
323n/a 'ImAge/JPeg',
324n/a 'image/jpeg',
325n/a 'image',
326n/a 'jpeg'),
327n/a
328n/a 'spaces_in_content_type': (
329n/a ' text / plain ',
330n/a 'text/plain',
331n/a 'text',
332n/a 'plain'),
333n/a
334n/a 'cfws_in_content_type': (
335n/a '(foo) text (bar)/(baz)plain(stuff)',
336n/a 'text/plain',
337n/a 'text',
338n/a 'plain'),
339n/a
340n/a # test some parameters (more tests could be added for parameters
341n/a # associated with other content types, but since parameter parsing is
342n/a # generic they would be redundant for the current implementation).
343n/a
344n/a 'charset_param': (
345n/a 'text/plain; charset="utf-8"',
346n/a 'text/plain',
347n/a 'text',
348n/a 'plain',
349n/a {'charset': 'utf-8'}),
350n/a
351n/a 'capitalized_charset': (
352n/a 'text/plain; charset="US-ASCII"',
353n/a 'text/plain',
354n/a 'text',
355n/a 'plain',
356n/a {'charset': 'US-ASCII'}),
357n/a
358n/a 'unknown_charset': (
359n/a 'text/plain; charset="fOo"',
360n/a 'text/plain',
361n/a 'text',
362n/a 'plain',
363n/a {'charset': 'fOo'}),
364n/a
365n/a 'capitalized_charset_param_name_and_comment': (
366n/a 'text/plain; (interjection) Charset="utf-8"',
367n/a 'text/plain',
368n/a 'text',
369n/a 'plain',
370n/a {'charset': 'utf-8'},
371n/a [],
372n/a # Should the parameter name be lowercased here?
373n/a 'text/plain; Charset="utf-8"'),
374n/a
375n/a # Since this is pretty much the ur-mimeheader, we'll put all the tests
376n/a # that exercise the parameter parsing and formatting here.
377n/a #
378n/a # XXX: question: is minimal quoting preferred?
379n/a
380n/a 'unquoted_param_value': (
381n/a 'text/plain; title=foo',
382n/a 'text/plain',
383n/a 'text',
384n/a 'plain',
385n/a {'title': 'foo'},
386n/a [],
387n/a 'text/plain; title="foo"'),
388n/a
389n/a 'param_value_with_tspecials': (
390n/a 'text/plain; title="(bar)foo blue"',
391n/a 'text/plain',
392n/a 'text',
393n/a 'plain',
394n/a {'title': '(bar)foo blue'}),
395n/a
396n/a 'param_with_extra_quoted_whitespace': (
397n/a 'text/plain; title=" a loong way \t home "',
398n/a 'text/plain',
399n/a 'text',
400n/a 'plain',
401n/a {'title': ' a loong way \t home '}),
402n/a
403n/a 'bad_params': (
404n/a 'blarg; baz; boo',
405n/a 'text/plain',
406n/a 'text',
407n/a 'plain',
408n/a {'baz': '', 'boo': ''},
409n/a [errors.InvalidHeaderDefect]*3),
410n/a
411n/a 'spaces_around_param_equals': (
412n/a 'Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"',
413n/a 'multipart/mixed',
414n/a 'multipart',
415n/a 'mixed',
416n/a {'boundary': 'CPIMSSMTPC06p5f3tG'},
417n/a [],
418n/a 'Multipart/mixed; boundary="CPIMSSMTPC06p5f3tG"'),
419n/a
420n/a 'spaces_around_semis': (
421n/a ('image/jpeg; name="wibble.JPG" ; x-mac-type="4A504547" ; '
422n/a 'x-mac-creator="474B4F4E"'),
423n/a 'image/jpeg',
424n/a 'image',
425n/a 'jpeg',
426n/a {'name': 'wibble.JPG',
427n/a 'x-mac-type': '4A504547',
428n/a 'x-mac-creator': '474B4F4E'},
429n/a [],
430n/a ('image/jpeg; name="wibble.JPG"; x-mac-type="4A504547"; '
431n/a 'x-mac-creator="474B4F4E"'),
432n/a # XXX: it could be that we will eventually prefer to fold starting
433n/a # from the decoded value, in which case these spaces and similar
434n/a # spaces in other tests will be wrong.
435n/a ('Content-Type: image/jpeg; name="wibble.JPG" ; '
436n/a 'x-mac-type="4A504547" ;\n'
437n/a ' x-mac-creator="474B4F4E"\n'),
438n/a ),
439n/a
440n/a 'semis_inside_quotes': (
441n/a 'image/jpeg; name="Jim&amp;&amp;Jill"',
442n/a 'image/jpeg',
443n/a 'image',
444n/a 'jpeg',
445n/a {'name': 'Jim&amp;&amp;Jill'}),
446n/a
447n/a 'single_quotes_inside_quotes': (
448n/a 'image/jpeg; name="Jim \'Bob\' Jill"',
449n/a 'image/jpeg',
450n/a 'image',
451n/a 'jpeg',
452n/a {'name': "Jim 'Bob' Jill"}),
453n/a
454n/a 'double_quotes_inside_quotes': (
455n/a r'image/jpeg; name="Jim \"Bob\" Jill"',
456n/a 'image/jpeg',
457n/a 'image',
458n/a 'jpeg',
459n/a {'name': 'Jim "Bob" Jill'},
460n/a [],
461n/a r'image/jpeg; name="Jim \"Bob\" Jill"'),
462n/a
463n/a # XXX: This test works except for the refolding of the header. I'll
464n/a # deal with that bug when I deal with the other folding bugs.
465n/a #'non_ascii_in_params': (
466n/a # ('foo\xa7/bar; b\xa7r=two; '
467n/a # 'baz=thr\xa7e'.encode('latin-1').decode('us-ascii',
468n/a # 'surrogateescape')),
469n/a # 'foo\uFFFD/bar',
470n/a # 'foo\uFFFD',
471n/a # 'bar',
472n/a # {'b\uFFFDr': 'two', 'baz': 'thr\uFFFDe'},
473n/a # [errors.UndecodableBytesDefect]*3,
474n/a # 'foo�/bar; b�r="two"; baz="thr�e"',
475n/a # ),
476n/a
477n/a # RFC 2231 parameter tests.
478n/a
479n/a 'rfc2231_segmented_normal_values': (
480n/a 'image/jpeg; name*0="abc"; name*1=".html"',
481n/a 'image/jpeg',
482n/a 'image',
483n/a 'jpeg',
484n/a {'name': "abc.html"},
485n/a [],
486n/a 'image/jpeg; name="abc.html"'),
487n/a
488n/a 'quotes_inside_rfc2231_value': (
489n/a r'image/jpeg; bar*0="baz\"foobar"; bar*1="\"baz"',
490n/a 'image/jpeg',
491n/a 'image',
492n/a 'jpeg',
493n/a {'bar': 'baz"foobar"baz'},
494n/a [],
495n/a r'image/jpeg; bar="baz\"foobar\"baz"'),
496n/a
497n/a # XXX: This test works except for the refolding of the header. I'll
498n/a # deal with that bug when I deal with the other folding bugs.
499n/a #'non_ascii_rfc2231_value': (
500n/a # ('text/plain; charset=us-ascii; '
501n/a # "title*=us-ascii'en'This%20is%20"
502n/a # 'not%20f\xa7n').encode('latin-1').decode('us-ascii',
503n/a # 'surrogateescape'),
504n/a # 'text/plain',
505n/a # 'text',
506n/a # 'plain',
507n/a # {'charset': 'us-ascii', 'title': 'This is not f\uFFFDn'},
508n/a # [errors.UndecodableBytesDefect],
509n/a # 'text/plain; charset="us-ascii"; title="This is not f�n"'),
510n/a
511n/a 'rfc2231_encoded_charset': (
512n/a 'text/plain; charset*=ansi-x3.4-1968\'\'us-ascii',
513n/a 'text/plain',
514n/a 'text',
515n/a 'plain',
516n/a {'charset': 'us-ascii'},
517n/a [],
518n/a 'text/plain; charset="us-ascii"'),
519n/a
520n/a # This follows the RFC: no double quotes around encoded values.
521n/a 'rfc2231_encoded_no_double_quotes': (
522n/a ("text/plain;"
523n/a "\tname*0*=''This%20is%20;"
524n/a "\tname*1*=%2A%2A%2Afun%2A%2A%2A%20;"
525n/a '\tname*2="is it not.pdf"'),
526n/a 'text/plain',
527n/a 'text',
528n/a 'plain',
529n/a {'name': 'This is ***fun*** is it not.pdf'},
530n/a [],
531n/a 'text/plain; name="This is ***fun*** is it not.pdf"',
532n/a ('Content-Type: text/plain;\tname*0*=\'\'This%20is%20;\n'
533n/a '\tname*1*=%2A%2A%2Afun%2A%2A%2A%20;\tname*2="is it not.pdf"\n'),
534n/a ),
535n/a
536n/a # Make sure we also handle it if there are spurious double quotes.
537n/a 'rfc2231_encoded_with_double_quotes': (
538n/a ("text/plain;"
539n/a '\tname*0*="us-ascii\'\'This%20is%20even%20more%20";'
540n/a '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";'
541n/a '\tname*2="is it not.pdf"'),
542n/a 'text/plain',
543n/a 'text',
544n/a 'plain',
545n/a {'name': 'This is even more ***fun*** is it not.pdf'},
546n/a [errors.InvalidHeaderDefect]*2,
547n/a 'text/plain; name="This is even more ***fun*** is it not.pdf"',
548n/a ('Content-Type: text/plain;\t'
549n/a 'name*0*="us-ascii\'\'This%20is%20even%20more%20";\n'
550n/a '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it not.pdf"\n'),
551n/a ),
552n/a
553n/a 'rfc2231_single_quote_inside_double_quotes': (
554n/a ('text/plain; charset=us-ascii;'
555n/a '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";'
556n/a '\ttitle*1*="%2A%2A%2Afun%2A%2A%2A%20";'
557n/a '\ttitle*2="isn\'t it!"'),
558n/a 'text/plain',
559n/a 'text',
560n/a 'plain',
561n/a {'charset': 'us-ascii', 'title': "This is really ***fun*** isn't it!"},
562n/a [errors.InvalidHeaderDefect]*2,
563n/a ('text/plain; charset="us-ascii"; '
564n/a 'title="This is really ***fun*** isn\'t it!"'),
565n/a ('Content-Type: text/plain; charset=us-ascii;\n'
566n/a '\ttitle*0*="us-ascii\'en\'This%20is%20really%20";\n'
567n/a '\ttitle*1*="%2A%2A%2Afun%2A%2A%2A%20";\ttitle*2="isn\'t it!"\n'),
568n/a ),
569n/a
570n/a 'rfc2231_single_quote_in_value_with_charset_and_lang': (
571n/a ('application/x-foo;'
572n/a "\tname*0*=\"us-ascii'en-us'Frank's\"; name*1*=\" Document\""),
573n/a 'application/x-foo',
574n/a 'application',
575n/a 'x-foo',
576n/a {'name': "Frank's Document"},
577n/a [errors.InvalidHeaderDefect]*2,
578n/a 'application/x-foo; name="Frank\'s Document"',
579n/a ('Content-Type: application/x-foo;\t'
580n/a 'name*0*="us-ascii\'en-us\'Frank\'s";\n'
581n/a ' name*1*=" Document"\n'),
582n/a ),
583n/a
584n/a 'rfc2231_single_quote_in_non_encoded_value': (
585n/a ('application/x-foo;'
586n/a "\tname*0=\"us-ascii'en-us'Frank's\"; name*1=\" Document\""),
587n/a 'application/x-foo',
588n/a 'application',
589n/a 'x-foo',
590n/a {'name': "us-ascii'en-us'Frank's Document"},
591n/a [],
592n/a 'application/x-foo; name="us-ascii\'en-us\'Frank\'s Document"',
593n/a ('Content-Type: application/x-foo;\t'
594n/a 'name*0="us-ascii\'en-us\'Frank\'s";\n'
595n/a ' name*1=" Document"\n'),
596n/a ),
597n/a
598n/a 'rfc2231_no_language_or_charset': (
599n/a 'text/plain; NAME*0*=english_is_the_default.html',
600n/a 'text/plain',
601n/a 'text',
602n/a 'plain',
603n/a {'name': 'english_is_the_default.html'},
604n/a [errors.InvalidHeaderDefect],
605n/a 'text/plain; NAME="english_is_the_default.html"'),
606n/a
607n/a 'rfc2231_encoded_no_charset': (
608n/a ("text/plain;"
609n/a '\tname*0*="\'\'This%20is%20even%20more%20";'
610n/a '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";'
611n/a '\tname*2="is it.pdf"'),
612n/a 'text/plain',
613n/a 'text',
614n/a 'plain',
615n/a {'name': 'This is even more ***fun*** is it.pdf'},
616n/a [errors.InvalidHeaderDefect]*2,
617n/a 'text/plain; name="This is even more ***fun*** is it.pdf"',
618n/a ('Content-Type: text/plain;\t'
619n/a 'name*0*="\'\'This%20is%20even%20more%20";\n'
620n/a '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'),
621n/a ),
622n/a
623n/a # XXX: see below...the first name line here should be *0 not *0*.
624n/a 'rfc2231_partly_encoded': (
625n/a ("text/plain;"
626n/a '\tname*0*="\'\'This%20is%20even%20more%20";'
627n/a '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";'
628n/a '\tname*2="is it.pdf"'),
629n/a 'text/plain',
630n/a 'text',
631n/a 'plain',
632n/a {'name': 'This is even more ***fun*** is it.pdf'},
633n/a [errors.InvalidHeaderDefect]*2,
634n/a 'text/plain; name="This is even more ***fun*** is it.pdf"',
635n/a ('Content-Type: text/plain;\t'
636n/a 'name*0*="\'\'This%20is%20even%20more%20";\n'
637n/a '\tname*1*="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'),
638n/a ),
639n/a
640n/a 'rfc2231_partly_encoded_2': (
641n/a ("text/plain;"
642n/a '\tname*0*="\'\'This%20is%20even%20more%20";'
643n/a '\tname*1="%2A%2A%2Afun%2A%2A%2A%20";'
644n/a '\tname*2="is it.pdf"'),
645n/a 'text/plain',
646n/a 'text',
647n/a 'plain',
648n/a {'name': 'This is even more %2A%2A%2Afun%2A%2A%2A%20is it.pdf'},
649n/a [errors.InvalidHeaderDefect],
650n/a 'text/plain; name="This is even more %2A%2A%2Afun%2A%2A%2A%20is it.pdf"',
651n/a ('Content-Type: text/plain;\t'
652n/a 'name*0*="\'\'This%20is%20even%20more%20";\n'
653n/a '\tname*1="%2A%2A%2Afun%2A%2A%2A%20";\tname*2="is it.pdf"\n'),
654n/a ),
655n/a
656n/a 'rfc2231_unknown_charset_treated_as_ascii': (
657n/a "text/plain; name*0*=bogus'xx'ascii_is_the_default",
658n/a 'text/plain',
659n/a 'text',
660n/a 'plain',
661n/a {'name': 'ascii_is_the_default'},
662n/a [],
663n/a 'text/plain; name="ascii_is_the_default"'),
664n/a
665n/a 'rfc2231_bad_character_in_charset_parameter_value': (
666n/a "text/plain; charset*=ascii''utf-8%F1%F2%F3",
667n/a 'text/plain',
668n/a 'text',
669n/a 'plain',
670n/a {'charset': 'utf-8\uFFFD\uFFFD\uFFFD'},
671n/a [errors.UndecodableBytesDefect],
672n/a 'text/plain; charset="utf-8\uFFFD\uFFFD\uFFFD"'),
673n/a
674n/a 'rfc2231_utf_8_in_supposedly_ascii_charset_parameter_value': (
675n/a "text/plain; charset*=ascii''utf-8%E2%80%9D",
676n/a 'text/plain',
677n/a 'text',
678n/a 'plain',
679n/a {'charset': 'utf-8”'},
680n/a [errors.UndecodableBytesDefect],
681n/a 'text/plain; charset="utf-8”"',
682n/a ),
683n/a # XXX: if the above were *re*folded, it would get tagged as utf-8
684n/a # instead of ascii in the param, since it now contains non-ASCII.
685n/a
686n/a 'rfc2231_encoded_then_unencoded_segments': (
687n/a ('application/x-foo;'
688n/a '\tname*0*="us-ascii\'en-us\'My";'
689n/a '\tname*1=" Document";'
690n/a '\tname*2=" For You"'),
691n/a 'application/x-foo',
692n/a 'application',
693n/a 'x-foo',
694n/a {'name': 'My Document For You'},
695n/a [errors.InvalidHeaderDefect],
696n/a 'application/x-foo; name="My Document For You"',
697n/a ('Content-Type: application/x-foo;\t'
698n/a 'name*0*="us-ascii\'en-us\'My";\n'
699n/a '\tname*1=" Document";\tname*2=" For You"\n'),
700n/a ),
701n/a
702n/a # My reading of the RFC is that this is an invalid header. The RFC
703n/a # says that if charset and language information is given, the first
704n/a # segment *must* be encoded.
705n/a 'rfc2231_unencoded_then_encoded_segments': (
706n/a ('application/x-foo;'
707n/a '\tname*0=us-ascii\'en-us\'My;'
708n/a '\tname*1*=" Document";'
709n/a '\tname*2*=" For You"'),
710n/a 'application/x-foo',
711n/a 'application',
712n/a 'x-foo',
713n/a {'name': 'My Document For You'},
714n/a [errors.InvalidHeaderDefect]*3,
715n/a 'application/x-foo; name="My Document For You"',
716n/a ("Content-Type: application/x-foo;\tname*0=us-ascii'en-us'My;\t"
717n/a # XXX: the newline is in the wrong place, come back and fix
718n/a # this when the rest of tests pass.
719n/a 'name*1*=" Document"\n;'
720n/a '\tname*2*=" For You"\n'),
721n/a ),
722n/a
723n/a # XXX: I would say this one should default to ascii/en for the
724n/a # "encoded" segment, since the first segment is not encoded and is
725n/a # in double quotes, making the value a valid non-encoded string. The
726n/a # old parser decodes this just like the previous case, which may be the
727n/a # better Postel rule, but could equally result in borking headers that
728n/a # intentionally have quoted quotes in them. We could get this 98%
729n/a # right if we treat it as a quoted string *unless* it matches the
730n/a # charset'lang'value pattern exactly *and* there is at least one
731n/a # encoded segment. Implementing that algorithm will require some
732n/a # refactoring, so I haven't done it (yet).
733n/a
734n/a 'rfc2231_qouted_unencoded_then_encoded_segments': (
735n/a ('application/x-foo;'
736n/a '\tname*0="us-ascii\'en-us\'My";'
737n/a '\tname*1*=" Document";'
738n/a '\tname*2*=" For You"'),
739n/a 'application/x-foo',
740n/a 'application',
741n/a 'x-foo',
742n/a {'name': "us-ascii'en-us'My Document For You"},
743n/a [errors.InvalidHeaderDefect]*2,
744n/a 'application/x-foo; name="us-ascii\'en-us\'My Document For You"',
745n/a ('Content-Type: application/x-foo;\t'
746n/a 'name*0="us-ascii\'en-us\'My";\n'
747n/a '\tname*1*=" Document";\tname*2*=" For You"\n'),
748n/a ),
749n/a
750n/a }
751n/a
752n/a
753n/a@parameterize
754n/aclass TestContentTransferEncoding(TestHeaderBase):
755n/a
756n/a def cte_as_value(self,
757n/a source,
758n/a cte,
759n/a *args):
760n/a l = len(args)
761n/a defects = args[0] if l>0 else []
762n/a decoded = args[1] if l>1 and args[1] is not DITTO else source
763n/a header = 'Content-Transfer-Encoding:' + ' ' if source else ''
764n/a folded = args[2] if l>2 else header + source + '\n'
765n/a h = self.make_header('Content-Transfer-Encoding', source)
766n/a self.assertEqual(h.cte, cte)
767n/a self.assertDefectsEqual(h.defects, defects)
768n/a self.assertEqual(h, decoded)
769n/a self.assertEqual(h.fold(policy=policy.default), folded)
770n/a
771n/a cte_params = {
772n/a
773n/a 'RFC_2183_1': (
774n/a 'base64',
775n/a 'base64',),
776n/a
777n/a 'no_value': (
778n/a '',
779n/a '7bit',
780n/a [errors.HeaderMissingRequiredValue],
781n/a '',
782n/a 'Content-Transfer-Encoding:\n',
783n/a ),
784n/a
785n/a 'junk_after_cte': (
786n/a '7bit and a bunch more',
787n/a '7bit',
788n/a [errors.InvalidHeaderDefect]),
789n/a
790n/a }
791n/a
792n/a
793n/a@parameterize
794n/aclass TestContentDisposition(TestHeaderBase):
795n/a
796n/a def content_disp_as_value(self,
797n/a source,
798n/a content_disposition,
799n/a *args):
800n/a l = len(args)
801n/a parmdict = args[0] if l>0 else {}
802n/a defects = args[1] if l>1 else []
803n/a decoded = args[2] if l>2 and args[2] is not DITTO else source
804n/a header = 'Content-Disposition:' + ' ' if source else ''
805n/a folded = args[3] if l>3 else header + source + '\n'
806n/a h = self.make_header('Content-Disposition', source)
807n/a self.assertEqual(h.content_disposition, content_disposition)
808n/a self.assertEqual(h.params, parmdict)
809n/a self.assertDefectsEqual(h.defects, defects)
810n/a self.assertEqual(h, decoded)
811n/a self.assertEqual(h.fold(policy=policy.default), folded)
812n/a
813n/a content_disp_params = {
814n/a
815n/a # Examples from RFC 2183.
816n/a
817n/a 'RFC_2183_1': (
818n/a 'inline',
819n/a 'inline',),
820n/a
821n/a 'RFC_2183_2': (
822n/a ('attachment; filename=genome.jpeg;'
823n/a ' modification-date="Wed, 12 Feb 1997 16:29:51 -0500";'),
824n/a 'attachment',
825n/a {'filename': 'genome.jpeg',
826n/a 'modification-date': 'Wed, 12 Feb 1997 16:29:51 -0500'},
827n/a [],
828n/a ('attachment; filename="genome.jpeg"; '
829n/a 'modification-date="Wed, 12 Feb 1997 16:29:51 -0500"'),
830n/a ('Content-Disposition: attachment; filename=genome.jpeg;\n'
831n/a ' modification-date="Wed, 12 Feb 1997 16:29:51 -0500";\n'),
832n/a ),
833n/a
834n/a 'no_value': (
835n/a '',
836n/a None,
837n/a {},
838n/a [errors.HeaderMissingRequiredValue],
839n/a '',
840n/a 'Content-Disposition:\n'),
841n/a
842n/a 'invalid_value': (
843n/a 'ab./k',
844n/a 'ab.',
845n/a {},
846n/a [errors.InvalidHeaderDefect]),
847n/a
848n/a 'invalid_value_with_params': (
849n/a 'ab./k; filename="foo"',
850n/a 'ab.',
851n/a {'filename': 'foo'},
852n/a [errors.InvalidHeaderDefect]),
853n/a
854n/a }
855n/a
856n/a
857n/a@parameterize
858n/aclass TestMIMEVersionHeader(TestHeaderBase):
859n/a
860n/a def version_string_as_MIME_Version(self,
861n/a source,
862n/a decoded,
863n/a version,
864n/a major,
865n/a minor,
866n/a defects):
867n/a h = self.make_header('MIME-Version', source)
868n/a self.assertEqual(h, decoded)
869n/a self.assertEqual(h.version, version)
870n/a self.assertEqual(h.major, major)
871n/a self.assertEqual(h.minor, minor)
872n/a self.assertDefectsEqual(h.defects, defects)
873n/a if source:
874n/a source = ' ' + source
875n/a self.assertEqual(h.fold(policy=policy.default),
876n/a 'MIME-Version:' + source + '\n')
877n/a
878n/a version_string_params = {
879n/a
880n/a # Examples from the RFC.
881n/a
882n/a 'RFC_2045_1': (
883n/a '1.0',
884n/a '1.0',
885n/a '1.0',
886n/a 1,
887n/a 0,
888n/a []),
889n/a
890n/a 'RFC_2045_2': (
891n/a '1.0 (produced by MetaSend Vx.x)',
892n/a '1.0 (produced by MetaSend Vx.x)',
893n/a '1.0',
894n/a 1,
895n/a 0,
896n/a []),
897n/a
898n/a 'RFC_2045_3': (
899n/a '(produced by MetaSend Vx.x) 1.0',
900n/a '(produced by MetaSend Vx.x) 1.0',
901n/a '1.0',
902n/a 1,
903n/a 0,
904n/a []),
905n/a
906n/a 'RFC_2045_4': (
907n/a '1.(produced by MetaSend Vx.x)0',
908n/a '1.(produced by MetaSend Vx.x)0',
909n/a '1.0',
910n/a 1,
911n/a 0,
912n/a []),
913n/a
914n/a # Other valid values.
915n/a
916n/a '1_1': (
917n/a '1.1',
918n/a '1.1',
919n/a '1.1',
920n/a 1,
921n/a 1,
922n/a []),
923n/a
924n/a '2_1': (
925n/a '2.1',
926n/a '2.1',
927n/a '2.1',
928n/a 2,
929n/a 1,
930n/a []),
931n/a
932n/a 'whitespace': (
933n/a '1 .0',
934n/a '1 .0',
935n/a '1.0',
936n/a 1,
937n/a 0,
938n/a []),
939n/a
940n/a 'leading_trailing_whitespace_ignored': (
941n/a ' 1.0 ',
942n/a ' 1.0 ',
943n/a '1.0',
944n/a 1,
945n/a 0,
946n/a []),
947n/a
948n/a # Recoverable invalid values. We can recover here only because we
949n/a # already have a valid value by the time we encounter the garbage.
950n/a # Anywhere else, and we don't know where the garbage ends.
951n/a
952n/a 'non_comment_garbage_after': (
953n/a '1.0 <abc>',
954n/a '1.0 <abc>',
955n/a '1.0',
956n/a 1,
957n/a 0,
958n/a [errors.InvalidHeaderDefect]),
959n/a
960n/a # Unrecoverable invalid values. We *could* apply more heuristics to
961n/a # get something out of the first two, but doing so is not worth the
962n/a # effort.
963n/a
964n/a 'non_comment_garbage_before': (
965n/a '<abc> 1.0',
966n/a '<abc> 1.0',
967n/a None,
968n/a None,
969n/a None,
970n/a [errors.InvalidHeaderDefect]),
971n/a
972n/a 'non_comment_garbage_inside': (
973n/a '1.<abc>0',
974n/a '1.<abc>0',
975n/a None,
976n/a None,
977n/a None,
978n/a [errors.InvalidHeaderDefect]),
979n/a
980n/a 'two_periods': (
981n/a '1..0',
982n/a '1..0',
983n/a None,
984n/a None,
985n/a None,
986n/a [errors.InvalidHeaderDefect]),
987n/a
988n/a '2_x': (
989n/a '2.x',
990n/a '2.x',
991n/a None, # This could be 2, but it seems safer to make it None.
992n/a None,
993n/a None,
994n/a [errors.InvalidHeaderDefect]),
995n/a
996n/a 'foo': (
997n/a 'foo',
998n/a 'foo',
999n/a None,
1000n/a None,
1001n/a None,
1002n/a [errors.InvalidHeaderDefect]),
1003n/a
1004n/a 'missing': (
1005n/a '',
1006n/a '',
1007n/a None,
1008n/a None,
1009n/a None,
1010n/a [errors.HeaderMissingRequiredValue]),
1011n/a
1012n/a }
1013n/a
1014n/a
1015n/a@parameterize
1016n/aclass TestAddressHeader(TestHeaderBase):
1017n/a
1018n/a example_params = {
1019n/a
1020n/a 'empty':
1021n/a ('<>',
1022n/a [errors.InvalidHeaderDefect],
1023n/a '<>',
1024n/a '',
1025n/a '<>',
1026n/a '',
1027n/a '',
1028n/a None),
1029n/a
1030n/a 'address_only':
1031n/a ('zippy@pinhead.com',
1032n/a [],
1033n/a 'zippy@pinhead.com',
1034n/a '',
1035n/a 'zippy@pinhead.com',
1036n/a 'zippy',
1037n/a 'pinhead.com',
1038n/a None),
1039n/a
1040n/a 'name_and_address':
1041n/a ('Zaphrod Beblebrux <zippy@pinhead.com>',
1042n/a [],
1043n/a 'Zaphrod Beblebrux <zippy@pinhead.com>',
1044n/a 'Zaphrod Beblebrux',
1045n/a 'zippy@pinhead.com',
1046n/a 'zippy',
1047n/a 'pinhead.com',
1048n/a None),
1049n/a
1050n/a 'quoted_local_part':
1051n/a ('Zaphrod Beblebrux <"foo bar"@pinhead.com>',
1052n/a [],
1053n/a 'Zaphrod Beblebrux <"foo bar"@pinhead.com>',
1054n/a 'Zaphrod Beblebrux',
1055n/a '"foo bar"@pinhead.com',
1056n/a 'foo bar',
1057n/a 'pinhead.com',
1058n/a None),
1059n/a
1060n/a 'quoted_parens_in_name':
1061n/a (r'"A \(Special\) Person" <person@dom.ain>',
1062n/a [],
1063n/a '"A (Special) Person" <person@dom.ain>',
1064n/a 'A (Special) Person',
1065n/a 'person@dom.ain',
1066n/a 'person',
1067n/a 'dom.ain',
1068n/a None),
1069n/a
1070n/a 'quoted_backslashes_in_name':
1071n/a (r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>',
1072n/a [],
1073n/a r'"Arthur \\Backslash\\ Foobar" <person@dom.ain>',
1074n/a r'Arthur \Backslash\ Foobar',
1075n/a 'person@dom.ain',
1076n/a 'person',
1077n/a 'dom.ain',
1078n/a None),
1079n/a
1080n/a 'name_with_dot':
1081n/a ('John X. Doe <jxd@example.com>',
1082n/a [errors.ObsoleteHeaderDefect],
1083n/a '"John X. Doe" <jxd@example.com>',
1084n/a 'John X. Doe',
1085n/a 'jxd@example.com',
1086n/a 'jxd',
1087n/a 'example.com',
1088n/a None),
1089n/a
1090n/a 'quoted_strings_in_local_part':
1091n/a ('""example" example"@example.com',
1092n/a [errors.InvalidHeaderDefect]*3,
1093n/a '"example example"@example.com',
1094n/a '',
1095n/a '"example example"@example.com',
1096n/a 'example example',
1097n/a 'example.com',
1098n/a None),
1099n/a
1100n/a 'escaped_quoted_strings_in_local_part':
1101n/a (r'"\"example\" example"@example.com',
1102n/a [],
1103n/a r'"\"example\" example"@example.com',
1104n/a '',
1105n/a r'"\"example\" example"@example.com',
1106n/a r'"example" example',
1107n/a 'example.com',
1108n/a None),
1109n/a
1110n/a 'escaped_escapes_in_local_part':
1111n/a (r'"\\"example\\" example"@example.com',
1112n/a [errors.InvalidHeaderDefect]*5,
1113n/a r'"\\example\\\\ example"@example.com',
1114n/a '',
1115n/a r'"\\example\\\\ example"@example.com',
1116n/a r'\example\\ example',
1117n/a 'example.com',
1118n/a None),
1119n/a
1120n/a 'spaces_in_unquoted_local_part_collapsed':
1121n/a ('merwok wok @example.com',
1122n/a [errors.InvalidHeaderDefect]*2,
1123n/a '"merwok wok"@example.com',
1124n/a '',
1125n/a '"merwok wok"@example.com',
1126n/a 'merwok wok',
1127n/a 'example.com',
1128n/a None),
1129n/a
1130n/a 'spaces_around_dots_in_local_part_removed':
1131n/a ('merwok. wok . wok@example.com',
1132n/a [errors.ObsoleteHeaderDefect],
1133n/a 'merwok.wok.wok@example.com',
1134n/a '',
1135n/a 'merwok.wok.wok@example.com',
1136n/a 'merwok.wok.wok',
1137n/a 'example.com',
1138n/a None),
1139n/a
1140n/a 'rfc2047_atom_is_decoded':
1141n/a ('=?utf-8?q?=C3=89ric?= <foo@example.com>',
1142n/a [],
1143n/a 'Éric <foo@example.com>',
1144n/a 'Éric',
1145n/a 'foo@example.com',
1146n/a 'foo',
1147n/a 'example.com',
1148n/a None),
1149n/a
1150n/a 'rfc2047_atom_in_phrase_is_decoded':
1151n/a ('The =?utf-8?q?=C3=89ric=2C?= Himself <foo@example.com>',
1152n/a [],
1153n/a '"The Éric, Himself" <foo@example.com>',
1154n/a 'The Éric, Himself',
1155n/a 'foo@example.com',
1156n/a 'foo',
1157n/a 'example.com',
1158n/a None),
1159n/a
1160n/a 'rfc2047_atom_in_quoted_string_is_decoded':
1161n/a ('"=?utf-8?q?=C3=89ric?=" <foo@example.com>',
1162n/a [errors.InvalidHeaderDefect],
1163n/a 'Éric <foo@example.com>',
1164n/a 'Éric',
1165n/a 'foo@example.com',
1166n/a 'foo',
1167n/a 'example.com',
1168n/a None),
1169n/a
1170n/a }
1171n/a
1172n/a # XXX: Need many more examples, and in particular some with names in
1173n/a # trailing comments, which aren't currently handled. comments in
1174n/a # general are not handled yet.
1175n/a
1176n/a def example_as_address(self, source, defects, decoded, display_name,
1177n/a addr_spec, username, domain, comment):
1178n/a h = self.make_header('sender', source)
1179n/a self.assertEqual(h, decoded)
1180n/a self.assertDefectsEqual(h.defects, defects)
1181n/a a = h.address
1182n/a self.assertEqual(str(a), decoded)
1183n/a self.assertEqual(len(h.groups), 1)
1184n/a self.assertEqual([a], list(h.groups[0].addresses))
1185n/a self.assertEqual([a], list(h.addresses))
1186n/a self.assertEqual(a.display_name, display_name)
1187n/a self.assertEqual(a.addr_spec, addr_spec)
1188n/a self.assertEqual(a.username, username)
1189n/a self.assertEqual(a.domain, domain)
1190n/a # XXX: we have no comment support yet.
1191n/a #self.assertEqual(a.comment, comment)
1192n/a
1193n/a def example_as_group(self, source, defects, decoded, display_name,
1194n/a addr_spec, username, domain, comment):
1195n/a source = 'foo: {};'.format(source)
1196n/a gdecoded = 'foo: {};'.format(decoded) if decoded else 'foo:;'
1197n/a h = self.make_header('to', source)
1198n/a self.assertEqual(h, gdecoded)
1199n/a self.assertDefectsEqual(h.defects, defects)
1200n/a self.assertEqual(h.groups[0].addresses, h.addresses)
1201n/a self.assertEqual(len(h.groups), 1)
1202n/a self.assertEqual(len(h.addresses), 1)
1203n/a a = h.addresses[0]
1204n/a self.assertEqual(str(a), decoded)
1205n/a self.assertEqual(a.display_name, display_name)
1206n/a self.assertEqual(a.addr_spec, addr_spec)
1207n/a self.assertEqual(a.username, username)
1208n/a self.assertEqual(a.domain, domain)
1209n/a
1210n/a def test_simple_address_list(self):
1211n/a value = ('Fred <dinsdale@python.org>, foo@example.com, '
1212n/a '"Harry W. Hastings" <hasty@example.com>')
1213n/a h = self.make_header('to', value)
1214n/a self.assertEqual(h, value)
1215n/a self.assertEqual(len(h.groups), 3)
1216n/a self.assertEqual(len(h.addresses), 3)
1217n/a for i in range(3):
1218n/a self.assertEqual(h.groups[i].addresses[0], h.addresses[i])
1219n/a self.assertEqual(str(h.addresses[0]), 'Fred <dinsdale@python.org>')
1220n/a self.assertEqual(str(h.addresses[1]), 'foo@example.com')
1221n/a self.assertEqual(str(h.addresses[2]),
1222n/a '"Harry W. Hastings" <hasty@example.com>')
1223n/a self.assertEqual(h.addresses[2].display_name,
1224n/a 'Harry W. Hastings')
1225n/a
1226n/a def test_complex_address_list(self):
1227n/a examples = list(self.example_params.values())
1228n/a source = ('dummy list:;, another: (empty);,' +
1229n/a ', '.join([x[0] for x in examples[:4]]) + ', ' +
1230n/a r'"A \"list\"": ' +
1231n/a ', '.join([x[0] for x in examples[4:6]]) + ';,' +
1232n/a ', '.join([x[0] for x in examples[6:]])
1233n/a )
1234n/a # XXX: the fact that (empty) disappears here is a potential API design
1235n/a # bug. We don't currently have a way to preserve comments.
1236n/a expected = ('dummy list:;, another:;, ' +
1237n/a ', '.join([x[2] for x in examples[:4]]) + ', ' +
1238n/a r'"A \"list\"": ' +
1239n/a ', '.join([x[2] for x in examples[4:6]]) + ';, ' +
1240n/a ', '.join([x[2] for x in examples[6:]])
1241n/a )
1242n/a
1243n/a h = self.make_header('to', source)
1244n/a self.assertEqual(h.split(','), expected.split(','))
1245n/a self.assertEqual(h, expected)
1246n/a self.assertEqual(len(h.groups), 7 + len(examples) - 6)
1247n/a self.assertEqual(h.groups[0].display_name, 'dummy list')
1248n/a self.assertEqual(h.groups[1].display_name, 'another')
1249n/a self.assertEqual(h.groups[6].display_name, 'A "list"')
1250n/a self.assertEqual(len(h.addresses), len(examples))
1251n/a for i in range(4):
1252n/a self.assertIsNone(h.groups[i+2].display_name)
1253n/a self.assertEqual(str(h.groups[i+2].addresses[0]), examples[i][2])
1254n/a for i in range(7, 7 + len(examples) - 6):
1255n/a self.assertIsNone(h.groups[i].display_name)
1256n/a self.assertEqual(str(h.groups[i].addresses[0]), examples[i-1][2])
1257n/a for i in range(len(examples)):
1258n/a self.assertEqual(str(h.addresses[i]), examples[i][2])
1259n/a self.assertEqual(h.addresses[i].addr_spec, examples[i][4])
1260n/a
1261n/a def test_address_read_only(self):
1262n/a h = self.make_header('sender', 'abc@xyz.com')
1263n/a with self.assertRaises(AttributeError):
1264n/a h.address = 'foo'
1265n/a
1266n/a def test_addresses_read_only(self):
1267n/a h = self.make_header('sender', 'abc@xyz.com')
1268n/a with self.assertRaises(AttributeError):
1269n/a h.addresses = 'foo'
1270n/a
1271n/a def test_groups_read_only(self):
1272n/a h = self.make_header('sender', 'abc@xyz.com')
1273n/a with self.assertRaises(AttributeError):
1274n/a h.groups = 'foo'
1275n/a
1276n/a def test_addresses_types(self):
1277n/a source = 'me <who@example.com>'
1278n/a h = self.make_header('to', source)
1279n/a self.assertIsInstance(h.addresses, tuple)
1280n/a self.assertIsInstance(h.addresses[0], Address)
1281n/a
1282n/a def test_groups_types(self):
1283n/a source = 'me <who@example.com>'
1284n/a h = self.make_header('to', source)
1285n/a self.assertIsInstance(h.groups, tuple)
1286n/a self.assertIsInstance(h.groups[0], Group)
1287n/a
1288n/a def test_set_from_Address(self):
1289n/a h = self.make_header('to', Address('me', 'foo', 'example.com'))
1290n/a self.assertEqual(h, 'me <foo@example.com>')
1291n/a
1292n/a def test_set_from_Address_list(self):
1293n/a h = self.make_header('to', [Address('me', 'foo', 'example.com'),
1294n/a Address('you', 'bar', 'example.com')])
1295n/a self.assertEqual(h, 'me <foo@example.com>, you <bar@example.com>')
1296n/a
1297n/a def test_set_from_Address_and_Group_list(self):
1298n/a h = self.make_header('to', [Address('me', 'foo', 'example.com'),
1299n/a Group('bing', [Address('fiz', 'z', 'b.com'),
1300n/a Address('zif', 'f', 'c.com')]),
1301n/a Address('you', 'bar', 'example.com')])
1302n/a self.assertEqual(h, 'me <foo@example.com>, bing: fiz <z@b.com>, '
1303n/a 'zif <f@c.com>;, you <bar@example.com>')
1304n/a self.assertEqual(h.fold(policy=policy.default.clone(max_line_length=40)),
1305n/a 'to: me <foo@example.com>,\n'
1306n/a ' bing: fiz <z@b.com>, zif <f@c.com>;,\n'
1307n/a ' you <bar@example.com>\n')
1308n/a
1309n/a def test_set_from_Group_list(self):
1310n/a h = self.make_header('to', [Group('bing', [Address('fiz', 'z', 'b.com'),
1311n/a Address('zif', 'f', 'c.com')])])
1312n/a self.assertEqual(h, 'bing: fiz <z@b.com>, zif <f@c.com>;')
1313n/a
1314n/a
1315n/aclass TestAddressAndGroup(TestEmailBase):
1316n/a
1317n/a def _test_attr_ro(self, obj, attr):
1318n/a with self.assertRaises(AttributeError):
1319n/a setattr(obj, attr, 'foo')
1320n/a
1321n/a def test_address_display_name_ro(self):
1322n/a self._test_attr_ro(Address('foo', 'bar', 'baz'), 'display_name')
1323n/a
1324n/a def test_address_username_ro(self):
1325n/a self._test_attr_ro(Address('foo', 'bar', 'baz'), 'username')
1326n/a
1327n/a def test_address_domain_ro(self):
1328n/a self._test_attr_ro(Address('foo', 'bar', 'baz'), 'domain')
1329n/a
1330n/a def test_group_display_name_ro(self):
1331n/a self._test_attr_ro(Group('foo'), 'display_name')
1332n/a
1333n/a def test_group_addresses_ro(self):
1334n/a self._test_attr_ro(Group('foo'), 'addresses')
1335n/a
1336n/a def test_address_from_username_domain(self):
1337n/a a = Address('foo', 'bar', 'baz')
1338n/a self.assertEqual(a.display_name, 'foo')
1339n/a self.assertEqual(a.username, 'bar')
1340n/a self.assertEqual(a.domain, 'baz')
1341n/a self.assertEqual(a.addr_spec, 'bar@baz')
1342n/a self.assertEqual(str(a), 'foo <bar@baz>')
1343n/a
1344n/a def test_address_from_addr_spec(self):
1345n/a a = Address('foo', addr_spec='bar@baz')
1346n/a self.assertEqual(a.display_name, 'foo')
1347n/a self.assertEqual(a.username, 'bar')
1348n/a self.assertEqual(a.domain, 'baz')
1349n/a self.assertEqual(a.addr_spec, 'bar@baz')
1350n/a self.assertEqual(str(a), 'foo <bar@baz>')
1351n/a
1352n/a def test_address_with_no_display_name(self):
1353n/a a = Address(addr_spec='bar@baz')
1354n/a self.assertEqual(a.display_name, '')
1355n/a self.assertEqual(a.username, 'bar')
1356n/a self.assertEqual(a.domain, 'baz')
1357n/a self.assertEqual(a.addr_spec, 'bar@baz')
1358n/a self.assertEqual(str(a), 'bar@baz')
1359n/a
1360n/a def test_null_address(self):
1361n/a a = Address()
1362n/a self.assertEqual(a.display_name, '')
1363n/a self.assertEqual(a.username, '')
1364n/a self.assertEqual(a.domain, '')
1365n/a self.assertEqual(a.addr_spec, '<>')
1366n/a self.assertEqual(str(a), '<>')
1367n/a
1368n/a def test_domain_only(self):
1369n/a # This isn't really a valid address.
1370n/a a = Address(domain='buzz')
1371n/a self.assertEqual(a.display_name, '')
1372n/a self.assertEqual(a.username, '')
1373n/a self.assertEqual(a.domain, 'buzz')
1374n/a self.assertEqual(a.addr_spec, '@buzz')
1375n/a self.assertEqual(str(a), '@buzz')
1376n/a
1377n/a def test_username_only(self):
1378n/a # This isn't really a valid address.
1379n/a a = Address(username='buzz')
1380n/a self.assertEqual(a.display_name, '')
1381n/a self.assertEqual(a.username, 'buzz')
1382n/a self.assertEqual(a.domain, '')
1383n/a self.assertEqual(a.addr_spec, 'buzz')
1384n/a self.assertEqual(str(a), 'buzz')
1385n/a
1386n/a def test_display_name_only(self):
1387n/a a = Address('buzz')
1388n/a self.assertEqual(a.display_name, 'buzz')
1389n/a self.assertEqual(a.username, '')
1390n/a self.assertEqual(a.domain, '')
1391n/a self.assertEqual(a.addr_spec, '<>')
1392n/a self.assertEqual(str(a), 'buzz <>')
1393n/a
1394n/a def test_quoting(self):
1395n/a # Ideally we'd check every special individually, but I'm not up for
1396n/a # writing that many tests.
1397n/a a = Address('Sara J.', 'bad name', 'example.com')
1398n/a self.assertEqual(a.display_name, 'Sara J.')
1399n/a self.assertEqual(a.username, 'bad name')
1400n/a self.assertEqual(a.domain, 'example.com')
1401n/a self.assertEqual(a.addr_spec, '"bad name"@example.com')
1402n/a self.assertEqual(str(a), '"Sara J." <"bad name"@example.com>')
1403n/a
1404n/a def test_il8n(self):
1405n/a a = Address('Éric', 'wok', 'exàmple.com')
1406n/a self.assertEqual(a.display_name, 'Éric')
1407n/a self.assertEqual(a.username, 'wok')
1408n/a self.assertEqual(a.domain, 'exàmple.com')
1409n/a self.assertEqual(a.addr_spec, 'wok@exàmple.com')
1410n/a self.assertEqual(str(a), 'Éric <wok@exàmple.com>')
1411n/a
1412n/a # XXX: there is an API design issue that needs to be solved here.
1413n/a #def test_non_ascii_username_raises(self):
1414n/a # with self.assertRaises(ValueError):
1415n/a # Address('foo', 'wők', 'example.com')
1416n/a
1417n/a def test_non_ascii_username_in_addr_spec_raises(self):
1418n/a with self.assertRaises(ValueError):
1419n/a Address('foo', addr_spec='wők@example.com')
1420n/a
1421n/a def test_address_addr_spec_and_username_raises(self):
1422n/a with self.assertRaises(TypeError):
1423n/a Address('foo', username='bing', addr_spec='bar@baz')
1424n/a
1425n/a def test_address_addr_spec_and_domain_raises(self):
1426n/a with self.assertRaises(TypeError):
1427n/a Address('foo', domain='bing', addr_spec='bar@baz')
1428n/a
1429n/a def test_address_addr_spec_and_username_and_domain_raises(self):
1430n/a with self.assertRaises(TypeError):
1431n/a Address('foo', username='bong', domain='bing', addr_spec='bar@baz')
1432n/a
1433n/a def test_space_in_addr_spec_username_raises(self):
1434n/a with self.assertRaises(ValueError):
1435n/a Address('foo', addr_spec="bad name@example.com")
1436n/a
1437n/a def test_bad_addr_sepc_raises(self):
1438n/a with self.assertRaises(ValueError):
1439n/a Address('foo', addr_spec="name@ex[]ample.com")
1440n/a
1441n/a def test_empty_group(self):
1442n/a g = Group('foo')
1443n/a self.assertEqual(g.display_name, 'foo')
1444n/a self.assertEqual(g.addresses, tuple())
1445n/a self.assertEqual(str(g), 'foo:;')
1446n/a
1447n/a def test_empty_group_list(self):
1448n/a g = Group('foo', addresses=[])
1449n/a self.assertEqual(g.display_name, 'foo')
1450n/a self.assertEqual(g.addresses, tuple())
1451n/a self.assertEqual(str(g), 'foo:;')
1452n/a
1453n/a def test_null_group(self):
1454n/a g = Group()
1455n/a self.assertIsNone(g.display_name)
1456n/a self.assertEqual(g.addresses, tuple())
1457n/a self.assertEqual(str(g), 'None:;')
1458n/a
1459n/a def test_group_with_addresses(self):
1460n/a addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')]
1461n/a g = Group('foo', addrs)
1462n/a self.assertEqual(g.display_name, 'foo')
1463n/a self.assertEqual(g.addresses, tuple(addrs))
1464n/a self.assertEqual(str(g), 'foo: b <b@c>, a <b@c>;')
1465n/a
1466n/a def test_group_with_addresses_no_display_name(self):
1467n/a addrs = [Address('b', 'b', 'c'), Address('a', 'b','c')]
1468n/a g = Group(addresses=addrs)
1469n/a self.assertIsNone(g.display_name)
1470n/a self.assertEqual(g.addresses, tuple(addrs))
1471n/a self.assertEqual(str(g), 'None: b <b@c>, a <b@c>;')
1472n/a
1473n/a def test_group_with_one_address_no_display_name(self):
1474n/a addrs = [Address('b', 'b', 'c')]
1475n/a g = Group(addresses=addrs)
1476n/a self.assertIsNone(g.display_name)
1477n/a self.assertEqual(g.addresses, tuple(addrs))
1478n/a self.assertEqual(str(g), 'b <b@c>')
1479n/a
1480n/a def test_display_name_quoting(self):
1481n/a g = Group('foo.bar')
1482n/a self.assertEqual(g.display_name, 'foo.bar')
1483n/a self.assertEqual(g.addresses, tuple())
1484n/a self.assertEqual(str(g), '"foo.bar":;')
1485n/a
1486n/a def test_display_name_blanks_not_quoted(self):
1487n/a g = Group('foo bar')
1488n/a self.assertEqual(g.display_name, 'foo bar')
1489n/a self.assertEqual(g.addresses, tuple())
1490n/a self.assertEqual(str(g), 'foo bar:;')
1491n/a
1492n/a def test_set_message_header_from_address(self):
1493n/a a = Address('foo', 'bar', 'example.com')
1494n/a m = Message(policy=policy.default)
1495n/a m['To'] = a
1496n/a self.assertEqual(m['to'], 'foo <bar@example.com>')
1497n/a self.assertEqual(m['to'].addresses, (a,))
1498n/a
1499n/a def test_set_message_header_from_group(self):
1500n/a g = Group('foo bar')
1501n/a m = Message(policy=policy.default)
1502n/a m['To'] = g
1503n/a self.assertEqual(m['to'], 'foo bar:;')
1504n/a self.assertEqual(m['to'].addresses, g.addresses)
1505n/a
1506n/a
1507n/aclass TestFolding(TestHeaderBase):
1508n/a
1509n/a def test_short_unstructured(self):
1510n/a h = self.make_header('subject', 'this is a test')
1511n/a self.assertEqual(h.fold(policy=policy.default),
1512n/a 'subject: this is a test\n')
1513n/a
1514n/a def test_long_unstructured(self):
1515n/a h = self.make_header('Subject', 'This is a long header '
1516n/a 'line that will need to be folded into two lines '
1517n/a 'and will demonstrate basic folding')
1518n/a self.assertEqual(h.fold(policy=policy.default),
1519n/a 'Subject: This is a long header line that will '
1520n/a 'need to be folded into two lines\n'
1521n/a ' and will demonstrate basic folding\n')
1522n/a
1523n/a def test_unstructured_short_max_line_length(self):
1524n/a h = self.make_header('Subject', 'this is a short header '
1525n/a 'that will be folded anyway')
1526n/a self.assertEqual(
1527n/a h.fold(policy=policy.default.clone(max_line_length=20)),
1528n/a textwrap.dedent("""\
1529n/a Subject: this is a
1530n/a short header that
1531n/a will be folded
1532n/a anyway
1533n/a """))
1534n/a
1535n/a def test_fold_unstructured_single_word(self):
1536n/a h = self.make_header('Subject', 'test')
1537n/a self.assertEqual(h.fold(policy=policy.default), 'Subject: test\n')
1538n/a
1539n/a def test_fold_unstructured_short(self):
1540n/a h = self.make_header('Subject', 'test test test')
1541n/a self.assertEqual(h.fold(policy=policy.default),
1542n/a 'Subject: test test test\n')
1543n/a
1544n/a def test_fold_unstructured_with_overlong_word(self):
1545n/a h = self.make_header('Subject', 'thisisaverylonglineconsistingofa'
1546n/a 'singlewordthatwontfit')
1547n/a self.assertEqual(
1548n/a h.fold(policy=policy.default.clone(max_line_length=20)),
1549n/a 'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n')
1550n/a
1551n/a def test_fold_unstructured_with_two_overlong_words(self):
1552n/a h = self.make_header('Subject', 'thisisaverylonglineconsistingofa'
1553n/a 'singlewordthatwontfit plusanotherverylongwordthatwontfit')
1554n/a self.assertEqual(
1555n/a h.fold(policy=policy.default.clone(max_line_length=20)),
1556n/a 'Subject: thisisaverylonglineconsistingofasinglewordthatwontfit\n'
1557n/a ' plusanotherverylongwordthatwontfit\n')
1558n/a
1559n/a def test_fold_unstructured_with_slightly_long_word(self):
1560n/a h = self.make_header('Subject', 'thislongwordislessthanmaxlinelen')
1561n/a self.assertEqual(
1562n/a h.fold(policy=policy.default.clone(max_line_length=35)),
1563n/a 'Subject:\n thislongwordislessthanmaxlinelen\n')
1564n/a
1565n/a def test_fold_unstructured_with_commas(self):
1566n/a # The old wrapper would fold this at the commas.
1567n/a h = self.make_header('Subject', "This header is intended to "
1568n/a "demonstrate, in a fairly succinct way, that we now do "
1569n/a "not give a , special treatment in unstructured headers.")
1570n/a self.assertEqual(
1571n/a h.fold(policy=policy.default.clone(max_line_length=60)),
1572n/a textwrap.dedent("""\
1573n/a Subject: This header is intended to demonstrate, in a fairly
1574n/a succinct way, that we now do not give a , special treatment
1575n/a in unstructured headers.
1576n/a """))
1577n/a
1578n/a def test_fold_address_list(self):
1579n/a h = self.make_header('To', '"Theodore H. Perfect" <yes@man.com>, '
1580n/a '"My address is very long because my name is long" <foo@bar.com>, '
1581n/a '"Only A. Friend" <no@yes.com>')
1582n/a self.assertEqual(h.fold(policy=policy.default), textwrap.dedent("""\
1583n/a To: "Theodore H. Perfect" <yes@man.com>,
1584n/a "My address is very long because my name is long" <foo@bar.com>,
1585n/a "Only A. Friend" <no@yes.com>
1586n/a """))
1587n/a
1588n/a def test_fold_date_header(self):
1589n/a h = self.make_header('Date', 'Sat, 2 Feb 2002 17:00:06 -0800')
1590n/a self.assertEqual(h.fold(policy=policy.default),
1591n/a 'Date: Sat, 02 Feb 2002 17:00:06 -0800\n')
1592n/a
1593n/a
1594n/a
1595n/aif __name__ == '__main__':
1596n/a unittest.main()