• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import string
2import unittest
3from email import _header_value_parser as parser
4from email import errors
5from email import policy
6from test.test_email import TestEmailBase, parameterize
7
8class TestTokens(TestEmailBase):
9
10    # EWWhiteSpaceTerminal
11
12    def test_EWWhiteSpaceTerminal(self):
13        x = parser.EWWhiteSpaceTerminal(' \t', 'fws')
14        self.assertEqual(x, ' \t')
15        self.assertEqual(str(x), '')
16        self.assertEqual(x.value, '')
17        self.assertEqual(x.token_type, 'fws')
18
19
20class TestParserMixin:
21
22    def _assert_results(self, tl, rest, string, value, defects, remainder,
23                        comments=None):
24        self.assertEqual(str(tl), string)
25        self.assertEqual(tl.value, value)
26        self.assertDefectsEqual(tl.all_defects, defects)
27        self.assertEqual(rest, remainder)
28        if comments is not None:
29            self.assertEqual(tl.comments, comments)
30
31    def _test_get_x(self, method, source, string, value, defects,
32                          remainder, comments=None):
33        tl, rest = method(source)
34        self._assert_results(tl, rest, string, value, defects, remainder,
35                             comments=None)
36        return tl
37
38    def _test_parse_x(self, method, input, string, value, defects,
39                             comments=None):
40        tl = method(input)
41        self._assert_results(tl, '', string, value, defects, '', comments)
42        return tl
43
44
45class TestParser(TestParserMixin, TestEmailBase):
46
47    # _wsp_splitter
48
49    rfc_printable_ascii = bytes(range(33, 127)).decode('ascii')
50    rfc_atext_chars = (string.ascii_letters + string.digits +
51                        "!#$%&\'*+-/=?^_`{}|~")
52    rfc_dtext_chars = rfc_printable_ascii.translate(str.maketrans('','',r'\[]'))
53
54    def test__wsp_splitter_one_word(self):
55        self.assertEqual(parser._wsp_splitter('foo', 1), ['foo'])
56
57    def test__wsp_splitter_two_words(self):
58        self.assertEqual(parser._wsp_splitter('foo def', 1),
59                                               ['foo', ' ', 'def'])
60
61    def test__wsp_splitter_ws_runs(self):
62        self.assertEqual(parser._wsp_splitter('foo \t def jik', 1),
63                                              ['foo', ' \t ', 'def jik'])
64
65
66    # get_fws
67
68    def test_get_fws_only(self):
69        fws = self._test_get_x(parser.get_fws, ' \t  ', ' \t  ', ' ', [], '')
70        self.assertEqual(fws.token_type, 'fws')
71
72    def test_get_fws_space(self):
73        self._test_get_x(parser.get_fws, ' foo', ' ', ' ', [], 'foo')
74
75    def test_get_fws_ws_run(self):
76        self._test_get_x(parser.get_fws, ' \t foo ', ' \t ', ' ', [], 'foo ')
77
78    # get_encoded_word
79
80    def test_get_encoded_word_missing_start_raises(self):
81        with self.assertRaises(errors.HeaderParseError):
82            parser.get_encoded_word('abc')
83
84    def test_get_encoded_word_missing_end_raises(self):
85        with self.assertRaises(errors.HeaderParseError):
86            parser.get_encoded_word('=?abc')
87
88    def test_get_encoded_word_missing_middle_raises(self):
89        with self.assertRaises(errors.HeaderParseError):
90            parser.get_encoded_word('=?abc?=')
91
92    def test_get_encoded_word_invalid_cte(self):
93        with self.assertRaises(errors.HeaderParseError):
94            parser.get_encoded_word('=?utf-8?X?somevalue?=')
95
96    def test_get_encoded_word_valid_ew(self):
97        self._test_get_x(parser.get_encoded_word,
98                         '=?us-ascii?q?this_is_a_test?=  bird',
99                         'this is a test',
100                         'this is a test',
101                         [],
102                         '  bird')
103
104    def test_get_encoded_word_internal_spaces(self):
105        self._test_get_x(parser.get_encoded_word,
106                         '=?us-ascii?q?this is a test?=  bird',
107                         'this is a test',
108                         'this is a test',
109                         [errors.InvalidHeaderDefect],
110                         '  bird')
111
112    def test_get_encoded_word_gets_first(self):
113        self._test_get_x(parser.get_encoded_word,
114                         '=?us-ascii?q?first?=  =?utf-8?q?second?=',
115                         'first',
116                         'first',
117                         [],
118                         '  =?utf-8?q?second?=')
119
120    def test_get_encoded_word_gets_first_even_if_no_space(self):
121        self._test_get_x(parser.get_encoded_word,
122                         '=?us-ascii?q?first?==?utf-8?q?second?=',
123                         'first',
124                         'first',
125                         [errors.InvalidHeaderDefect],
126                         '=?utf-8?q?second?=')
127
128    def test_get_encoded_word_sets_extra_attributes(self):
129        ew = self._test_get_x(parser.get_encoded_word,
130                         '=?us-ascii*jive?q?first_second?=',
131                         'first second',
132                         'first second',
133                         [],
134                         '')
135        self.assertEqual(ew.charset, 'us-ascii')
136        self.assertEqual(ew.lang, 'jive')
137
138    def test_get_encoded_word_lang_default_is_blank(self):
139        ew = self._test_get_x(parser.get_encoded_word,
140                         '=?us-ascii?q?first_second?=',
141                         'first second',
142                         'first second',
143                         [],
144                         '')
145        self.assertEqual(ew.charset, 'us-ascii')
146        self.assertEqual(ew.lang, '')
147
148    def test_get_encoded_word_non_printable_defect(self):
149        self._test_get_x(parser.get_encoded_word,
150                         '=?us-ascii?q?first\x02second?=',
151                         'first\x02second',
152                         'first\x02second',
153                         [errors.NonPrintableDefect],
154                         '')
155
156    def test_get_encoded_word_leading_internal_space(self):
157        self._test_get_x(parser.get_encoded_word,
158                        '=?us-ascii?q?=20foo?=',
159                        ' foo',
160                        ' foo',
161                        [],
162                        '')
163
164    def test_get_encoded_word_quopri_utf_escape_follows_cte(self):
165        # Issue 18044
166        self._test_get_x(parser.get_encoded_word,
167                        '=?utf-8?q?=C3=89ric?=',
168                        'Éric',
169                        'Éric',
170                        [],
171                        '')
172
173    # get_unstructured
174
175    def _get_unst(self, value):
176        token = parser.get_unstructured(value)
177        return token, ''
178
179    def test_get_unstructured_null(self):
180        self._test_get_x(self._get_unst, '', '', '', [], '')
181
182    def test_get_unstructured_one_word(self):
183        self._test_get_x(self._get_unst, 'foo', 'foo', 'foo', [], '')
184
185    def test_get_unstructured_normal_phrase(self):
186        self._test_get_x(self._get_unst, 'foo bar bird',
187                                         'foo bar bird',
188                                         'foo bar bird',
189                                         [],
190                                         '')
191
192    def test_get_unstructured_normal_phrase_with_whitespace(self):
193        self._test_get_x(self._get_unst, 'foo \t bar      bird',
194                                         'foo \t bar      bird',
195                                         'foo bar bird',
196                                         [],
197                                         '')
198
199    def test_get_unstructured_leading_whitespace(self):
200        self._test_get_x(self._get_unst, '  foo bar',
201                                         '  foo bar',
202                                         ' foo bar',
203                                         [],
204                                         '')
205
206    def test_get_unstructured_trailing_whitespace(self):
207        self._test_get_x(self._get_unst, 'foo bar  ',
208                                         'foo bar  ',
209                                         'foo bar ',
210                                         [],
211                                         '')
212
213    def test_get_unstructured_leading_and_trailing_whitespace(self):
214        self._test_get_x(self._get_unst, '  foo bar  ',
215                                         '  foo bar  ',
216                                         ' foo bar ',
217                                         [],
218                                         '')
219
220    def test_get_unstructured_one_valid_ew_no_ws(self):
221        self._test_get_x(self._get_unst, '=?us-ascii?q?bar?=',
222                                         'bar',
223                                         'bar',
224                                         [],
225                                         '')
226
227    def test_get_unstructured_one_ew_trailing_ws(self):
228        self._test_get_x(self._get_unst, '=?us-ascii?q?bar?=  ',
229                                         'bar  ',
230                                         'bar ',
231                                         [],
232                                         '')
233
234    def test_get_unstructured_one_valid_ew_trailing_text(self):
235        self._test_get_x(self._get_unst, '=?us-ascii?q?bar?= bird',
236                                         'bar bird',
237                                         'bar bird',
238                                         [],
239                                         '')
240
241    def test_get_unstructured_phrase_with_ew_in_middle_of_text(self):
242        self._test_get_x(self._get_unst, 'foo =?us-ascii?q?bar?= bird',
243                                         'foo bar bird',
244                                         'foo bar bird',
245                                         [],
246                                         '')
247
248    def test_get_unstructured_phrase_with_two_ew(self):
249        self._test_get_x(self._get_unst,
250            'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?=',
251            'foo barbird',
252            'foo barbird',
253            [],
254            '')
255
256    def test_get_unstructured_phrase_with_two_ew_trailing_ws(self):
257        self._test_get_x(self._get_unst,
258            'foo =?us-ascii?q?bar?= =?us-ascii?q?bird?=   ',
259            'foo barbird   ',
260            'foo barbird ',
261            [],
262            '')
263
264    def test_get_unstructured_phrase_with_ew_with_leading_ws(self):
265        self._test_get_x(self._get_unst,
266            '  =?us-ascii?q?bar?=',
267            '  bar',
268            ' bar',
269            [],
270            '')
271
272    def test_get_unstructured_phrase_with_two_ew_extra_ws(self):
273        self._test_get_x(self._get_unst,
274            'foo =?us-ascii?q?bar?= \t  =?us-ascii?q?bird?=',
275            'foo barbird',
276            'foo barbird',
277            [],
278            '')
279
280    def test_get_unstructured_two_ew_extra_ws_trailing_text(self):
281        self._test_get_x(self._get_unst,
282            '=?us-ascii?q?test?=   =?us-ascii?q?foo?=  val',
283            'testfoo  val',
284            'testfoo val',
285            [],
286            '')
287
288    def test_get_unstructured_ew_with_internal_ws(self):
289        self._test_get_x(self._get_unst,
290            '=?iso-8859-1?q?hello=20world?=',
291            'hello world',
292            'hello world',
293            [],
294            '')
295
296    def test_get_unstructured_ew_with_internal_leading_ws(self):
297        self._test_get_x(self._get_unst,
298            '   =?us-ascii?q?=20test?=   =?us-ascii?q?=20foo?=  val',
299            '    test foo  val',
300            '  test foo val',
301            [],
302            '')
303
304    def test_get_unstructured_invalid_ew(self):
305        self._test_get_x(self._get_unst,
306            '=?test val',
307            '=?test val',
308            '=?test val',
309            [],
310            '')
311
312    def test_get_unstructured_undecodable_bytes(self):
313        self._test_get_x(self._get_unst,
314            b'test \xACfoo  val'.decode('ascii', 'surrogateescape'),
315            'test \uDCACfoo  val',
316            'test \uDCACfoo val',
317            [errors.UndecodableBytesDefect],
318            '')
319
320    def test_get_unstructured_undecodable_bytes_in_EW(self):
321        self._test_get_x(self._get_unst,
322            (b'=?us-ascii?q?=20test?=   =?us-ascii?q?=20\xACfoo?='
323                b'  val').decode('ascii', 'surrogateescape'),
324            ' test \uDCACfoo  val',
325            ' test \uDCACfoo val',
326            [errors.UndecodableBytesDefect]*2,
327            '')
328
329    def test_get_unstructured_missing_base64_padding(self):
330        self._test_get_x(self._get_unst,
331            '=?utf-8?b?dmk?=',
332            'vi',
333            'vi',
334            [errors.InvalidBase64PaddingDefect],
335            '')
336
337    def test_get_unstructured_invalid_base64_character(self):
338        self._test_get_x(self._get_unst,
339            '=?utf-8?b?dm\x01k===?=',
340            'vi',
341            'vi',
342            [errors.InvalidBase64CharactersDefect],
343            '')
344
345    def test_get_unstructured_invalid_base64_character_and_bad_padding(self):
346        self._test_get_x(self._get_unst,
347            '=?utf-8?b?dm\x01k?=',
348            'vi',
349            'vi',
350            [errors.InvalidBase64CharactersDefect,
351             errors.InvalidBase64PaddingDefect],
352            '')
353
354    def test_get_unstructured_invalid_base64_length(self):
355        # bpo-27397: Return the encoded string since there's no way to decode.
356        self._test_get_x(self._get_unst,
357            '=?utf-8?b?abcde?=',
358            'abcde',
359            'abcde',
360            [errors.InvalidBase64LengthDefect],
361            '')
362
363    def test_get_unstructured_no_whitespace_between_ews(self):
364        self._test_get_x(self._get_unst,
365            '=?utf-8?q?foo?==?utf-8?q?bar?=',
366            'foobar',
367            'foobar',
368            [errors.InvalidHeaderDefect,
369            errors.InvalidHeaderDefect],
370            '')
371
372    def test_get_unstructured_ew_without_leading_whitespace(self):
373        self._test_get_x(
374            self._get_unst,
375            'nowhitespace=?utf-8?q?somevalue?=',
376            'nowhitespacesomevalue',
377            'nowhitespacesomevalue',
378            [errors.InvalidHeaderDefect],
379            '')
380
381    def test_get_unstructured_ew_without_trailing_whitespace(self):
382        self._test_get_x(
383            self._get_unst,
384            '=?utf-8?q?somevalue?=nowhitespace',
385            'somevaluenowhitespace',
386            'somevaluenowhitespace',
387            [errors.InvalidHeaderDefect],
388            '')
389
390    def test_get_unstructured_without_trailing_whitespace_hang_case(self):
391        self._test_get_x(self._get_unst,
392            '=?utf-8?q?somevalue?=aa',
393            'somevalueaa',
394            'somevalueaa',
395            [errors.InvalidHeaderDefect],
396            '')
397
398    def test_get_unstructured_invalid_ew2(self):
399        self._test_get_x(self._get_unst,
400            '=?utf-8?q?=somevalue?=',
401            '=?utf-8?q?=somevalue?=',
402            '=?utf-8?q?=somevalue?=',
403            [],
404            '')
405
406    def test_get_unstructured_invalid_ew_cte(self):
407        self._test_get_x(self._get_unst,
408            '=?utf-8?X?=somevalue?=',
409            '=?utf-8?X?=somevalue?=',
410            '=?utf-8?X?=somevalue?=',
411            [],
412            '')
413
414    # get_qp_ctext
415
416    def test_get_qp_ctext_only(self):
417        ptext = self._test_get_x(parser.get_qp_ctext,
418                                'foobar', 'foobar', ' ', [], '')
419        self.assertEqual(ptext.token_type, 'ptext')
420
421    def test_get_qp_ctext_all_printables(self):
422        with_qp = self.rfc_printable_ascii.replace('\\', '\\\\')
423        with_qp = with_qp.  replace('(', r'\(')
424        with_qp = with_qp.replace(')', r'\)')
425        ptext = self._test_get_x(parser.get_qp_ctext,
426                                 with_qp, self.rfc_printable_ascii, ' ', [], '')
427
428    def test_get_qp_ctext_two_words_gets_first(self):
429        self._test_get_x(parser.get_qp_ctext,
430                        'foo de', 'foo', ' ', [], ' de')
431
432    def test_get_qp_ctext_following_wsp_preserved(self):
433        self._test_get_x(parser.get_qp_ctext,
434                        'foo \t\tde', 'foo', ' ', [], ' \t\tde')
435
436    def test_get_qp_ctext_up_to_close_paren_only(self):
437        self._test_get_x(parser.get_qp_ctext,
438                        'foo)', 'foo', ' ', [], ')')
439
440    def test_get_qp_ctext_wsp_before_close_paren_preserved(self):
441        self._test_get_x(parser.get_qp_ctext,
442                        'foo  )', 'foo', ' ', [], '  )')
443
444    def test_get_qp_ctext_close_paren_mid_word(self):
445        self._test_get_x(parser.get_qp_ctext,
446                        'foo)bar', 'foo', ' ', [], ')bar')
447
448    def test_get_qp_ctext_up_to_open_paren_only(self):
449        self._test_get_x(parser.get_qp_ctext,
450                        'foo(', 'foo', ' ', [], '(')
451
452    def test_get_qp_ctext_wsp_before_open_paren_preserved(self):
453        self._test_get_x(parser.get_qp_ctext,
454                        'foo  (', 'foo', ' ', [], '  (')
455
456    def test_get_qp_ctext_open_paren_mid_word(self):
457        self._test_get_x(parser.get_qp_ctext,
458                        'foo(bar', 'foo', ' ', [], '(bar')
459
460    def test_get_qp_ctext_non_printables(self):
461        ptext = self._test_get_x(parser.get_qp_ctext,
462                                'foo\x00bar)', 'foo\x00bar', ' ',
463                                [errors.NonPrintableDefect], ')')
464        self.assertEqual(ptext.defects[0].non_printables[0], '\x00')
465
466    # get_qcontent
467
468    def test_get_qcontent_only(self):
469        ptext = self._test_get_x(parser.get_qcontent,
470                                'foobar', 'foobar', 'foobar', [], '')
471        self.assertEqual(ptext.token_type, 'ptext')
472
473    def test_get_qcontent_all_printables(self):
474        with_qp = self.rfc_printable_ascii.replace('\\', '\\\\')
475        with_qp = with_qp.  replace('"', r'\"')
476        ptext = self._test_get_x(parser.get_qcontent, with_qp,
477                                 self.rfc_printable_ascii,
478                                 self.rfc_printable_ascii, [], '')
479
480    def test_get_qcontent_two_words_gets_first(self):
481        self._test_get_x(parser.get_qcontent,
482                        'foo de', 'foo', 'foo', [], ' de')
483
484    def test_get_qcontent_following_wsp_preserved(self):
485        self._test_get_x(parser.get_qcontent,
486                        'foo \t\tde', 'foo', 'foo', [], ' \t\tde')
487
488    def test_get_qcontent_up_to_dquote_only(self):
489        self._test_get_x(parser.get_qcontent,
490                        'foo"', 'foo', 'foo', [], '"')
491
492    def test_get_qcontent_wsp_before_close_paren_preserved(self):
493        self._test_get_x(parser.get_qcontent,
494                        'foo  "', 'foo', 'foo', [], '  "')
495
496    def test_get_qcontent_close_paren_mid_word(self):
497        self._test_get_x(parser.get_qcontent,
498                        'foo"bar', 'foo', 'foo', [], '"bar')
499
500    def test_get_qcontent_non_printables(self):
501        ptext = self._test_get_x(parser.get_qcontent,
502                                'foo\x00fg"', 'foo\x00fg', 'foo\x00fg',
503                                [errors.NonPrintableDefect], '"')
504        self.assertEqual(ptext.defects[0].non_printables[0], '\x00')
505
506    # get_atext
507
508    def test_get_atext_only(self):
509        atext = self._test_get_x(parser.get_atext,
510                                'foobar', 'foobar', 'foobar', [], '')
511        self.assertEqual(atext.token_type, 'atext')
512
513    def test_get_atext_all_atext(self):
514        atext = self._test_get_x(parser.get_atext, self.rfc_atext_chars,
515                                 self.rfc_atext_chars,
516                                 self.rfc_atext_chars, [], '')
517
518    def test_get_atext_two_words_gets_first(self):
519        self._test_get_x(parser.get_atext,
520                        'foo bar', 'foo', 'foo', [], ' bar')
521
522    def test_get_atext_following_wsp_preserved(self):
523        self._test_get_x(parser.get_atext,
524                        'foo \t\tbar', 'foo', 'foo', [], ' \t\tbar')
525
526    def test_get_atext_up_to_special(self):
527        self._test_get_x(parser.get_atext,
528                        'foo@bar', 'foo', 'foo', [], '@bar')
529
530    def test_get_atext_non_printables(self):
531        atext = self._test_get_x(parser.get_atext,
532                                'foo\x00bar(', 'foo\x00bar', 'foo\x00bar',
533                                [errors.NonPrintableDefect], '(')
534        self.assertEqual(atext.defects[0].non_printables[0], '\x00')
535
536    # get_bare_quoted_string
537
538    def test_get_bare_quoted_string_only(self):
539        bqs = self._test_get_x(parser.get_bare_quoted_string,
540                               '"foo"', '"foo"', 'foo', [], '')
541        self.assertEqual(bqs.token_type, 'bare-quoted-string')
542
543    def test_get_bare_quoted_string_must_start_with_dquote(self):
544        with self.assertRaises(errors.HeaderParseError):
545            parser.get_bare_quoted_string('foo"')
546        with self.assertRaises(errors.HeaderParseError):
547            parser.get_bare_quoted_string('  "foo"')
548
549    def test_get_bare_quoted_string_only_quotes(self):
550        self._test_get_x(parser.get_bare_quoted_string,
551                         '""', '""', '', [], '')
552
553    def test_get_bare_quoted_string_missing_endquotes(self):
554        self._test_get_x(parser.get_bare_quoted_string,
555                         '"', '""', '', [errors.InvalidHeaderDefect], '')
556
557    def test_get_bare_quoted_string_following_wsp_preserved(self):
558        self._test_get_x(parser.get_bare_quoted_string,
559             '"foo"\t bar', '"foo"', 'foo', [], '\t bar')
560
561    def test_get_bare_quoted_string_multiple_words(self):
562        self._test_get_x(parser.get_bare_quoted_string,
563             '"foo bar moo"', '"foo bar moo"', 'foo bar moo', [], '')
564
565    def test_get_bare_quoted_string_multiple_words_wsp_preserved(self):
566        self._test_get_x(parser.get_bare_quoted_string,
567             '" foo  moo\t"', '" foo  moo\t"', ' foo  moo\t', [], '')
568
569    def test_get_bare_quoted_string_end_dquote_mid_word(self):
570        self._test_get_x(parser.get_bare_quoted_string,
571             '"foo"bar', '"foo"', 'foo', [], 'bar')
572
573    def test_get_bare_quoted_string_quoted_dquote(self):
574        self._test_get_x(parser.get_bare_quoted_string,
575             r'"foo\"in"a', r'"foo\"in"', 'foo"in', [], 'a')
576
577    def test_get_bare_quoted_string_non_printables(self):
578        self._test_get_x(parser.get_bare_quoted_string,
579             '"a\x01a"', '"a\x01a"', 'a\x01a',
580             [errors.NonPrintableDefect], '')
581
582    def test_get_bare_quoted_string_no_end_dquote(self):
583        self._test_get_x(parser.get_bare_quoted_string,
584             '"foo', '"foo"', 'foo',
585             [errors.InvalidHeaderDefect], '')
586        self._test_get_x(parser.get_bare_quoted_string,
587             '"foo ', '"foo "', 'foo ',
588             [errors.InvalidHeaderDefect], '')
589
590    def test_get_bare_quoted_string_empty_quotes(self):
591        self._test_get_x(parser.get_bare_quoted_string,
592            '""', '""', '', [], '')
593
594    # Issue 16983: apply postel's law to some bad encoding.
595    def test_encoded_word_inside_quotes(self):
596        self._test_get_x(parser.get_bare_quoted_string,
597            '"=?utf-8?Q?not_really_valid?="',
598            '"not really valid"',
599            'not really valid',
600            [errors.InvalidHeaderDefect,
601             errors.InvalidHeaderDefect],
602            '')
603
604    # get_comment
605
606    def test_get_comment_only(self):
607        comment = self._test_get_x(parser.get_comment,
608            '(comment)', '(comment)', ' ', [], '', ['comment'])
609        self.assertEqual(comment.token_type, 'comment')
610
611    def test_get_comment_must_start_with_paren(self):
612        with self.assertRaises(errors.HeaderParseError):
613            parser.get_comment('foo"')
614        with self.assertRaises(errors.HeaderParseError):
615            parser.get_comment('  (foo"')
616
617    def test_get_comment_following_wsp_preserved(self):
618        self._test_get_x(parser.get_comment,
619            '(comment)  \t', '(comment)', ' ', [], '  \t', ['comment'])
620
621    def test_get_comment_multiple_words(self):
622        self._test_get_x(parser.get_comment,
623            '(foo bar)  \t', '(foo bar)', ' ', [], '  \t', ['foo bar'])
624
625    def test_get_comment_multiple_words_wsp_preserved(self):
626        self._test_get_x(parser.get_comment,
627            '( foo  bar\t )  \t', '( foo  bar\t )', ' ', [], '  \t',
628                [' foo  bar\t '])
629
630    def test_get_comment_end_paren_mid_word(self):
631        self._test_get_x(parser.get_comment,
632            '(foo)bar', '(foo)', ' ', [], 'bar', ['foo'])
633
634    def test_get_comment_quoted_parens(self):
635        self._test_get_x(parser.get_comment,
636            r'(foo\) \(\)bar)', r'(foo\) \(\)bar)', ' ', [], '', ['foo) ()bar'])
637
638    def test_get_comment_non_printable(self):
639        self._test_get_x(parser.get_comment,
640            '(foo\x7Fbar)', '(foo\x7Fbar)', ' ',
641            [errors.NonPrintableDefect], '', ['foo\x7Fbar'])
642
643    def test_get_comment_no_end_paren(self):
644        self._test_get_x(parser.get_comment,
645            '(foo bar', '(foo bar)', ' ',
646            [errors.InvalidHeaderDefect], '', ['foo bar'])
647        self._test_get_x(parser.get_comment,
648            '(foo bar  ', '(foo bar  )', ' ',
649            [errors.InvalidHeaderDefect], '', ['foo bar  '])
650
651    def test_get_comment_nested_comment(self):
652        comment = self._test_get_x(parser.get_comment,
653            '(foo(bar))', '(foo(bar))', ' ', [], '', ['foo(bar)'])
654        self.assertEqual(comment[1].content, 'bar')
655
656    def test_get_comment_nested_comment_wsp(self):
657        comment = self._test_get_x(parser.get_comment,
658            '(foo ( bar ) )', '(foo ( bar ) )', ' ', [], '', ['foo ( bar ) '])
659        self.assertEqual(comment[2].content, ' bar ')
660
661    def test_get_comment_empty_comment(self):
662        self._test_get_x(parser.get_comment,
663            '()', '()', ' ', [], '', [''])
664
665    def test_get_comment_multiple_nesting(self):
666        comment = self._test_get_x(parser.get_comment,
667            '(((((foo)))))', '(((((foo)))))', ' ', [], '', ['((((foo))))'])
668        for i in range(4, 0, -1):
669            self.assertEqual(comment[0].content, '('*(i-1)+'foo'+')'*(i-1))
670            comment = comment[0]
671        self.assertEqual(comment.content, 'foo')
672
673    def test_get_comment_missing_end_of_nesting(self):
674        self._test_get_x(parser.get_comment,
675            '(((((foo)))', '(((((foo)))))', ' ',
676            [errors.InvalidHeaderDefect]*2, '', ['((((foo))))'])
677
678    def test_get_comment_qs_in_nested_comment(self):
679        comment = self._test_get_x(parser.get_comment,
680            r'(foo (b\)))', r'(foo (b\)))', ' ', [], '', [r'foo (b\))'])
681        self.assertEqual(comment[2].content, 'b)')
682
683    # get_cfws
684
685    def test_get_cfws_only_ws(self):
686        cfws = self._test_get_x(parser.get_cfws,
687            '  \t \t', '  \t \t', ' ', [], '', [])
688        self.assertEqual(cfws.token_type, 'cfws')
689
690    def test_get_cfws_only_comment(self):
691        cfws = self._test_get_x(parser.get_cfws,
692            '(foo)', '(foo)', ' ', [], '', ['foo'])
693        self.assertEqual(cfws[0].content, 'foo')
694
695    def test_get_cfws_only_mixed(self):
696        cfws = self._test_get_x(parser.get_cfws,
697            ' (foo )  ( bar) ', ' (foo )  ( bar) ', ' ', [], '',
698                ['foo ', ' bar'])
699        self.assertEqual(cfws[1].content, 'foo ')
700        self.assertEqual(cfws[3].content, ' bar')
701
702    def test_get_cfws_ends_at_non_leader(self):
703        cfws = self._test_get_x(parser.get_cfws,
704            '(foo) bar', '(foo) ', ' ', [], 'bar', ['foo'])
705        self.assertEqual(cfws[0].content, 'foo')
706
707    def test_get_cfws_ends_at_non_printable(self):
708        cfws = self._test_get_x(parser.get_cfws,
709            '(foo) \x07', '(foo) ', ' ', [], '\x07', ['foo'])
710        self.assertEqual(cfws[0].content, 'foo')
711
712    def test_get_cfws_non_printable_in_comment(self):
713        cfws = self._test_get_x(parser.get_cfws,
714            '(foo \x07) "test"', '(foo \x07) ', ' ',
715            [errors.NonPrintableDefect], '"test"', ['foo \x07'])
716        self.assertEqual(cfws[0].content, 'foo \x07')
717
718    def test_get_cfws_header_ends_in_comment(self):
719        cfws = self._test_get_x(parser.get_cfws,
720            '  (foo ', '  (foo )', ' ',
721            [errors.InvalidHeaderDefect], '', ['foo '])
722        self.assertEqual(cfws[1].content, 'foo ')
723
724    def test_get_cfws_multiple_nested_comments(self):
725        cfws = self._test_get_x(parser.get_cfws,
726            '(foo (bar)) ((a)(a))', '(foo (bar)) ((a)(a))', ' ', [],
727                '', ['foo (bar)', '(a)(a)'])
728        self.assertEqual(cfws[0].comments, ['foo (bar)'])
729        self.assertEqual(cfws[2].comments, ['(a)(a)'])
730
731    # get_quoted_string
732
733    def test_get_quoted_string_only(self):
734        qs = self._test_get_x(parser.get_quoted_string,
735            '"bob"', '"bob"', 'bob', [], '')
736        self.assertEqual(qs.token_type, 'quoted-string')
737        self.assertEqual(qs.quoted_value, '"bob"')
738        self.assertEqual(qs.content, 'bob')
739
740    def test_get_quoted_string_with_wsp(self):
741        qs = self._test_get_x(parser.get_quoted_string,
742            '\t "bob"  ', '\t "bob"  ', ' bob ', [], '')
743        self.assertEqual(qs.quoted_value, ' "bob" ')
744        self.assertEqual(qs.content, 'bob')
745
746    def test_get_quoted_string_with_comments_and_wsp(self):
747        qs = self._test_get_x(parser.get_quoted_string,
748            ' (foo) "bob"(bar)', ' (foo) "bob"(bar)', ' bob ', [], '')
749        self.assertEqual(qs[0][1].content, 'foo')
750        self.assertEqual(qs[2][0].content, 'bar')
751        self.assertEqual(qs.content, 'bob')
752        self.assertEqual(qs.quoted_value, ' "bob" ')
753
754    def test_get_quoted_string_with_multiple_comments(self):
755        qs = self._test_get_x(parser.get_quoted_string,
756            ' (foo) (bar) "bob"(bird)', ' (foo) (bar) "bob"(bird)', ' bob ',
757                [], '')
758        self.assertEqual(qs[0].comments, ['foo', 'bar'])
759        self.assertEqual(qs[2].comments, ['bird'])
760        self.assertEqual(qs.content, 'bob')
761        self.assertEqual(qs.quoted_value, ' "bob" ')
762
763    def test_get_quoted_string_non_printable_in_comment(self):
764        qs = self._test_get_x(parser.get_quoted_string,
765            ' (\x0A) "bob"', ' (\x0A) "bob"', ' bob',
766                [errors.NonPrintableDefect], '')
767        self.assertEqual(qs[0].comments, ['\x0A'])
768        self.assertEqual(qs.content, 'bob')
769        self.assertEqual(qs.quoted_value, ' "bob"')
770
771    def test_get_quoted_string_non_printable_in_qcontent(self):
772        qs = self._test_get_x(parser.get_quoted_string,
773            ' (a) "a\x0B"', ' (a) "a\x0B"', ' a\x0B',
774                [errors.NonPrintableDefect], '')
775        self.assertEqual(qs[0].comments, ['a'])
776        self.assertEqual(qs.content, 'a\x0B')
777        self.assertEqual(qs.quoted_value, ' "a\x0B"')
778
779    def test_get_quoted_string_internal_ws(self):
780        qs = self._test_get_x(parser.get_quoted_string,
781            ' (a) "foo  bar "', ' (a) "foo  bar "', ' foo  bar ',
782                [], '')
783        self.assertEqual(qs[0].comments, ['a'])
784        self.assertEqual(qs.content, 'foo  bar ')
785        self.assertEqual(qs.quoted_value, ' "foo  bar "')
786
787    def test_get_quoted_string_header_ends_in_comment(self):
788        qs = self._test_get_x(parser.get_quoted_string,
789            ' (a) "bob" (a', ' (a) "bob" (a)', ' bob ',
790                [errors.InvalidHeaderDefect], '')
791        self.assertEqual(qs[0].comments, ['a'])
792        self.assertEqual(qs[2].comments, ['a'])
793        self.assertEqual(qs.content, 'bob')
794        self.assertEqual(qs.quoted_value, ' "bob" ')
795
796    def test_get_quoted_string_header_ends_in_qcontent(self):
797        qs = self._test_get_x(parser.get_quoted_string,
798            ' (a) "bob', ' (a) "bob"', ' bob',
799                [errors.InvalidHeaderDefect], '')
800        self.assertEqual(qs[0].comments, ['a'])
801        self.assertEqual(qs.content, 'bob')
802        self.assertEqual(qs.quoted_value, ' "bob"')
803
804    def test_get_quoted_string_no_quoted_string(self):
805        with self.assertRaises(errors.HeaderParseError):
806            parser.get_quoted_string(' (ab) xyz')
807
808    def test_get_quoted_string_qs_ends_at_noncfws(self):
809        qs = self._test_get_x(parser.get_quoted_string,
810            '\t "bob" fee', '\t "bob" ', ' bob ', [], 'fee')
811        self.assertEqual(qs.content, 'bob')
812        self.assertEqual(qs.quoted_value, ' "bob" ')
813
814    # get_atom
815
816    def test_get_atom_only(self):
817        atom = self._test_get_x(parser.get_atom,
818            'bob', 'bob', 'bob', [], '')
819        self.assertEqual(atom.token_type, 'atom')
820
821    def test_get_atom_with_wsp(self):
822        self._test_get_x(parser.get_atom,
823            '\t bob  ', '\t bob  ', ' bob ', [], '')
824
825    def test_get_atom_with_comments_and_wsp(self):
826        atom = self._test_get_x(parser.get_atom,
827            ' (foo) bob(bar)', ' (foo) bob(bar)', ' bob ', [], '')
828        self.assertEqual(atom[0][1].content, 'foo')
829        self.assertEqual(atom[2][0].content, 'bar')
830
831    def test_get_atom_with_multiple_comments(self):
832        atom = self._test_get_x(parser.get_atom,
833            ' (foo) (bar) bob(bird)', ' (foo) (bar) bob(bird)', ' bob ',
834                [], '')
835        self.assertEqual(atom[0].comments, ['foo', 'bar'])
836        self.assertEqual(atom[2].comments, ['bird'])
837
838    def test_get_atom_non_printable_in_comment(self):
839        atom = self._test_get_x(parser.get_atom,
840            ' (\x0A) bob', ' (\x0A) bob', ' bob',
841                [errors.NonPrintableDefect], '')
842        self.assertEqual(atom[0].comments, ['\x0A'])
843
844    def test_get_atom_non_printable_in_atext(self):
845        atom = self._test_get_x(parser.get_atom,
846            ' (a) a\x0B', ' (a) a\x0B', ' a\x0B',
847                [errors.NonPrintableDefect], '')
848        self.assertEqual(atom[0].comments, ['a'])
849
850    def test_get_atom_header_ends_in_comment(self):
851        atom = self._test_get_x(parser.get_atom,
852            ' (a) bob (a', ' (a) bob (a)', ' bob ',
853                [errors.InvalidHeaderDefect], '')
854        self.assertEqual(atom[0].comments, ['a'])
855        self.assertEqual(atom[2].comments, ['a'])
856
857    def test_get_atom_no_atom(self):
858        with self.assertRaises(errors.HeaderParseError):
859            parser.get_atom(' (ab) ')
860
861    def test_get_atom_no_atom_before_special(self):
862        with self.assertRaises(errors.HeaderParseError):
863            parser.get_atom(' (ab) @')
864
865    def test_get_atom_atom_ends_at_special(self):
866        atom = self._test_get_x(parser.get_atom,
867            ' (foo) bob(bar)  @bang', ' (foo) bob(bar)  ', ' bob ', [], '@bang')
868        self.assertEqual(atom[0].comments, ['foo'])
869        self.assertEqual(atom[2].comments, ['bar'])
870
871    def test_get_atom_atom_ends_at_noncfws(self):
872        self._test_get_x(parser.get_atom,
873            'bob  fred', 'bob  ', 'bob ', [], 'fred')
874
875    def test_get_atom_rfc2047_atom(self):
876        self._test_get_x(parser.get_atom,
877            '=?utf-8?q?=20bob?=', ' bob', ' bob', [], '')
878
879    # get_dot_atom_text
880
881    def test_get_dot_atom_text(self):
882        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
883            'foo.bar.bang', 'foo.bar.bang', 'foo.bar.bang', [], '')
884        self.assertEqual(dot_atom_text.token_type, 'dot-atom-text')
885        self.assertEqual(len(dot_atom_text), 5)
886
887    def test_get_dot_atom_text_lone_atom_is_valid(self):
888        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
889            'foo', 'foo', 'foo', [], '')
890
891    def test_get_dot_atom_text_raises_on_leading_dot(self):
892        with self.assertRaises(errors.HeaderParseError):
893            parser.get_dot_atom_text('.foo.bar')
894
895    def test_get_dot_atom_text_raises_on_trailing_dot(self):
896        with self.assertRaises(errors.HeaderParseError):
897            parser.get_dot_atom_text('foo.bar.')
898
899    def test_get_dot_atom_text_raises_on_leading_non_atext(self):
900        with self.assertRaises(errors.HeaderParseError):
901            parser.get_dot_atom_text(' foo.bar')
902        with self.assertRaises(errors.HeaderParseError):
903            parser.get_dot_atom_text('@foo.bar')
904        with self.assertRaises(errors.HeaderParseError):
905            parser.get_dot_atom_text('"foo.bar"')
906
907    def test_get_dot_atom_text_trailing_text_preserved(self):
908        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
909            'foo@bar', 'foo', 'foo', [], '@bar')
910
911    def test_get_dot_atom_text_trailing_ws_preserved(self):
912        dot_atom_text = self._test_get_x(parser.get_dot_atom_text,
913            'foo .bar', 'foo', 'foo', [], ' .bar')
914
915    # get_dot_atom
916
917    def test_get_dot_atom_only(self):
918        dot_atom = self._test_get_x(parser.get_dot_atom,
919            'foo.bar.bing', 'foo.bar.bing', 'foo.bar.bing', [], '')
920        self.assertEqual(dot_atom.token_type, 'dot-atom')
921        self.assertEqual(len(dot_atom), 1)
922
923    def test_get_dot_atom_with_wsp(self):
924        self._test_get_x(parser.get_dot_atom,
925            '\t  foo.bar.bing  ', '\t  foo.bar.bing  ', ' foo.bar.bing ', [], '')
926
927    def test_get_dot_atom_with_comments_and_wsp(self):
928        self._test_get_x(parser.get_dot_atom,
929            ' (sing)  foo.bar.bing (here) ', ' (sing)  foo.bar.bing (here) ',
930                ' foo.bar.bing ', [], '')
931
932    def test_get_dot_atom_space_ends_dot_atom(self):
933        self._test_get_x(parser.get_dot_atom,
934            ' (sing)  foo.bar .bing (here) ', ' (sing)  foo.bar ',
935                ' foo.bar ', [], '.bing (here) ')
936
937    def test_get_dot_atom_no_atom_raises(self):
938        with self.assertRaises(errors.HeaderParseError):
939            parser.get_dot_atom(' (foo) ')
940
941    def test_get_dot_atom_leading_dot_raises(self):
942        with self.assertRaises(errors.HeaderParseError):
943            parser.get_dot_atom(' (foo) .bar')
944
945    def test_get_dot_atom_two_dots_raises(self):
946        with self.assertRaises(errors.HeaderParseError):
947            parser.get_dot_atom('bar..bang')
948
949    def test_get_dot_atom_trailing_dot_raises(self):
950        with self.assertRaises(errors.HeaderParseError):
951            parser.get_dot_atom(' (foo) bar.bang. foo')
952
953    def test_get_dot_atom_rfc2047_atom(self):
954        self._test_get_x(parser.get_dot_atom,
955            '=?utf-8?q?=20bob?=', ' bob', ' bob', [], '')
956
957    # get_word (if this were black box we'd repeat all the qs/atom tests)
958
959    def test_get_word_atom_yields_atom(self):
960        word = self._test_get_x(parser.get_word,
961            ' (foo) bar (bang) :ah', ' (foo) bar (bang) ', ' bar ', [], ':ah')
962        self.assertEqual(word.token_type, 'atom')
963        self.assertEqual(word[0].token_type, 'cfws')
964
965    def test_get_word_all_CFWS(self):
966        # bpo-29412: Test that we don't raise IndexError when parsing CFWS only
967        # token.
968        with self.assertRaises(errors.HeaderParseError):
969            parser.get_word('(Recipients list suppressed')
970
971    def test_get_word_qs_yields_qs(self):
972        word = self._test_get_x(parser.get_word,
973            '"bar " (bang) ah', '"bar " (bang) ', 'bar  ', [], 'ah')
974        self.assertEqual(word.token_type, 'quoted-string')
975        self.assertEqual(word[0].token_type, 'bare-quoted-string')
976        self.assertEqual(word[0].value, 'bar ')
977        self.assertEqual(word.content, 'bar ')
978
979    def test_get_word_ends_at_dot(self):
980        self._test_get_x(parser.get_word,
981            'foo.', 'foo', 'foo', [], '.')
982
983    # get_phrase
984
985    def test_get_phrase_simple(self):
986        phrase = self._test_get_x(parser.get_phrase,
987            '"Fred A. Johnson" is his name, oh.',
988            '"Fred A. Johnson" is his name',
989            'Fred A. Johnson is his name',
990            [],
991            ', oh.')
992        self.assertEqual(phrase.token_type, 'phrase')
993
994    def test_get_phrase_complex(self):
995        phrase = self._test_get_x(parser.get_phrase,
996            ' (A) bird (in (my|your)) "hand  " is messy\t<>\t',
997            ' (A) bird (in (my|your)) "hand  " is messy\t',
998            ' bird hand   is messy ',
999            [],
1000            '<>\t')
1001        self.assertEqual(phrase[0][0].comments, ['A'])
1002        self.assertEqual(phrase[0][2].comments, ['in (my|your)'])
1003
1004    def test_get_phrase_obsolete(self):
1005        phrase = self._test_get_x(parser.get_phrase,
1006            'Fred A.(weird).O Johnson',
1007            'Fred A.(weird).O Johnson',
1008            'Fred A. .O Johnson',
1009            [errors.ObsoleteHeaderDefect]*3,
1010            '')
1011        self.assertEqual(len(phrase), 7)
1012        self.assertEqual(phrase[3].comments, ['weird'])
1013
1014    def test_get_phrase_pharse_must_start_with_word(self):
1015        phrase = self._test_get_x(parser.get_phrase,
1016            '(even weirder).name',
1017            '(even weirder).name',
1018            ' .name',
1019            [errors.InvalidHeaderDefect] + [errors.ObsoleteHeaderDefect]*2,
1020            '')
1021        self.assertEqual(len(phrase), 3)
1022        self.assertEqual(phrase[0].comments, ['even weirder'])
1023
1024    def test_get_phrase_ending_with_obsolete(self):
1025        phrase = self._test_get_x(parser.get_phrase,
1026            'simple phrase.(with trailing comment):boo',
1027            'simple phrase.(with trailing comment)',
1028            'simple phrase. ',
1029            [errors.ObsoleteHeaderDefect]*2,
1030            ':boo')
1031        self.assertEqual(len(phrase), 4)
1032        self.assertEqual(phrase[3].comments, ['with trailing comment'])
1033
1034    def get_phrase_cfws_only_raises(self):
1035        with self.assertRaises(errors.HeaderParseError):
1036            parser.get_phrase(' (foo) ')
1037
1038    # get_local_part
1039
1040    def test_get_local_part_simple(self):
1041        local_part = self._test_get_x(parser.get_local_part,
1042            'dinsdale@python.org', 'dinsdale', 'dinsdale', [], '@python.org')
1043        self.assertEqual(local_part.token_type, 'local-part')
1044        self.assertEqual(local_part.local_part, 'dinsdale')
1045
1046    def test_get_local_part_with_dot(self):
1047        local_part = self._test_get_x(parser.get_local_part,
1048            'Fred.A.Johnson@python.org',
1049            'Fred.A.Johnson',
1050            'Fred.A.Johnson',
1051            [],
1052            '@python.org')
1053        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
1054
1055    def test_get_local_part_with_whitespace(self):
1056        local_part = self._test_get_x(parser.get_local_part,
1057            ' Fred.A.Johnson  @python.org',
1058            ' Fred.A.Johnson  ',
1059            ' Fred.A.Johnson ',
1060            [],
1061            '@python.org')
1062        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
1063
1064    def test_get_local_part_with_cfws(self):
1065        local_part = self._test_get_x(parser.get_local_part,
1066            ' (foo) Fred.A.Johnson (bar (bird))  @python.org',
1067            ' (foo) Fred.A.Johnson (bar (bird))  ',
1068            ' Fred.A.Johnson ',
1069            [],
1070            '@python.org')
1071        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
1072        self.assertEqual(local_part[0][0].comments, ['foo'])
1073        self.assertEqual(local_part[0][2].comments, ['bar (bird)'])
1074
1075    def test_get_local_part_simple_quoted(self):
1076        local_part = self._test_get_x(parser.get_local_part,
1077            '"dinsdale"@python.org', '"dinsdale"', '"dinsdale"', [], '@python.org')
1078        self.assertEqual(local_part.token_type, 'local-part')
1079        self.assertEqual(local_part.local_part, 'dinsdale')
1080
1081    def test_get_local_part_with_quoted_dot(self):
1082        local_part = self._test_get_x(parser.get_local_part,
1083            '"Fred.A.Johnson"@python.org',
1084            '"Fred.A.Johnson"',
1085            '"Fred.A.Johnson"',
1086            [],
1087            '@python.org')
1088        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
1089
1090    def test_get_local_part_quoted_with_whitespace(self):
1091        local_part = self._test_get_x(parser.get_local_part,
1092            ' "Fred A. Johnson"  @python.org',
1093            ' "Fred A. Johnson"  ',
1094            ' "Fred A. Johnson" ',
1095            [],
1096            '@python.org')
1097        self.assertEqual(local_part.local_part, 'Fred A. Johnson')
1098
1099    def test_get_local_part_quoted_with_cfws(self):
1100        local_part = self._test_get_x(parser.get_local_part,
1101            ' (foo) " Fred A. Johnson " (bar (bird))  @python.org',
1102            ' (foo) " Fred A. Johnson " (bar (bird))  ',
1103            ' " Fred A. Johnson " ',
1104            [],
1105            '@python.org')
1106        self.assertEqual(local_part.local_part, ' Fred A. Johnson ')
1107        self.assertEqual(local_part[0][0].comments, ['foo'])
1108        self.assertEqual(local_part[0][2].comments, ['bar (bird)'])
1109
1110
1111    def test_get_local_part_simple_obsolete(self):
1112        local_part = self._test_get_x(parser.get_local_part,
1113            'Fred. A.Johnson@python.org',
1114            'Fred. A.Johnson',
1115            'Fred. A.Johnson',
1116            [errors.ObsoleteHeaderDefect],
1117            '@python.org')
1118        self.assertEqual(local_part.local_part, 'Fred.A.Johnson')
1119
1120    def test_get_local_part_complex_obsolete_1(self):
1121        local_part = self._test_get_x(parser.get_local_part,
1122            ' (foo )Fred (bar).(bird) A.(sheep)Johnson."and  dogs "@python.org',
1123            ' (foo )Fred (bar).(bird) A.(sheep)Johnson."and  dogs "',
1124            ' Fred . A. Johnson.and  dogs ',
1125            [errors.ObsoleteHeaderDefect],
1126            '@python.org')
1127        self.assertEqual(local_part.local_part, 'Fred.A.Johnson.and  dogs ')
1128
1129    def test_get_local_part_complex_obsolete_invalid(self):
1130        local_part = self._test_get_x(parser.get_local_part,
1131            ' (foo )Fred (bar).(bird) A.(sheep)Johnson "and  dogs"@python.org',
1132            ' (foo )Fred (bar).(bird) A.(sheep)Johnson "and  dogs"',
1133            ' Fred . A. Johnson and  dogs',
1134            [errors.InvalidHeaderDefect]*2,
1135            '@python.org')
1136        self.assertEqual(local_part.local_part, 'Fred.A.Johnson and  dogs')
1137
1138    def test_get_local_part_no_part_raises(self):
1139        with self.assertRaises(errors.HeaderParseError):
1140            parser.get_local_part(' (foo) ')
1141
1142    def test_get_local_part_special_instead_raises(self):
1143        with self.assertRaises(errors.HeaderParseError):
1144            parser.get_local_part(' (foo) @python.org')
1145
1146    def test_get_local_part_trailing_dot(self):
1147        local_part = self._test_get_x(parser.get_local_part,
1148            ' borris.@python.org',
1149            ' borris.',
1150            ' borris.',
1151            [errors.InvalidHeaderDefect]*2,
1152            '@python.org')
1153        self.assertEqual(local_part.local_part, 'borris.')
1154
1155    def test_get_local_part_trailing_dot_with_ws(self):
1156        local_part = self._test_get_x(parser.get_local_part,
1157            ' borris. @python.org',
1158            ' borris. ',
1159            ' borris. ',
1160            [errors.InvalidHeaderDefect]*2,
1161            '@python.org')
1162        self.assertEqual(local_part.local_part, 'borris.')
1163
1164    def test_get_local_part_leading_dot(self):
1165        local_part = self._test_get_x(parser.get_local_part,
1166            '.borris@python.org',
1167            '.borris',
1168            '.borris',
1169            [errors.InvalidHeaderDefect]*2,
1170            '@python.org')
1171        self.assertEqual(local_part.local_part, '.borris')
1172
1173    def test_get_local_part_leading_dot_after_ws(self):
1174        local_part = self._test_get_x(parser.get_local_part,
1175            ' .borris@python.org',
1176            ' .borris',
1177            ' .borris',
1178            [errors.InvalidHeaderDefect]*2,
1179            '@python.org')
1180        self.assertEqual(local_part.local_part, '.borris')
1181
1182    def test_get_local_part_double_dot_raises(self):
1183        local_part = self._test_get_x(parser.get_local_part,
1184            ' borris.(foo).natasha@python.org',
1185            ' borris.(foo).natasha',
1186            ' borris. .natasha',
1187            [errors.InvalidHeaderDefect]*2,
1188            '@python.org')
1189        self.assertEqual(local_part.local_part, 'borris..natasha')
1190
1191    def test_get_local_part_quoted_strings_in_atom_list(self):
1192        local_part = self._test_get_x(parser.get_local_part,
1193            '""example" example"@example.com',
1194            '""example" example"',
1195            'example example',
1196            [errors.InvalidHeaderDefect]*3,
1197            '@example.com')
1198        self.assertEqual(local_part.local_part, 'example example')
1199
1200    def test_get_local_part_valid_and_invalid_qp_in_atom_list(self):
1201        local_part = self._test_get_x(parser.get_local_part,
1202            r'"\\"example\\" example"@example.com',
1203            r'"\\"example\\" example"',
1204            r'\example\\ example',
1205            [errors.InvalidHeaderDefect]*5,
1206            '@example.com')
1207        self.assertEqual(local_part.local_part, r'\example\\ example')
1208
1209    def test_get_local_part_unicode_defect(self):
1210        # Currently this only happens when parsing unicode, not when parsing
1211        # stuff that was originally binary.
1212        local_part = self._test_get_x(parser.get_local_part,
1213            'exámple@example.com',
1214            'exámple',
1215            'exámple',
1216            [errors.NonASCIILocalPartDefect],
1217            '@example.com')
1218        self.assertEqual(local_part.local_part, 'exámple')
1219
1220    # get_dtext
1221
1222    def test_get_dtext_only(self):
1223        dtext = self._test_get_x(parser.get_dtext,
1224                                'foobar', 'foobar', 'foobar', [], '')
1225        self.assertEqual(dtext.token_type, 'ptext')
1226
1227    def test_get_dtext_all_dtext(self):
1228        dtext = self._test_get_x(parser.get_dtext, self.rfc_dtext_chars,
1229                                 self.rfc_dtext_chars,
1230                                 self.rfc_dtext_chars, [], '')
1231
1232    def test_get_dtext_two_words_gets_first(self):
1233        self._test_get_x(parser.get_dtext,
1234                        'foo bar', 'foo', 'foo', [], ' bar')
1235
1236    def test_get_dtext_following_wsp_preserved(self):
1237        self._test_get_x(parser.get_dtext,
1238                        'foo \t\tbar', 'foo', 'foo', [], ' \t\tbar')
1239
1240    def test_get_dtext_non_printables(self):
1241        dtext = self._test_get_x(parser.get_dtext,
1242                                'foo\x00bar]', 'foo\x00bar', 'foo\x00bar',
1243                                [errors.NonPrintableDefect], ']')
1244        self.assertEqual(dtext.defects[0].non_printables[0], '\x00')
1245
1246    def test_get_dtext_with_qp(self):
1247        ptext = self._test_get_x(parser.get_dtext,
1248                                 r'foo\]\[\\bar\b\e\l\l',
1249                                 r'foo][\barbell',
1250                                 r'foo][\barbell',
1251                                 [errors.ObsoleteHeaderDefect],
1252                                 '')
1253
1254    def test_get_dtext_up_to_close_bracket_only(self):
1255        self._test_get_x(parser.get_dtext,
1256                        'foo]', 'foo', 'foo', [], ']')
1257
1258    def test_get_dtext_wsp_before_close_bracket_preserved(self):
1259        self._test_get_x(parser.get_dtext,
1260                        'foo  ]', 'foo', 'foo', [], '  ]')
1261
1262    def test_get_dtext_close_bracket_mid_word(self):
1263        self._test_get_x(parser.get_dtext,
1264                        'foo]bar', 'foo', 'foo', [], ']bar')
1265
1266    def test_get_dtext_up_to_open_bracket_only(self):
1267        self._test_get_x(parser.get_dtext,
1268                        'foo[', 'foo', 'foo', [], '[')
1269
1270    def test_get_dtext_wsp_before_open_bracket_preserved(self):
1271        self._test_get_x(parser.get_dtext,
1272                        'foo  [', 'foo', 'foo', [], '  [')
1273
1274    def test_get_dtext_open_bracket_mid_word(self):
1275        self._test_get_x(parser.get_dtext,
1276                        'foo[bar', 'foo', 'foo', [], '[bar')
1277
1278    # get_domain_literal
1279
1280    def test_get_domain_literal_only(self):
1281        domain_literal = domain_literal = self._test_get_x(parser.get_domain_literal,
1282                                '[127.0.0.1]',
1283                                '[127.0.0.1]',
1284                                '[127.0.0.1]',
1285                                [],
1286                                '')
1287        self.assertEqual(domain_literal.token_type, 'domain-literal')
1288        self.assertEqual(domain_literal.domain, '[127.0.0.1]')
1289        self.assertEqual(domain_literal.ip, '127.0.0.1')
1290
1291    def test_get_domain_literal_with_internal_ws(self):
1292        domain_literal = self._test_get_x(parser.get_domain_literal,
1293                                '[  127.0.0.1\t ]',
1294                                '[  127.0.0.1\t ]',
1295                                '[ 127.0.0.1 ]',
1296                                [],
1297                                '')
1298        self.assertEqual(domain_literal.domain, '[127.0.0.1]')
1299        self.assertEqual(domain_literal.ip, '127.0.0.1')
1300
1301    def test_get_domain_literal_with_surrounding_cfws(self):
1302        domain_literal = self._test_get_x(parser.get_domain_literal,
1303                                '(foo)[  127.0.0.1] (bar)',
1304                                '(foo)[  127.0.0.1] (bar)',
1305                                ' [ 127.0.0.1] ',
1306                                [],
1307                                '')
1308        self.assertEqual(domain_literal.domain, '[127.0.0.1]')
1309        self.assertEqual(domain_literal.ip, '127.0.0.1')
1310
1311    def test_get_domain_literal_no_start_char_raises(self):
1312        with self.assertRaises(errors.HeaderParseError):
1313            parser.get_domain_literal('(foo) ')
1314
1315    def test_get_domain_literal_no_start_char_before_special_raises(self):
1316        with self.assertRaises(errors.HeaderParseError):
1317            parser.get_domain_literal('(foo) @')
1318
1319    def test_get_domain_literal_bad_dtext_char_before_special_raises(self):
1320        with self.assertRaises(errors.HeaderParseError):
1321            parser.get_domain_literal('(foo) [abc[@')
1322
1323    # get_domain
1324
1325    def test_get_domain_regular_domain_only(self):
1326        domain = self._test_get_x(parser.get_domain,
1327                                  'example.com',
1328                                  'example.com',
1329                                  'example.com',
1330                                  [],
1331                                  '')
1332        self.assertEqual(domain.token_type, 'domain')
1333        self.assertEqual(domain.domain, 'example.com')
1334
1335    def test_get_domain_domain_literal_only(self):
1336        domain = self._test_get_x(parser.get_domain,
1337                                  '[127.0.0.1]',
1338                                  '[127.0.0.1]',
1339                                  '[127.0.0.1]',
1340                                  [],
1341                                  '')
1342        self.assertEqual(domain.token_type, 'domain')
1343        self.assertEqual(domain.domain, '[127.0.0.1]')
1344
1345    def test_get_domain_with_cfws(self):
1346        domain = self._test_get_x(parser.get_domain,
1347                                  '(foo) example.com(bar)\t',
1348                                  '(foo) example.com(bar)\t',
1349                                  ' example.com ',
1350                                  [],
1351                                  '')
1352        self.assertEqual(domain.domain, 'example.com')
1353
1354    def test_get_domain_domain_literal_with_cfws(self):
1355        domain = self._test_get_x(parser.get_domain,
1356                                  '(foo)[127.0.0.1]\t(bar)',
1357                                  '(foo)[127.0.0.1]\t(bar)',
1358                                  ' [127.0.0.1] ',
1359                                  [],
1360                                  '')
1361        self.assertEqual(domain.domain, '[127.0.0.1]')
1362
1363    def test_get_domain_domain_with_cfws_ends_at_special(self):
1364        domain = self._test_get_x(parser.get_domain,
1365                                  '(foo)example.com\t(bar), next',
1366                                  '(foo)example.com\t(bar)',
1367                                  ' example.com ',
1368                                  [],
1369                                  ', next')
1370        self.assertEqual(domain.domain, 'example.com')
1371
1372    def test_get_domain_domain_literal_with_cfws_ends_at_special(self):
1373        domain = self._test_get_x(parser.get_domain,
1374                                  '(foo)[127.0.0.1]\t(bar), next',
1375                                  '(foo)[127.0.0.1]\t(bar)',
1376                                  ' [127.0.0.1] ',
1377                                  [],
1378                                  ', next')
1379        self.assertEqual(domain.domain, '[127.0.0.1]')
1380
1381    def test_get_domain_obsolete(self):
1382        domain = self._test_get_x(parser.get_domain,
1383                                  '(foo) example . (bird)com(bar)\t',
1384                                  '(foo) example . (bird)com(bar)\t',
1385                                  ' example . com ',
1386                                  [errors.ObsoleteHeaderDefect],
1387                                  '')
1388        self.assertEqual(domain.domain, 'example.com')
1389
1390    def test_get_domain_no_non_cfws_raises(self):
1391        with self.assertRaises(errors.HeaderParseError):
1392            parser.get_domain("  (foo)\t")
1393
1394    def test_get_domain_no_atom_raises(self):
1395        with self.assertRaises(errors.HeaderParseError):
1396            parser.get_domain("  (foo)\t, broken")
1397
1398
1399    # get_addr_spec
1400
1401    def test_get_addr_spec_normal(self):
1402        addr_spec = self._test_get_x(parser.get_addr_spec,
1403                                    'dinsdale@example.com',
1404                                    'dinsdale@example.com',
1405                                    'dinsdale@example.com',
1406                                    [],
1407                                    '')
1408        self.assertEqual(addr_spec.token_type, 'addr-spec')
1409        self.assertEqual(addr_spec.local_part, 'dinsdale')
1410        self.assertEqual(addr_spec.domain, 'example.com')
1411        self.assertEqual(addr_spec.addr_spec, 'dinsdale@example.com')
1412
1413    def test_get_addr_spec_with_doamin_literal(self):
1414        addr_spec = self._test_get_x(parser.get_addr_spec,
1415                                    'dinsdale@[127.0.0.1]',
1416                                    'dinsdale@[127.0.0.1]',
1417                                    'dinsdale@[127.0.0.1]',
1418                                    [],
1419                                    '')
1420        self.assertEqual(addr_spec.local_part, 'dinsdale')
1421        self.assertEqual(addr_spec.domain, '[127.0.0.1]')
1422        self.assertEqual(addr_spec.addr_spec, 'dinsdale@[127.0.0.1]')
1423
1424    def test_get_addr_spec_with_cfws(self):
1425        addr_spec = self._test_get_x(parser.get_addr_spec,
1426                '(foo) dinsdale(bar)@ (bird) example.com (bog)',
1427                '(foo) dinsdale(bar)@ (bird) example.com (bog)',
1428                ' dinsdale@example.com ',
1429                [],
1430                '')
1431        self.assertEqual(addr_spec.local_part, 'dinsdale')
1432        self.assertEqual(addr_spec.domain, 'example.com')
1433        self.assertEqual(addr_spec.addr_spec, 'dinsdale@example.com')
1434
1435    def test_get_addr_spec_with_qouoted_string_and_cfws(self):
1436        addr_spec = self._test_get_x(parser.get_addr_spec,
1437                '(foo) "roy a bug"(bar)@ (bird) example.com (bog)',
1438                '(foo) "roy a bug"(bar)@ (bird) example.com (bog)',
1439                ' "roy a bug"@example.com ',
1440                [],
1441                '')
1442        self.assertEqual(addr_spec.local_part, 'roy a bug')
1443        self.assertEqual(addr_spec.domain, 'example.com')
1444        self.assertEqual(addr_spec.addr_spec, '"roy a bug"@example.com')
1445
1446    def test_get_addr_spec_ends_at_special(self):
1447        addr_spec = self._test_get_x(parser.get_addr_spec,
1448                '(foo) "roy a bug"(bar)@ (bird) example.com (bog) , next',
1449                '(foo) "roy a bug"(bar)@ (bird) example.com (bog) ',
1450                ' "roy a bug"@example.com ',
1451                [],
1452                ', next')
1453        self.assertEqual(addr_spec.local_part, 'roy a bug')
1454        self.assertEqual(addr_spec.domain, 'example.com')
1455        self.assertEqual(addr_spec.addr_spec, '"roy a bug"@example.com')
1456
1457    def test_get_addr_spec_quoted_strings_in_atom_list(self):
1458        addr_spec = self._test_get_x(parser.get_addr_spec,
1459            '""example" example"@example.com',
1460            '""example" example"@example.com',
1461            'example example@example.com',
1462            [errors.InvalidHeaderDefect]*3,
1463            '')
1464        self.assertEqual(addr_spec.local_part, 'example example')
1465        self.assertEqual(addr_spec.domain, 'example.com')
1466        self.assertEqual(addr_spec.addr_spec, '"example example"@example.com')
1467
1468    def test_get_addr_spec_dot_atom(self):
1469        addr_spec = self._test_get_x(parser.get_addr_spec,
1470            'star.a.star@example.com',
1471            'star.a.star@example.com',
1472            'star.a.star@example.com',
1473            [],
1474            '')
1475        self.assertEqual(addr_spec.local_part, 'star.a.star')
1476        self.assertEqual(addr_spec.domain, 'example.com')
1477        self.assertEqual(addr_spec.addr_spec, 'star.a.star@example.com')
1478
1479    def test_get_addr_spec_multiple_domains(self):
1480        with self.assertRaises(errors.HeaderParseError):
1481            parser.get_addr_spec('star@a.star@example.com')
1482
1483        with self.assertRaises(errors.HeaderParseError):
1484            parser.get_addr_spec('star@a@example.com')
1485
1486        with self.assertRaises(errors.HeaderParseError):
1487            parser.get_addr_spec('star@172.17.0.1@example.com')
1488
1489    # get_obs_route
1490
1491    def test_get_obs_route_simple(self):
1492        obs_route = self._test_get_x(parser.get_obs_route,
1493            '@example.com, @two.example.com:',
1494            '@example.com, @two.example.com:',
1495            '@example.com, @two.example.com:',
1496            [],
1497            '')
1498        self.assertEqual(obs_route.token_type, 'obs-route')
1499        self.assertEqual(obs_route.domains, ['example.com', 'two.example.com'])
1500
1501    def test_get_obs_route_complex(self):
1502        obs_route = self._test_get_x(parser.get_obs_route,
1503            '(foo),, (blue)@example.com (bar),@two.(foo) example.com (bird):',
1504            '(foo),, (blue)@example.com (bar),@two.(foo) example.com (bird):',
1505            ' ,, @example.com ,@two. example.com :',
1506            [errors.ObsoleteHeaderDefect],  # This is the obs-domain
1507            '')
1508        self.assertEqual(obs_route.token_type, 'obs-route')
1509        self.assertEqual(obs_route.domains, ['example.com', 'two.example.com'])
1510
1511    def test_get_obs_route_no_route_before_end_raises(self):
1512        with self.assertRaises(errors.HeaderParseError):
1513            parser.get_obs_route('(foo) @example.com,')
1514
1515    def test_get_obs_route_no_route_before_special_raises(self):
1516        with self.assertRaises(errors.HeaderParseError):
1517            parser.get_obs_route('(foo) [abc],')
1518
1519    def test_get_obs_route_no_route_before_special_raises2(self):
1520        with self.assertRaises(errors.HeaderParseError):
1521            parser.get_obs_route('(foo) @example.com [abc],')
1522
1523    # get_angle_addr
1524
1525    def test_get_angle_addr_simple(self):
1526        angle_addr = self._test_get_x(parser.get_angle_addr,
1527            '<dinsdale@example.com>',
1528            '<dinsdale@example.com>',
1529            '<dinsdale@example.com>',
1530            [],
1531            '')
1532        self.assertEqual(angle_addr.token_type, 'angle-addr')
1533        self.assertEqual(angle_addr.local_part, 'dinsdale')
1534        self.assertEqual(angle_addr.domain, 'example.com')
1535        self.assertIsNone(angle_addr.route)
1536        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1537
1538    def test_get_angle_addr_empty(self):
1539        angle_addr = self._test_get_x(parser.get_angle_addr,
1540            '<>',
1541            '<>',
1542            '<>',
1543            [errors.InvalidHeaderDefect],
1544            '')
1545        self.assertEqual(angle_addr.token_type, 'angle-addr')
1546        self.assertIsNone(angle_addr.local_part)
1547        self.assertIsNone(angle_addr.domain)
1548        self.assertIsNone(angle_addr.route)
1549        self.assertEqual(angle_addr.addr_spec, '<>')
1550
1551    def test_get_angle_addr_qs_only_quotes(self):
1552        angle_addr = self._test_get_x(parser.get_angle_addr,
1553            '<""@example.com>',
1554            '<""@example.com>',
1555            '<""@example.com>',
1556            [],
1557            '')
1558        self.assertEqual(angle_addr.token_type, 'angle-addr')
1559        self.assertEqual(angle_addr.local_part, '')
1560        self.assertEqual(angle_addr.domain, 'example.com')
1561        self.assertIsNone(angle_addr.route)
1562        self.assertEqual(angle_addr.addr_spec, '""@example.com')
1563
1564    def test_get_angle_addr_with_cfws(self):
1565        angle_addr = self._test_get_x(parser.get_angle_addr,
1566            ' (foo) <dinsdale@example.com>(bar)',
1567            ' (foo) <dinsdale@example.com>(bar)',
1568            ' <dinsdale@example.com> ',
1569            [],
1570            '')
1571        self.assertEqual(angle_addr.token_type, 'angle-addr')
1572        self.assertEqual(angle_addr.local_part, 'dinsdale')
1573        self.assertEqual(angle_addr.domain, 'example.com')
1574        self.assertIsNone(angle_addr.route)
1575        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1576
1577    def test_get_angle_addr_qs_and_domain_literal(self):
1578        angle_addr = self._test_get_x(parser.get_angle_addr,
1579            '<"Fred Perfect"@[127.0.0.1]>',
1580            '<"Fred Perfect"@[127.0.0.1]>',
1581            '<"Fred Perfect"@[127.0.0.1]>',
1582            [],
1583            '')
1584        self.assertEqual(angle_addr.local_part, 'Fred Perfect')
1585        self.assertEqual(angle_addr.domain, '[127.0.0.1]')
1586        self.assertIsNone(angle_addr.route)
1587        self.assertEqual(angle_addr.addr_spec, '"Fred Perfect"@[127.0.0.1]')
1588
1589    def test_get_angle_addr_internal_cfws(self):
1590        angle_addr = self._test_get_x(parser.get_angle_addr,
1591            '<(foo) dinsdale@example.com(bar)>',
1592            '<(foo) dinsdale@example.com(bar)>',
1593            '< dinsdale@example.com >',
1594            [],
1595            '')
1596        self.assertEqual(angle_addr.local_part, 'dinsdale')
1597        self.assertEqual(angle_addr.domain, 'example.com')
1598        self.assertIsNone(angle_addr.route)
1599        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1600
1601    def test_get_angle_addr_obs_route(self):
1602        angle_addr = self._test_get_x(parser.get_angle_addr,
1603            '(foo)<@example.com, (bird) @two.example.com: dinsdale@example.com> (bar) ',
1604            '(foo)<@example.com, (bird) @two.example.com: dinsdale@example.com> (bar) ',
1605            ' <@example.com, @two.example.com: dinsdale@example.com> ',
1606            [errors.ObsoleteHeaderDefect],
1607            '')
1608        self.assertEqual(angle_addr.local_part, 'dinsdale')
1609        self.assertEqual(angle_addr.domain, 'example.com')
1610        self.assertEqual(angle_addr.route, ['example.com', 'two.example.com'])
1611        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1612
1613    def test_get_angle_addr_missing_closing_angle(self):
1614        angle_addr = self._test_get_x(parser.get_angle_addr,
1615            '<dinsdale@example.com',
1616            '<dinsdale@example.com>',
1617            '<dinsdale@example.com>',
1618            [errors.InvalidHeaderDefect],
1619            '')
1620        self.assertEqual(angle_addr.local_part, 'dinsdale')
1621        self.assertEqual(angle_addr.domain, 'example.com')
1622        self.assertIsNone(angle_addr.route)
1623        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1624
1625    def test_get_angle_addr_missing_closing_angle_with_cfws(self):
1626        angle_addr = self._test_get_x(parser.get_angle_addr,
1627            '<dinsdale@example.com (foo)',
1628            '<dinsdale@example.com (foo)>',
1629            '<dinsdale@example.com >',
1630            [errors.InvalidHeaderDefect],
1631            '')
1632        self.assertEqual(angle_addr.local_part, 'dinsdale')
1633        self.assertEqual(angle_addr.domain, 'example.com')
1634        self.assertIsNone(angle_addr.route)
1635        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1636
1637    def test_get_angle_addr_ends_at_special(self):
1638        angle_addr = self._test_get_x(parser.get_angle_addr,
1639            '<dinsdale@example.com> (foo), next',
1640            '<dinsdale@example.com> (foo)',
1641            '<dinsdale@example.com> ',
1642            [],
1643            ', next')
1644        self.assertEqual(angle_addr.local_part, 'dinsdale')
1645        self.assertEqual(angle_addr.domain, 'example.com')
1646        self.assertIsNone(angle_addr.route)
1647        self.assertEqual(angle_addr.addr_spec, 'dinsdale@example.com')
1648
1649    def test_get_angle_addr_no_angle_raise(self):
1650        with self.assertRaises(errors.HeaderParseError):
1651            parser.get_angle_addr('(foo) ')
1652
1653    def test_get_angle_addr_no_angle_before_special_raises(self):
1654        with self.assertRaises(errors.HeaderParseError):
1655            parser.get_angle_addr('(foo) , next')
1656
1657    def test_get_angle_addr_no_angle_raises(self):
1658        with self.assertRaises(errors.HeaderParseError):
1659            parser.get_angle_addr('bar')
1660
1661    def test_get_angle_addr_special_after_angle_raises(self):
1662        with self.assertRaises(errors.HeaderParseError):
1663            parser.get_angle_addr('(foo) <, bar')
1664
1665    # get_display_name  This is phrase but with a different value.
1666
1667    def test_get_display_name_simple(self):
1668        display_name = self._test_get_x(parser.get_display_name,
1669            'Fred A Johnson',
1670            'Fred A Johnson',
1671            'Fred A Johnson',
1672            [],
1673            '')
1674        self.assertEqual(display_name.token_type, 'display-name')
1675        self.assertEqual(display_name.display_name, 'Fred A Johnson')
1676
1677    def test_get_display_name_complex1(self):
1678        display_name = self._test_get_x(parser.get_display_name,
1679            '"Fred A. Johnson" is his name, oh.',
1680            '"Fred A. Johnson" is his name',
1681            '"Fred A. Johnson is his name"',
1682            [],
1683            ', oh.')
1684        self.assertEqual(display_name.token_type, 'display-name')
1685        self.assertEqual(display_name.display_name, 'Fred A. Johnson is his name')
1686
1687    def test_get_display_name_complex2(self):
1688        display_name = self._test_get_x(parser.get_display_name,
1689            ' (A) bird (in (my|your)) "hand  " is messy\t<>\t',
1690            ' (A) bird (in (my|your)) "hand  " is messy\t',
1691            ' "bird hand   is messy" ',
1692            [],
1693            '<>\t')
1694        self.assertEqual(display_name[0][0].comments, ['A'])
1695        self.assertEqual(display_name[0][2].comments, ['in (my|your)'])
1696        self.assertEqual(display_name.display_name, 'bird hand   is messy')
1697
1698    def test_get_display_name_obsolete(self):
1699        display_name = self._test_get_x(parser.get_display_name,
1700            'Fred A.(weird).O Johnson',
1701            'Fred A.(weird).O Johnson',
1702            '"Fred A. .O Johnson"',
1703            [errors.ObsoleteHeaderDefect]*3,
1704            '')
1705        self.assertEqual(len(display_name), 7)
1706        self.assertEqual(display_name[3].comments, ['weird'])
1707        self.assertEqual(display_name.display_name, 'Fred A. .O Johnson')
1708
1709    def test_get_display_name_pharse_must_start_with_word(self):
1710        display_name = self._test_get_x(parser.get_display_name,
1711            '(even weirder).name',
1712            '(even weirder).name',
1713            ' ".name"',
1714            [errors.InvalidHeaderDefect] + [errors.ObsoleteHeaderDefect]*2,
1715            '')
1716        self.assertEqual(len(display_name), 3)
1717        self.assertEqual(display_name[0].comments, ['even weirder'])
1718        self.assertEqual(display_name.display_name, '.name')
1719
1720    def test_get_display_name_ending_with_obsolete(self):
1721        display_name = self._test_get_x(parser.get_display_name,
1722            'simple phrase.(with trailing comment):boo',
1723            'simple phrase.(with trailing comment)',
1724            '"simple phrase." ',
1725            [errors.ObsoleteHeaderDefect]*2,
1726            ':boo')
1727        self.assertEqual(len(display_name), 4)
1728        self.assertEqual(display_name[3].comments, ['with trailing comment'])
1729        self.assertEqual(display_name.display_name, 'simple phrase.')
1730
1731    def test_get_display_name_for_invalid_address_field(self):
1732        # bpo-32178: Test that address fields starting with `:` don't cause
1733        # IndexError when parsing the display name.
1734        display_name = self._test_get_x(
1735            parser.get_display_name,
1736            ':Foo ', '', '', [errors.InvalidHeaderDefect], ':Foo ')
1737        self.assertEqual(display_name.value, '')
1738
1739    # get_name_addr
1740
1741    def test_get_name_addr_angle_addr_only(self):
1742        name_addr = self._test_get_x(parser.get_name_addr,
1743            '<dinsdale@example.com>',
1744            '<dinsdale@example.com>',
1745            '<dinsdale@example.com>',
1746            [],
1747            '')
1748        self.assertEqual(name_addr.token_type, 'name-addr')
1749        self.assertIsNone(name_addr.display_name)
1750        self.assertEqual(name_addr.local_part, 'dinsdale')
1751        self.assertEqual(name_addr.domain, 'example.com')
1752        self.assertIsNone(name_addr.route)
1753        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1754
1755    def test_get_name_addr_atom_name(self):
1756        name_addr = self._test_get_x(parser.get_name_addr,
1757            'Dinsdale <dinsdale@example.com>',
1758            'Dinsdale <dinsdale@example.com>',
1759            'Dinsdale <dinsdale@example.com>',
1760            [],
1761            '')
1762        self.assertEqual(name_addr.token_type, 'name-addr')
1763        self.assertEqual(name_addr.display_name, 'Dinsdale')
1764        self.assertEqual(name_addr.local_part, 'dinsdale')
1765        self.assertEqual(name_addr.domain, 'example.com')
1766        self.assertIsNone(name_addr.route)
1767        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1768
1769    def test_get_name_addr_atom_name_with_cfws(self):
1770        name_addr = self._test_get_x(parser.get_name_addr,
1771            '(foo) Dinsdale (bar) <dinsdale@example.com> (bird)',
1772            '(foo) Dinsdale (bar) <dinsdale@example.com> (bird)',
1773            ' Dinsdale <dinsdale@example.com> ',
1774            [],
1775            '')
1776        self.assertEqual(name_addr.display_name, 'Dinsdale')
1777        self.assertEqual(name_addr.local_part, 'dinsdale')
1778        self.assertEqual(name_addr.domain, 'example.com')
1779        self.assertIsNone(name_addr.route)
1780        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1781
1782    def test_get_name_addr_name_with_cfws_and_dots(self):
1783        name_addr = self._test_get_x(parser.get_name_addr,
1784            '(foo) Roy.A.Bear (bar) <dinsdale@example.com> (bird)',
1785            '(foo) Roy.A.Bear (bar) <dinsdale@example.com> (bird)',
1786            ' "Roy.A.Bear" <dinsdale@example.com> ',
1787            [errors.ObsoleteHeaderDefect]*2,
1788            '')
1789        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
1790        self.assertEqual(name_addr.local_part, 'dinsdale')
1791        self.assertEqual(name_addr.domain, 'example.com')
1792        self.assertIsNone(name_addr.route)
1793        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1794
1795    def test_get_name_addr_qs_name(self):
1796        name_addr = self._test_get_x(parser.get_name_addr,
1797            '"Roy.A.Bear" <dinsdale@example.com>',
1798            '"Roy.A.Bear" <dinsdale@example.com>',
1799            '"Roy.A.Bear" <dinsdale@example.com>',
1800            [],
1801            '')
1802        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
1803        self.assertEqual(name_addr.local_part, 'dinsdale')
1804        self.assertEqual(name_addr.domain, 'example.com')
1805        self.assertIsNone(name_addr.route)
1806        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1807
1808    def test_get_name_addr_with_route(self):
1809        name_addr = self._test_get_x(parser.get_name_addr,
1810            '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
1811            '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
1812            '"Roy.A.Bear" <@two.example.com: dinsdale@example.com>',
1813            [errors.ObsoleteHeaderDefect],
1814            '')
1815        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
1816        self.assertEqual(name_addr.local_part, 'dinsdale')
1817        self.assertEqual(name_addr.domain, 'example.com')
1818        self.assertEqual(name_addr.route, ['two.example.com'])
1819        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1820
1821    def test_get_name_addr_ends_at_special(self):
1822        name_addr = self._test_get_x(parser.get_name_addr,
1823            '"Roy.A.Bear" <dinsdale@example.com>, next',
1824            '"Roy.A.Bear" <dinsdale@example.com>',
1825            '"Roy.A.Bear" <dinsdale@example.com>',
1826            [],
1827            ', next')
1828        self.assertEqual(name_addr.display_name, 'Roy.A.Bear')
1829        self.assertEqual(name_addr.local_part, 'dinsdale')
1830        self.assertEqual(name_addr.domain, 'example.com')
1831        self.assertIsNone(name_addr.route)
1832        self.assertEqual(name_addr.addr_spec, 'dinsdale@example.com')
1833
1834    def test_get_name_addr_no_content_raises(self):
1835        with self.assertRaises(errors.HeaderParseError):
1836            parser.get_name_addr(' (foo) ')
1837
1838    def test_get_name_addr_no_content_before_special_raises(self):
1839        with self.assertRaises(errors.HeaderParseError):
1840            parser.get_name_addr(' (foo) ,')
1841
1842    def test_get_name_addr_no_angle_after_display_name_raises(self):
1843        with self.assertRaises(errors.HeaderParseError):
1844            parser.get_name_addr('foo bar')
1845
1846    # get_mailbox
1847
1848    def test_get_mailbox_addr_spec_only(self):
1849        mailbox = self._test_get_x(parser.get_mailbox,
1850            'dinsdale@example.com',
1851            'dinsdale@example.com',
1852            'dinsdale@example.com',
1853            [],
1854            '')
1855        self.assertEqual(mailbox.token_type, 'mailbox')
1856        self.assertIsNone(mailbox.display_name)
1857        self.assertEqual(mailbox.local_part, 'dinsdale')
1858        self.assertEqual(mailbox.domain, 'example.com')
1859        self.assertIsNone(mailbox.route)
1860        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
1861
1862    def test_get_mailbox_angle_addr_only(self):
1863        mailbox = self._test_get_x(parser.get_mailbox,
1864            '<dinsdale@example.com>',
1865            '<dinsdale@example.com>',
1866            '<dinsdale@example.com>',
1867            [],
1868            '')
1869        self.assertEqual(mailbox.token_type, 'mailbox')
1870        self.assertIsNone(mailbox.display_name)
1871        self.assertEqual(mailbox.local_part, 'dinsdale')
1872        self.assertEqual(mailbox.domain, 'example.com')
1873        self.assertIsNone(mailbox.route)
1874        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
1875
1876    def test_get_mailbox_name_addr(self):
1877        mailbox = self._test_get_x(parser.get_mailbox,
1878            '"Roy A. Bear" <dinsdale@example.com>',
1879            '"Roy A. Bear" <dinsdale@example.com>',
1880            '"Roy A. Bear" <dinsdale@example.com>',
1881            [],
1882            '')
1883        self.assertEqual(mailbox.token_type, 'mailbox')
1884        self.assertEqual(mailbox.display_name, 'Roy A. Bear')
1885        self.assertEqual(mailbox.local_part, 'dinsdale')
1886        self.assertEqual(mailbox.domain, 'example.com')
1887        self.assertIsNone(mailbox.route)
1888        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
1889
1890    def test_get_mailbox_ends_at_special(self):
1891        mailbox = self._test_get_x(parser.get_mailbox,
1892            '"Roy A. Bear" <dinsdale@example.com>, rest',
1893            '"Roy A. Bear" <dinsdale@example.com>',
1894            '"Roy A. Bear" <dinsdale@example.com>',
1895            [],
1896            ', rest')
1897        self.assertEqual(mailbox.token_type, 'mailbox')
1898        self.assertEqual(mailbox.display_name, 'Roy A. Bear')
1899        self.assertEqual(mailbox.local_part, 'dinsdale')
1900        self.assertEqual(mailbox.domain, 'example.com')
1901        self.assertIsNone(mailbox.route)
1902        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
1903
1904    def test_get_mailbox_quoted_strings_in_atom_list(self):
1905        mailbox = self._test_get_x(parser.get_mailbox,
1906            '""example" example"@example.com',
1907            '""example" example"@example.com',
1908            'example example@example.com',
1909            [errors.InvalidHeaderDefect]*3,
1910            '')
1911        self.assertEqual(mailbox.local_part, 'example example')
1912        self.assertEqual(mailbox.domain, 'example.com')
1913        self.assertEqual(mailbox.addr_spec, '"example example"@example.com')
1914
1915    # get_mailbox_list
1916
1917    def test_get_mailbox_list_single_addr(self):
1918        mailbox_list = self._test_get_x(parser.get_mailbox_list,
1919            'dinsdale@example.com',
1920            'dinsdale@example.com',
1921            'dinsdale@example.com',
1922            [],
1923            '')
1924        self.assertEqual(mailbox_list.token_type, 'mailbox-list')
1925        self.assertEqual(len(mailbox_list.mailboxes), 1)
1926        mailbox = mailbox_list.mailboxes[0]
1927        self.assertIsNone(mailbox.display_name)
1928        self.assertEqual(mailbox.local_part, 'dinsdale')
1929        self.assertEqual(mailbox.domain, 'example.com')
1930        self.assertIsNone(mailbox.route)
1931        self.assertEqual(mailbox.addr_spec, 'dinsdale@example.com')
1932        self.assertEqual(mailbox_list.mailboxes,
1933                         mailbox_list.all_mailboxes)
1934
1935    def test_get_mailbox_list_two_simple_addr(self):
1936        mailbox_list = self._test_get_x(parser.get_mailbox_list,
1937            'dinsdale@example.com, dinsdale@test.example.com',
1938            'dinsdale@example.com, dinsdale@test.example.com',
1939            'dinsdale@example.com, dinsdale@test.example.com',
1940            [],
1941            '')
1942        self.assertEqual(mailbox_list.token_type, 'mailbox-list')
1943        self.assertEqual(len(mailbox_list.mailboxes), 2)
1944        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
1945                        'dinsdale@example.com')
1946        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
1947                        'dinsdale@test.example.com')
1948        self.assertEqual(mailbox_list.mailboxes,
1949                         mailbox_list.all_mailboxes)
1950
1951    def test_get_mailbox_list_two_name_addr(self):
1952        mailbox_list = self._test_get_x(parser.get_mailbox_list,
1953            ('"Roy A. Bear" <dinsdale@example.com>,'
1954                ' "Fred Flintstone" <dinsdale@test.example.com>'),
1955            ('"Roy A. Bear" <dinsdale@example.com>,'
1956                ' "Fred Flintstone" <dinsdale@test.example.com>'),
1957            ('"Roy A. Bear" <dinsdale@example.com>,'
1958                ' "Fred Flintstone" <dinsdale@test.example.com>'),
1959            [],
1960            '')
1961        self.assertEqual(len(mailbox_list.mailboxes), 2)
1962        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
1963                        'dinsdale@example.com')
1964        self.assertEqual(mailbox_list.mailboxes[0].display_name,
1965                        'Roy A. Bear')
1966        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
1967                        'dinsdale@test.example.com')
1968        self.assertEqual(mailbox_list.mailboxes[1].display_name,
1969                        'Fred Flintstone')
1970        self.assertEqual(mailbox_list.mailboxes,
1971                         mailbox_list.all_mailboxes)
1972
1973    def test_get_mailbox_list_two_complex(self):
1974        mailbox_list = self._test_get_x(parser.get_mailbox_list,
1975            ('(foo) "Roy A. Bear" <dinsdale@example.com>(bar),'
1976                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
1977            ('(foo) "Roy A. Bear" <dinsdale@example.com>(bar),'
1978                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
1979            (' "Roy A. Bear" <dinsdale@example.com> ,'
1980                ' "Fred Flintstone" <dinsdale@test. example.com>'),
1981            [errors.ObsoleteHeaderDefect],
1982            '')
1983        self.assertEqual(len(mailbox_list.mailboxes), 2)
1984        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
1985                        'dinsdale@example.com')
1986        self.assertEqual(mailbox_list.mailboxes[0].display_name,
1987                        'Roy A. Bear')
1988        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
1989                        'dinsdale@test.example.com')
1990        self.assertEqual(mailbox_list.mailboxes[1].display_name,
1991                        'Fred Flintstone')
1992        self.assertEqual(mailbox_list.mailboxes,
1993                         mailbox_list.all_mailboxes)
1994
1995    def test_get_mailbox_list_unparseable_mailbox_null(self):
1996        mailbox_list = self._test_get_x(parser.get_mailbox_list,
1997            ('"Roy A. Bear"[] dinsdale@example.com,'
1998                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
1999            ('"Roy A. Bear"[] dinsdale@example.com,'
2000                ' "Fred Flintstone" <dinsdale@test.(bird)example.com>'),
2001            ('"Roy A. Bear"[] dinsdale@example.com,'
2002                ' "Fred Flintstone" <dinsdale@test. example.com>'),
2003            [errors.InvalidHeaderDefect,   # the 'extra' text after the local part
2004             errors.InvalidHeaderDefect,   # the local part with no angle-addr
2005             errors.ObsoleteHeaderDefect,  # period in extra text (example.com)
2006             errors.ObsoleteHeaderDefect], # (bird) in valid address.
2007            '')
2008        self.assertEqual(len(mailbox_list.mailboxes), 1)
2009        self.assertEqual(len(mailbox_list.all_mailboxes), 2)
2010        self.assertEqual(mailbox_list.all_mailboxes[0].token_type,
2011                        'invalid-mailbox')
2012        self.assertIsNone(mailbox_list.all_mailboxes[0].display_name)
2013        self.assertEqual(mailbox_list.all_mailboxes[0].local_part,
2014                        'Roy A. Bear')
2015        self.assertIsNone(mailbox_list.all_mailboxes[0].domain)
2016        self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
2017                        '"Roy A. Bear"')
2018        self.assertIs(mailbox_list.all_mailboxes[1],
2019                        mailbox_list.mailboxes[0])
2020        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
2021                        'dinsdale@test.example.com')
2022        self.assertEqual(mailbox_list.mailboxes[0].display_name,
2023                        'Fred Flintstone')
2024
2025    def test_get_mailbox_list_junk_after_valid_address(self):
2026        mailbox_list = self._test_get_x(parser.get_mailbox_list,
2027            ('"Roy A. Bear" <dinsdale@example.com>@@,'
2028                ' "Fred Flintstone" <dinsdale@test.example.com>'),
2029            ('"Roy A. Bear" <dinsdale@example.com>@@,'
2030                ' "Fred Flintstone" <dinsdale@test.example.com>'),
2031            ('"Roy A. Bear" <dinsdale@example.com>@@,'
2032                ' "Fred Flintstone" <dinsdale@test.example.com>'),
2033            [errors.InvalidHeaderDefect],
2034            '')
2035        self.assertEqual(len(mailbox_list.mailboxes), 1)
2036        self.assertEqual(len(mailbox_list.all_mailboxes), 2)
2037        self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
2038                        'dinsdale@example.com')
2039        self.assertEqual(mailbox_list.all_mailboxes[0].display_name,
2040                        'Roy A. Bear')
2041        self.assertEqual(mailbox_list.all_mailboxes[0].token_type,
2042                        'invalid-mailbox')
2043        self.assertIs(mailbox_list.all_mailboxes[1],
2044                        mailbox_list.mailboxes[0])
2045        self.assertEqual(mailbox_list.mailboxes[0].addr_spec,
2046                        'dinsdale@test.example.com')
2047        self.assertEqual(mailbox_list.mailboxes[0].display_name,
2048                        'Fred Flintstone')
2049
2050    def test_get_mailbox_list_empty_list_element(self):
2051        mailbox_list = self._test_get_x(parser.get_mailbox_list,
2052            ('"Roy A. Bear" <dinsdale@example.com>, (bird),,'
2053                ' "Fred Flintstone" <dinsdale@test.example.com>'),
2054            ('"Roy A. Bear" <dinsdale@example.com>, (bird),,'
2055                ' "Fred Flintstone" <dinsdale@test.example.com>'),
2056            ('"Roy A. Bear" <dinsdale@example.com>, ,,'
2057                ' "Fred Flintstone" <dinsdale@test.example.com>'),
2058            [errors.ObsoleteHeaderDefect]*2,
2059            '')
2060        self.assertEqual(len(mailbox_list.mailboxes), 2)
2061        self.assertEqual(mailbox_list.all_mailboxes,
2062                         mailbox_list.mailboxes)
2063        self.assertEqual(mailbox_list.all_mailboxes[0].addr_spec,
2064                        'dinsdale@example.com')
2065        self.assertEqual(mailbox_list.all_mailboxes[0].display_name,
2066                        'Roy A. Bear')
2067        self.assertEqual(mailbox_list.mailboxes[1].addr_spec,
2068                        'dinsdale@test.example.com')
2069        self.assertEqual(mailbox_list.mailboxes[1].display_name,
2070                        'Fred Flintstone')
2071
2072    def test_get_mailbox_list_only_empty_elements(self):
2073        mailbox_list = self._test_get_x(parser.get_mailbox_list,
2074            '(foo),, (bar)',
2075            '(foo),, (bar)',
2076            ' ,, ',
2077            [errors.ObsoleteHeaderDefect]*3,
2078            '')
2079        self.assertEqual(len(mailbox_list.mailboxes), 0)
2080        self.assertEqual(mailbox_list.all_mailboxes,
2081                         mailbox_list.mailboxes)
2082
2083    # get_group_list
2084
2085    def test_get_group_list_cfws_only(self):
2086        group_list = self._test_get_x(parser.get_group_list,
2087            '(hidden);',
2088            '(hidden)',
2089            ' ',
2090            [],
2091            ';')
2092        self.assertEqual(group_list.token_type, 'group-list')
2093        self.assertEqual(len(group_list.mailboxes), 0)
2094        self.assertEqual(group_list.mailboxes,
2095                         group_list.all_mailboxes)
2096
2097    def test_get_group_list_mailbox_list(self):
2098        group_list = self._test_get_x(parser.get_group_list,
2099            'dinsdale@example.org, "Fred A. Bear" <dinsdale@example.org>',
2100            'dinsdale@example.org, "Fred A. Bear" <dinsdale@example.org>',
2101            'dinsdale@example.org, "Fred A. Bear" <dinsdale@example.org>',
2102            [],
2103            '')
2104        self.assertEqual(group_list.token_type, 'group-list')
2105        self.assertEqual(len(group_list.mailboxes), 2)
2106        self.assertEqual(group_list.mailboxes,
2107                         group_list.all_mailboxes)
2108        self.assertEqual(group_list.mailboxes[1].display_name,
2109                         'Fred A. Bear')
2110
2111    def test_get_group_list_obs_group_list(self):
2112        group_list = self._test_get_x(parser.get_group_list,
2113            ', (foo),,(bar)',
2114            ', (foo),,(bar)',
2115            ', ,, ',
2116            [errors.ObsoleteHeaderDefect],
2117            '')
2118        self.assertEqual(group_list.token_type, 'group-list')
2119        self.assertEqual(len(group_list.mailboxes), 0)
2120        self.assertEqual(group_list.mailboxes,
2121                         group_list.all_mailboxes)
2122
2123    def test_get_group_list_comment_only_invalid(self):
2124        group_list = self._test_get_x(parser.get_group_list,
2125            '(bar)',
2126            '(bar)',
2127            ' ',
2128            [errors.InvalidHeaderDefect],
2129            '')
2130        self.assertEqual(group_list.token_type, 'group-list')
2131        self.assertEqual(len(group_list.mailboxes), 0)
2132        self.assertEqual(group_list.mailboxes,
2133                         group_list.all_mailboxes)
2134
2135    # get_group
2136
2137    def test_get_group_empty(self):
2138        group = self._test_get_x(parser.get_group,
2139            'Monty Python:;',
2140            'Monty Python:;',
2141            'Monty Python:;',
2142            [],
2143            '')
2144        self.assertEqual(group.token_type, 'group')
2145        self.assertEqual(group.display_name, 'Monty Python')
2146        self.assertEqual(len(group.mailboxes), 0)
2147        self.assertEqual(group.mailboxes,
2148                         group.all_mailboxes)
2149
2150    def test_get_group_null_addr_spec(self):
2151        group = self._test_get_x(parser.get_group,
2152            'foo: <>;',
2153            'foo: <>;',
2154            'foo: <>;',
2155            [errors.InvalidHeaderDefect],
2156            '')
2157        self.assertEqual(group.display_name, 'foo')
2158        self.assertEqual(len(group.mailboxes), 0)
2159        self.assertEqual(len(group.all_mailboxes), 1)
2160        self.assertEqual(group.all_mailboxes[0].value, '<>')
2161
2162    def test_get_group_cfws_only(self):
2163        group = self._test_get_x(parser.get_group,
2164            'Monty Python: (hidden);',
2165            'Monty Python: (hidden);',
2166            'Monty Python: ;',
2167            [],
2168            '')
2169        self.assertEqual(group.token_type, 'group')
2170        self.assertEqual(group.display_name, 'Monty Python')
2171        self.assertEqual(len(group.mailboxes), 0)
2172        self.assertEqual(group.mailboxes,
2173                         group.all_mailboxes)
2174
2175    def test_get_group_single_mailbox(self):
2176        group = self._test_get_x(parser.get_group,
2177            'Monty Python: "Fred A. Bear" <dinsdale@example.com>;',
2178            'Monty Python: "Fred A. Bear" <dinsdale@example.com>;',
2179            'Monty Python: "Fred A. Bear" <dinsdale@example.com>;',
2180            [],
2181            '')
2182        self.assertEqual(group.token_type, 'group')
2183        self.assertEqual(group.display_name, 'Monty Python')
2184        self.assertEqual(len(group.mailboxes), 1)
2185        self.assertEqual(group.mailboxes,
2186                         group.all_mailboxes)
2187        self.assertEqual(group.mailboxes[0].addr_spec,
2188                         'dinsdale@example.com')
2189
2190    def test_get_group_mixed_list(self):
2191        group = self._test_get_x(parser.get_group,
2192            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
2193                '(foo) Roger <ping@exampele.com>, x@test.example.com;'),
2194            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
2195                '(foo) Roger <ping@exampele.com>, x@test.example.com;'),
2196            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
2197                ' Roger <ping@exampele.com>, x@test.example.com;'),
2198            [],
2199            '')
2200        self.assertEqual(group.token_type, 'group')
2201        self.assertEqual(group.display_name, 'Monty Python')
2202        self.assertEqual(len(group.mailboxes), 3)
2203        self.assertEqual(group.mailboxes,
2204                         group.all_mailboxes)
2205        self.assertEqual(group.mailboxes[0].display_name,
2206                         'Fred A. Bear')
2207        self.assertEqual(group.mailboxes[1].display_name,
2208                         'Roger')
2209        self.assertEqual(group.mailboxes[2].local_part, 'x')
2210
2211    def test_get_group_one_invalid(self):
2212        group = self._test_get_x(parser.get_group,
2213            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
2214                '(foo) Roger ping@exampele.com, x@test.example.com;'),
2215            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
2216                '(foo) Roger ping@exampele.com, x@test.example.com;'),
2217            ('Monty Python: "Fred A. Bear" <dinsdale@example.com>,'
2218                ' Roger ping@exampele.com, x@test.example.com;'),
2219            [errors.InvalidHeaderDefect,   # non-angle addr makes local part invalid
2220             errors.InvalidHeaderDefect],   # and its not obs-local either: no dots.
2221            '')
2222        self.assertEqual(group.token_type, 'group')
2223        self.assertEqual(group.display_name, 'Monty Python')
2224        self.assertEqual(len(group.mailboxes), 2)
2225        self.assertEqual(len(group.all_mailboxes), 3)
2226        self.assertEqual(group.mailboxes[0].display_name,
2227                         'Fred A. Bear')
2228        self.assertEqual(group.mailboxes[1].local_part, 'x')
2229        self.assertIsNone(group.all_mailboxes[1].display_name)
2230
2231    def test_get_group_missing_final_semicol(self):
2232        group = self._test_get_x(parser.get_group,
2233            ('Monty Python:"Fred A. Bear" <dinsdale@example.com>,'
2234             'eric@where.test,John <jdoe@test>'),
2235            ('Monty Python:"Fred A. Bear" <dinsdale@example.com>,'
2236             'eric@where.test,John <jdoe@test>;'),
2237            ('Monty Python:"Fred A. Bear" <dinsdale@example.com>,'
2238             'eric@where.test,John <jdoe@test>;'),
2239            [errors.InvalidHeaderDefect],
2240            '')
2241        self.assertEqual(group.token_type, 'group')
2242        self.assertEqual(group.display_name, 'Monty Python')
2243        self.assertEqual(len(group.mailboxes), 3)
2244        self.assertEqual(group.mailboxes,
2245                         group.all_mailboxes)
2246        self.assertEqual(group.mailboxes[0].addr_spec,
2247                         'dinsdale@example.com')
2248        self.assertEqual(group.mailboxes[0].display_name,
2249                         'Fred A. Bear')
2250        self.assertEqual(group.mailboxes[1].addr_spec,
2251                         'eric@where.test')
2252        self.assertEqual(group.mailboxes[2].display_name,
2253                         'John')
2254        self.assertEqual(group.mailboxes[2].addr_spec,
2255                         'jdoe@test')
2256    # get_address
2257
2258    def test_get_address_simple(self):
2259        address = self._test_get_x(parser.get_address,
2260            'dinsdale@example.com',
2261            'dinsdale@example.com',
2262            'dinsdale@example.com',
2263            [],
2264            '')
2265        self.assertEqual(address.token_type, 'address')
2266        self.assertEqual(len(address.mailboxes), 1)
2267        self.assertEqual(address.mailboxes,
2268                         address.all_mailboxes)
2269        self.assertEqual(address.mailboxes[0].domain,
2270                         'example.com')
2271        self.assertEqual(address[0].token_type,
2272                         'mailbox')
2273
2274    def test_get_address_complex(self):
2275        address = self._test_get_x(parser.get_address,
2276            '(foo) "Fred A. Bear" <(bird)dinsdale@example.com>',
2277            '(foo) "Fred A. Bear" <(bird)dinsdale@example.com>',
2278            ' "Fred A. Bear" < dinsdale@example.com>',
2279            [],
2280            '')
2281        self.assertEqual(address.token_type, 'address')
2282        self.assertEqual(len(address.mailboxes), 1)
2283        self.assertEqual(address.mailboxes,
2284                         address.all_mailboxes)
2285        self.assertEqual(address.mailboxes[0].display_name,
2286                         'Fred A. Bear')
2287        self.assertEqual(address[0].token_type,
2288                         'mailbox')
2289
2290    def test_get_address_rfc2047_display_name(self):
2291        address = self._test_get_x(parser.get_address,
2292            '=?utf-8?q?=C3=89ric?= <foo@example.com>',
2293            'Éric <foo@example.com>',
2294            'Éric <foo@example.com>',
2295            [],
2296            '')
2297        self.assertEqual(address.token_type, 'address')
2298        self.assertEqual(len(address.mailboxes), 1)
2299        self.assertEqual(address.mailboxes,
2300                         address.all_mailboxes)
2301        self.assertEqual(address.mailboxes[0].display_name,
2302                         'Éric')
2303        self.assertEqual(address[0].token_type,
2304                         'mailbox')
2305
2306    def test_get_address_empty_group(self):
2307        address = self._test_get_x(parser.get_address,
2308            'Monty Python:;',
2309            'Monty Python:;',
2310            'Monty Python:;',
2311            [],
2312            '')
2313        self.assertEqual(address.token_type, 'address')
2314        self.assertEqual(len(address.mailboxes), 0)
2315        self.assertEqual(address.mailboxes,
2316                         address.all_mailboxes)
2317        self.assertEqual(address[0].token_type,
2318                         'group')
2319        self.assertEqual(address[0].display_name,
2320                         'Monty Python')
2321
2322    def test_get_address_group(self):
2323        address = self._test_get_x(parser.get_address,
2324            'Monty Python: x@example.com, y@example.com;',
2325            'Monty Python: x@example.com, y@example.com;',
2326            'Monty Python: x@example.com, y@example.com;',
2327            [],
2328            '')
2329        self.assertEqual(address.token_type, 'address')
2330        self.assertEqual(len(address.mailboxes), 2)
2331        self.assertEqual(address.mailboxes,
2332                         address.all_mailboxes)
2333        self.assertEqual(address[0].token_type,
2334                         'group')
2335        self.assertEqual(address[0].display_name,
2336                         'Monty Python')
2337        self.assertEqual(address.mailboxes[0].local_part, 'x')
2338
2339    def test_get_address_quoted_local_part(self):
2340        address = self._test_get_x(parser.get_address,
2341            '"foo bar"@example.com',
2342            '"foo bar"@example.com',
2343            '"foo bar"@example.com',
2344            [],
2345            '')
2346        self.assertEqual(address.token_type, 'address')
2347        self.assertEqual(len(address.mailboxes), 1)
2348        self.assertEqual(address.mailboxes,
2349                         address.all_mailboxes)
2350        self.assertEqual(address.mailboxes[0].domain,
2351                         'example.com')
2352        self.assertEqual(address.mailboxes[0].local_part,
2353                         'foo bar')
2354        self.assertEqual(address[0].token_type, 'mailbox')
2355
2356    def test_get_address_ends_at_special(self):
2357        address = self._test_get_x(parser.get_address,
2358            'dinsdale@example.com, next',
2359            'dinsdale@example.com',
2360            'dinsdale@example.com',
2361            [],
2362            ', next')
2363        self.assertEqual(address.token_type, 'address')
2364        self.assertEqual(len(address.mailboxes), 1)
2365        self.assertEqual(address.mailboxes,
2366                         address.all_mailboxes)
2367        self.assertEqual(address.mailboxes[0].domain,
2368                         'example.com')
2369        self.assertEqual(address[0].token_type, 'mailbox')
2370
2371    def test_get_address_invalid_mailbox_invalid(self):
2372        address = self._test_get_x(parser.get_address,
2373            'ping example.com, next',
2374            'ping example.com',
2375            'ping example.com',
2376            [errors.InvalidHeaderDefect,    # addr-spec with no domain
2377             errors.InvalidHeaderDefect,    # invalid local-part
2378             errors.InvalidHeaderDefect,    # missing .s in local-part
2379            ],
2380            ', next')
2381        self.assertEqual(address.token_type, 'address')
2382        self.assertEqual(len(address.mailboxes), 0)
2383        self.assertEqual(len(address.all_mailboxes), 1)
2384        self.assertIsNone(address.all_mailboxes[0].domain)
2385        self.assertEqual(address.all_mailboxes[0].local_part, 'ping example.com')
2386        self.assertEqual(address[0].token_type, 'invalid-mailbox')
2387
2388    def test_get_address_quoted_strings_in_atom_list(self):
2389        address = self._test_get_x(parser.get_address,
2390            '""example" example"@example.com',
2391            '""example" example"@example.com',
2392            'example example@example.com',
2393            [errors.InvalidHeaderDefect]*3,
2394            '')
2395        self.assertEqual(address.all_mailboxes[0].local_part, 'example example')
2396        self.assertEqual(address.all_mailboxes[0].domain, 'example.com')
2397        self.assertEqual(address.all_mailboxes[0].addr_spec, '"example example"@example.com')
2398
2399
2400    # get_address_list
2401
2402    def test_get_address_list_CFWS(self):
2403        address_list = self._test_get_x(parser.get_address_list,
2404                                        '(Recipient list suppressed)',
2405                                        '(Recipient list suppressed)',
2406                                        ' ',
2407                                        [errors.ObsoleteHeaderDefect],  # no content in address list
2408                                        '')
2409        self.assertEqual(address_list.token_type, 'address-list')
2410        self.assertEqual(len(address_list.mailboxes), 0)
2411        self.assertEqual(address_list.mailboxes, address_list.all_mailboxes)
2412
2413    def test_get_address_list_mailboxes_simple(self):
2414        address_list = self._test_get_x(parser.get_address_list,
2415            'dinsdale@example.com',
2416            'dinsdale@example.com',
2417            'dinsdale@example.com',
2418            [],
2419            '')
2420        self.assertEqual(address_list.token_type, 'address-list')
2421        self.assertEqual(len(address_list.mailboxes), 1)
2422        self.assertEqual(address_list.mailboxes,
2423                         address_list.all_mailboxes)
2424        self.assertEqual([str(x) for x in address_list.mailboxes],
2425                         [str(x) for x in address_list.addresses])
2426        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
2427        self.assertEqual(address_list[0].token_type, 'address')
2428        self.assertIsNone(address_list[0].display_name)
2429
2430    def test_get_address_list_mailboxes_two_simple(self):
2431        address_list = self._test_get_x(parser.get_address_list,
2432            'foo@example.com, "Fred A. Bar" <bar@example.com>',
2433            'foo@example.com, "Fred A. Bar" <bar@example.com>',
2434            'foo@example.com, "Fred A. Bar" <bar@example.com>',
2435            [],
2436            '')
2437        self.assertEqual(address_list.token_type, 'address-list')
2438        self.assertEqual(len(address_list.mailboxes), 2)
2439        self.assertEqual(address_list.mailboxes,
2440                         address_list.all_mailboxes)
2441        self.assertEqual([str(x) for x in address_list.mailboxes],
2442                         [str(x) for x in address_list.addresses])
2443        self.assertEqual(address_list.mailboxes[0].local_part, 'foo')
2444        self.assertEqual(address_list.mailboxes[1].display_name, "Fred A. Bar")
2445
2446    def test_get_address_list_mailboxes_complex(self):
2447        address_list = self._test_get_x(parser.get_address_list,
2448            ('"Roy A. Bear" <dinsdale@example.com>, '
2449                '(ping) Foo <x@example.com>,'
2450                'Nobody Is. Special <y@(bird)example.(bad)com>'),
2451            ('"Roy A. Bear" <dinsdale@example.com>, '
2452                '(ping) Foo <x@example.com>,'
2453                'Nobody Is. Special <y@(bird)example.(bad)com>'),
2454            ('"Roy A. Bear" <dinsdale@example.com>, '
2455                'Foo <x@example.com>,'
2456                '"Nobody Is. Special" <y@example. com>'),
2457            [errors.ObsoleteHeaderDefect, # period in Is.
2458            errors.ObsoleteHeaderDefect], # cfws in domain
2459            '')
2460        self.assertEqual(address_list.token_type, 'address-list')
2461        self.assertEqual(len(address_list.mailboxes), 3)
2462        self.assertEqual(address_list.mailboxes,
2463                         address_list.all_mailboxes)
2464        self.assertEqual([str(x) for x in address_list.mailboxes],
2465                         [str(x) for x in address_list.addresses])
2466        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
2467        self.assertEqual(address_list.mailboxes[0].token_type, 'mailbox')
2468        self.assertEqual(address_list.addresses[0].token_type, 'address')
2469        self.assertEqual(address_list.mailboxes[1].local_part, 'x')
2470        self.assertEqual(address_list.mailboxes[2].display_name,
2471                         'Nobody Is. Special')
2472
2473    def test_get_address_list_mailboxes_invalid_addresses(self):
2474        address_list = self._test_get_x(parser.get_address_list,
2475            ('"Roy A. Bear" <dinsdale@example.com>, '
2476                '(ping) Foo x@example.com[],'
2477                'Nobody Is. Special <(bird)example.(bad)com>'),
2478            ('"Roy A. Bear" <dinsdale@example.com>, '
2479                '(ping) Foo x@example.com[],'
2480                'Nobody Is. Special <(bird)example.(bad)com>'),
2481            ('"Roy A. Bear" <dinsdale@example.com>, '
2482                'Foo x@example.com[],'
2483                '"Nobody Is. Special" < example. com>'),
2484             [errors.InvalidHeaderDefect,   # invalid address in list
2485              errors.InvalidHeaderDefect,   # 'Foo x' local part invalid.
2486              errors.InvalidHeaderDefect,   # Missing . in 'Foo x' local part
2487              errors.ObsoleteHeaderDefect,  # period in 'Is.' disp-name phrase
2488              errors.InvalidHeaderDefect,   # no domain part in addr-spec
2489              errors.ObsoleteHeaderDefect], # addr-spec has comment in it
2490            '')
2491        self.assertEqual(address_list.token_type, 'address-list')
2492        self.assertEqual(len(address_list.mailboxes), 1)
2493        self.assertEqual(len(address_list.all_mailboxes), 3)
2494        self.assertEqual([str(x) for x in address_list.all_mailboxes],
2495                         [str(x) for x in address_list.addresses])
2496        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
2497        self.assertEqual(address_list.mailboxes[0].token_type, 'mailbox')
2498        self.assertEqual(address_list.addresses[0].token_type, 'address')
2499        self.assertEqual(address_list.addresses[1].token_type, 'address')
2500        self.assertEqual(len(address_list.addresses[0].mailboxes), 1)
2501        self.assertEqual(len(address_list.addresses[1].mailboxes), 0)
2502        self.assertEqual(len(address_list.addresses[1].mailboxes), 0)
2503        self.assertEqual(
2504            address_list.addresses[1].all_mailboxes[0].local_part, 'Foo x')
2505        self.assertEqual(
2506            address_list.addresses[2].all_mailboxes[0].display_name,
2507                "Nobody Is. Special")
2508
2509    def test_get_address_list_group_empty(self):
2510        address_list = self._test_get_x(parser.get_address_list,
2511            'Monty Python: ;',
2512            'Monty Python: ;',
2513            'Monty Python: ;',
2514            [],
2515            '')
2516        self.assertEqual(address_list.token_type, 'address-list')
2517        self.assertEqual(len(address_list.mailboxes), 0)
2518        self.assertEqual(address_list.mailboxes,
2519                         address_list.all_mailboxes)
2520        self.assertEqual(len(address_list.addresses), 1)
2521        self.assertEqual(address_list.addresses[0].token_type, 'address')
2522        self.assertEqual(address_list.addresses[0].display_name, 'Monty Python')
2523        self.assertEqual(len(address_list.addresses[0].mailboxes), 0)
2524
2525    def test_get_address_list_group_simple(self):
2526        address_list = self._test_get_x(parser.get_address_list,
2527            'Monty Python: dinsdale@example.com;',
2528            'Monty Python: dinsdale@example.com;',
2529            'Monty Python: dinsdale@example.com;',
2530            [],
2531            '')
2532        self.assertEqual(address_list.token_type, 'address-list')
2533        self.assertEqual(len(address_list.mailboxes), 1)
2534        self.assertEqual(address_list.mailboxes,
2535                         address_list.all_mailboxes)
2536        self.assertEqual(address_list.mailboxes[0].domain, 'example.com')
2537        self.assertEqual(address_list.addresses[0].display_name,
2538                         'Monty Python')
2539        self.assertEqual(address_list.addresses[0].mailboxes[0].domain,
2540                         'example.com')
2541
2542    def test_get_address_list_group_and_mailboxes(self):
2543        address_list = self._test_get_x(parser.get_address_list,
2544            ('Monty Python: dinsdale@example.com, "Fred" <flint@example.com>;, '
2545                'Abe <x@example.com>, Bee <y@example.com>'),
2546            ('Monty Python: dinsdale@example.com, "Fred" <flint@example.com>;, '
2547                'Abe <x@example.com>, Bee <y@example.com>'),
2548            ('Monty Python: dinsdale@example.com, "Fred" <flint@example.com>;, '
2549                'Abe <x@example.com>, Bee <y@example.com>'),
2550            [],
2551            '')
2552        self.assertEqual(address_list.token_type, 'address-list')
2553        self.assertEqual(len(address_list.mailboxes), 4)
2554        self.assertEqual(address_list.mailboxes,
2555                         address_list.all_mailboxes)
2556        self.assertEqual(len(address_list.addresses), 3)
2557        self.assertEqual(address_list.mailboxes[0].local_part, 'dinsdale')
2558        self.assertEqual(address_list.addresses[0].display_name,
2559                         'Monty Python')
2560        self.assertEqual(address_list.addresses[0].mailboxes[0].domain,
2561                         'example.com')
2562        self.assertEqual(address_list.addresses[0].mailboxes[1].local_part,
2563                         'flint')
2564        self.assertEqual(address_list.addresses[1].mailboxes[0].local_part,
2565                         'x')
2566        self.assertEqual(address_list.addresses[2].mailboxes[0].local_part,
2567                         'y')
2568        self.assertEqual(str(address_list.addresses[1]),
2569                         str(address_list.mailboxes[2]))
2570
2571    def test_invalid_content_disposition(self):
2572        content_disp = self._test_parse_x(
2573            parser.parse_content_disposition_header,
2574            ";attachment", "; attachment", ";attachment",
2575            [errors.InvalidHeaderDefect]*2
2576        )
2577
2578    def test_invalid_content_transfer_encoding(self):
2579        cte = self._test_parse_x(
2580            parser.parse_content_transfer_encoding_header,
2581            ";foo", ";foo", ";foo", [errors.InvalidHeaderDefect]*3
2582        )
2583
2584    # get_msg_id
2585
2586    def test_get_msg_id_empty(self):
2587        # bpo-38708: Test that HeaderParseError is raised and not IndexError.
2588        with self.assertRaises(errors.HeaderParseError):
2589            parser.get_msg_id('')
2590
2591    def test_get_msg_id_valid(self):
2592        msg_id = self._test_get_x(
2593            parser.get_msg_id,
2594            "<simeple.local@example.something.com>",
2595            "<simeple.local@example.something.com>",
2596            "<simeple.local@example.something.com>",
2597            [],
2598            '',
2599            )
2600        self.assertEqual(msg_id.token_type, 'msg-id')
2601
2602    def test_get_msg_id_obsolete_local(self):
2603        msg_id = self._test_get_x(
2604            parser.get_msg_id,
2605            '<"simeple.local"@example.com>',
2606            '<"simeple.local"@example.com>',
2607            '<simeple.local@example.com>',
2608            [errors.ObsoleteHeaderDefect],
2609            '',
2610            )
2611        self.assertEqual(msg_id.token_type, 'msg-id')
2612
2613    def test_get_msg_id_non_folding_literal_domain(self):
2614        msg_id = self._test_get_x(
2615            parser.get_msg_id,
2616            "<simple.local@[someexamplecom.domain]>",
2617            "<simple.local@[someexamplecom.domain]>",
2618            "<simple.local@[someexamplecom.domain]>",
2619            [],
2620            "",
2621            )
2622        self.assertEqual(msg_id.token_type, 'msg-id')
2623
2624
2625    def test_get_msg_id_obsolete_domain_part(self):
2626        msg_id = self._test_get_x(
2627            parser.get_msg_id,
2628            "<simplelocal@(old)example.com>",
2629            "<simplelocal@(old)example.com>",
2630            "<simplelocal@ example.com>",
2631            [errors.ObsoleteHeaderDefect],
2632            ""
2633        )
2634
2635    def test_get_msg_id_no_id_right_part(self):
2636        msg_id = self._test_get_x(
2637            parser.get_msg_id,
2638            "<simplelocal>",
2639            "<simplelocal>",
2640            "<simplelocal>",
2641            [errors.InvalidHeaderDefect],
2642            ""
2643        )
2644        self.assertEqual(msg_id.token_type, 'msg-id')
2645
2646    def test_get_msg_id_invalid_expected_msg_id_not_found(self):
2647        text = "935-XPB-567:0:45327:9:90305:17843586-40@example.com"
2648        msg_id = parser.parse_message_id(text)
2649        self.assertDefectsEqual(
2650            msg_id.all_defects,
2651            [errors.InvalidHeaderDefect])
2652
2653    def test_parse_invalid_message_id(self):
2654        message_id = self._test_parse_x(
2655            parser.parse_message_id,
2656            "935-XPB-567:0:45327:9:90305:17843586-40@example.com",
2657            "935-XPB-567:0:45327:9:90305:17843586-40@example.com",
2658            "935-XPB-567:0:45327:9:90305:17843586-40@example.com",
2659            [errors.InvalidHeaderDefect],
2660            )
2661        self.assertEqual(message_id.token_type, 'invalid-message-id')
2662
2663    def test_parse_valid_message_id(self):
2664        message_id = self._test_parse_x(
2665            parser.parse_message_id,
2666            "<aperson@somedomain>",
2667            "<aperson@somedomain>",
2668            "<aperson@somedomain>",
2669            [],
2670            )
2671        self.assertEqual(message_id.token_type, 'message-id')
2672
2673    def test_parse_message_id_with_remaining(self):
2674        message_id = self._test_parse_x(
2675            parser.parse_message_id,
2676            "<validmessageid@example>thensomething",
2677            "<validmessageid@example>",
2678            "<validmessageid@example>",
2679            [errors.InvalidHeaderDefect],
2680            [],
2681            )
2682        self.assertEqual(message_id.token_type, 'message-id')
2683        self.assertEqual(str(message_id.all_defects[0]),
2684                         "Unexpected 'thensomething'")
2685
2686    def test_get_msg_id_no_angle_start(self):
2687        with self.assertRaises(errors.HeaderParseError):
2688            parser.get_msg_id("msgwithnoankle")
2689
2690    def test_get_msg_id_no_angle_end(self):
2691        msg_id = self._test_get_x(
2692            parser.get_msg_id,
2693            "<simplelocal@domain",
2694            "<simplelocal@domain>",
2695            "<simplelocal@domain>",
2696            [errors.InvalidHeaderDefect],
2697            ""
2698        )
2699        self.assertEqual(msg_id.token_type, 'msg-id')
2700
2701
2702
2703@parameterize
2704class Test_parse_mime_parameters(TestParserMixin, TestEmailBase):
2705
2706    def mime_parameters_as_value(self,
2707                                 value,
2708                                 tl_str,
2709                                 tl_value,
2710                                 params,
2711                                 defects):
2712        mime_parameters = self._test_parse_x(parser.parse_mime_parameters,
2713            value, tl_str, tl_value, defects)
2714        self.assertEqual(mime_parameters.token_type, 'mime-parameters')
2715        self.assertEqual(list(mime_parameters.params), params)
2716
2717
2718    mime_parameters_params = {
2719
2720        'simple': (
2721            'filename="abc.py"',
2722            ' filename="abc.py"',
2723            'filename=abc.py',
2724            [('filename', 'abc.py')],
2725            []),
2726
2727        'multiple_keys': (
2728            'filename="abc.py"; xyz=abc',
2729            ' filename="abc.py"; xyz="abc"',
2730            'filename=abc.py; xyz=abc',
2731            [('filename', 'abc.py'), ('xyz', 'abc')],
2732            []),
2733
2734        'split_value': (
2735            "filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66",
2736            ' filename="201.tif"',
2737            "filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66",
2738            [('filename', '201.tif')],
2739            []),
2740
2741        # Note that it is undefined what we should do for error recovery when
2742        # there are duplicate parameter names or duplicate parts in a split
2743        # part.  We choose to ignore all duplicate parameters after the first
2744        # and to take duplicate or missing rfc 2231 parts in appearance order.
2745        # This is backward compatible with get_param's behavior, but the
2746        # decisions are arbitrary.
2747
2748        'duplicate_key': (
2749            'filename=abc.gif; filename=def.tiff',
2750            ' filename="abc.gif"',
2751            "filename=abc.gif; filename=def.tiff",
2752            [('filename', 'abc.gif')],
2753            [errors.InvalidHeaderDefect]),
2754
2755        'duplicate_key_with_split_value': (
2756            "filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66;"
2757                " filename=abc.gif",
2758            ' filename="201.tif"',
2759            "filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66;"
2760                " filename=abc.gif",
2761            [('filename', '201.tif')],
2762            [errors.InvalidHeaderDefect]),
2763
2764        'duplicate_key_with_split_value_other_order': (
2765            "filename=abc.gif; "
2766                " filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66",
2767            ' filename="abc.gif"',
2768            "filename=abc.gif;"
2769                " filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66",
2770            [('filename', 'abc.gif')],
2771            [errors.InvalidHeaderDefect]),
2772
2773        'duplicate_in_split_value': (
2774            "filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66;"
2775                " filename*1*=abc.gif",
2776            ' filename="201.tifabc.gif"',
2777            "filename*0*=iso-8859-1''%32%30%31%2E; filename*1*=%74%69%66;"
2778                " filename*1*=abc.gif",
2779            [('filename', '201.tifabc.gif')],
2780            [errors.InvalidHeaderDefect]),
2781
2782        'missing_split_value': (
2783            "filename*0*=iso-8859-1''%32%30%31%2E; filename*3*=%74%69%66;",
2784            ' filename="201.tif"',
2785            "filename*0*=iso-8859-1''%32%30%31%2E; filename*3*=%74%69%66;",
2786            [('filename', '201.tif')],
2787            [errors.InvalidHeaderDefect]),
2788
2789        'duplicate_and_missing_split_value': (
2790            "filename*0*=iso-8859-1''%32%30%31%2E; filename*3*=%74%69%66;"
2791                " filename*3*=abc.gif",
2792            ' filename="201.tifabc.gif"',
2793            "filename*0*=iso-8859-1''%32%30%31%2E; filename*3*=%74%69%66;"
2794                " filename*3*=abc.gif",
2795            [('filename', '201.tifabc.gif')],
2796            [errors.InvalidHeaderDefect]*2),
2797
2798        # Here we depart from get_param and assume the *0* was missing.
2799        'duplicate_with_broken_split_value': (
2800            "filename=abc.gif; "
2801                " filename*2*=iso-8859-1''%32%30%31%2E; filename*3*=%74%69%66",
2802            ' filename="abc.gif201.tif"',
2803            "filename=abc.gif;"
2804                " filename*2*=iso-8859-1''%32%30%31%2E; filename*3*=%74%69%66",
2805            [('filename', 'abc.gif201.tif')],
2806            # Defects are apparent missing *0*, and two 'out of sequence'.
2807            [errors.InvalidHeaderDefect]*3),
2808
2809        # bpo-37461: Check that we don't go into an infinite loop.
2810        'extra_dquote': (
2811            'r*="\'a\'\\"',
2812            ' r="\\""',
2813            'r*=\'a\'"',
2814            [('r', '"')],
2815            [errors.InvalidHeaderDefect]*2),
2816    }
2817
2818@parameterize
2819class Test_parse_mime_version(TestParserMixin, TestEmailBase):
2820
2821    def mime_version_as_value(self,
2822                              value,
2823                              tl_str,
2824                              tl_value,
2825                              major,
2826                              minor,
2827                              defects):
2828        mime_version = self._test_parse_x(parser.parse_mime_version,
2829            value, tl_str, tl_value, defects)
2830        self.assertEqual(mime_version.major, major)
2831        self.assertEqual(mime_version.minor, minor)
2832
2833    mime_version_params = {
2834
2835        'rfc_2045_1': (
2836            '1.0',
2837            '1.0',
2838            '1.0',
2839            1,
2840            0,
2841            []),
2842
2843        'RFC_2045_2': (
2844            '1.0 (produced by MetaSend Vx.x)',
2845            '1.0 (produced by MetaSend Vx.x)',
2846            '1.0 ',
2847            1,
2848            0,
2849            []),
2850
2851        'RFC_2045_3': (
2852            '(produced by MetaSend Vx.x) 1.0',
2853            '(produced by MetaSend Vx.x) 1.0',
2854            ' 1.0',
2855            1,
2856            0,
2857            []),
2858
2859        'RFC_2045_4': (
2860            '1.(produced by MetaSend Vx.x)0',
2861            '1.(produced by MetaSend Vx.x)0',
2862            '1. 0',
2863            1,
2864            0,
2865            []),
2866
2867        'empty': (
2868            '',
2869            '',
2870            '',
2871            None,
2872            None,
2873            [errors.HeaderMissingRequiredValue]),
2874
2875        }
2876
2877
2878
2879class TestFolding(TestEmailBase):
2880
2881    policy = policy.default
2882
2883    def _test(self, tl, folded, policy=policy):
2884        self.assertEqual(tl.fold(policy=policy), folded, tl.ppstr())
2885
2886    def test_simple_unstructured_no_folds(self):
2887        self._test(parser.get_unstructured("This is a test"),
2888                   "This is a test\n")
2889
2890    def test_simple_unstructured_folded(self):
2891        self._test(parser.get_unstructured("This is also a test, but this "
2892                        "time there are enough words (and even some "
2893                        "symbols) to make it wrap; at least in theory."),
2894                   "This is also a test, but this time there are enough "
2895                        "words (and even some\n"
2896                   " symbols) to make it wrap; at least in theory.\n")
2897
2898    def test_unstructured_with_unicode_no_folds(self):
2899        self._test(parser.get_unstructured("hübsch kleiner beißt"),
2900                   "=?utf-8?q?h=C3=BCbsch_kleiner_bei=C3=9Ft?=\n")
2901
2902    def test_one_ew_on_each_of_two_wrapped_lines(self):
2903        self._test(parser.get_unstructured("Mein kleiner Kaktus ist sehr "
2904                                           "hübsch.  Es hat viele Stacheln "
2905                                           "und oft beißt mich."),
2906                   "Mein kleiner Kaktus ist sehr =?utf-8?q?h=C3=BCbsch=2E?=  "
2907                        "Es hat viele Stacheln\n"
2908                   " und oft =?utf-8?q?bei=C3=9Ft?= mich.\n")
2909
2910    def test_ews_combined_before_wrap(self):
2911        self._test(parser.get_unstructured("Mein Kaktus ist hübsch.  "
2912                                           "Es beißt mich.  "
2913                                           "And that's all I'm sayin."),
2914                   "Mein Kaktus ist =?utf-8?q?h=C3=BCbsch=2E__Es_bei=C3=9Ft?= "
2915                        "mich.  And that's\n"
2916                   " all I'm sayin.\n")
2917
2918    # XXX Need test of an encoded word so long that it needs to be wrapped
2919
2920    def test_simple_address(self):
2921        self._test(parser.get_address_list("abc <xyz@example.com>")[0],
2922                   "abc <xyz@example.com>\n")
2923
2924    def test_address_list_folding_at_commas(self):
2925        self._test(parser.get_address_list('abc <xyz@example.com>, '
2926                                            '"Fred Blunt" <sharp@example.com>, '
2927                                            '"J.P.Cool" <hot@example.com>, '
2928                                            '"K<>y" <key@example.com>, '
2929                                            'Firesale <cheap@example.com>, '
2930                                            '<end@example.com>')[0],
2931                    'abc <xyz@example.com>, "Fred Blunt" <sharp@example.com>,\n'
2932                    ' "J.P.Cool" <hot@example.com>, "K<>y" <key@example.com>,\n'
2933                    ' Firesale <cheap@example.com>, <end@example.com>\n')
2934
2935    def test_address_list_with_unicode_names(self):
2936        self._test(parser.get_address_list(
2937            'Hübsch Kaktus <beautiful@example.com>, '
2938                'beißt beißt <biter@example.com>')[0],
2939            '=?utf-8?q?H=C3=BCbsch?= Kaktus <beautiful@example.com>,\n'
2940                ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= <biter@example.com>\n')
2941
2942    def test_address_list_with_unicode_names_in_quotes(self):
2943        self._test(parser.get_address_list(
2944            '"Hübsch Kaktus" <beautiful@example.com>, '
2945                '"beißt" beißt <biter@example.com>')[0],
2946            '=?utf-8?q?H=C3=BCbsch?= Kaktus <beautiful@example.com>,\n'
2947                ' =?utf-8?q?bei=C3=9Ft_bei=C3=9Ft?= <biter@example.com>\n')
2948
2949    # XXX Need tests with comments on various sides of a unicode token,
2950    # and with unicode tokens in the comments.  Spaces inside the quotes
2951    # currently don't do the right thing.
2952
2953    def test_split_at_whitespace_after_header_before_long_token(self):
2954        body = parser.get_unstructured('   ' + 'x'*77)
2955        header = parser.Header([
2956            parser.HeaderLabel([parser.ValueTerminal('test:', 'atext')]),
2957            parser.CFWSList([parser.WhiteSpaceTerminal(' ', 'fws')]), body])
2958        self._test(header, 'test:   \n ' + 'x'*77 + '\n')
2959
2960    def test_split_at_whitespace_before_long_token(self):
2961        self._test(parser.get_unstructured('xxx   ' + 'y'*77),
2962                   'xxx  \n ' + 'y'*77 + '\n')
2963
2964    def test_overlong_encodeable_is_wrapped(self):
2965        first_token_with_whitespace = 'xxx   '
2966        chrome_leader = '=?utf-8?q?'
2967        len_chrome = len(chrome_leader) + 2
2968        len_non_y = len_chrome + len(first_token_with_whitespace)
2969        self._test(parser.get_unstructured(first_token_with_whitespace +
2970                                           'y'*80),
2971                   first_token_with_whitespace + chrome_leader +
2972                       'y'*(78-len_non_y) + '?=\n' +
2973                       ' ' + chrome_leader + 'y'*(80-(78-len_non_y)) + '?=\n')
2974
2975    def test_long_filename_attachment(self):
2976        self._test(parser.parse_content_disposition_header(
2977            'attachment; filename="TEST_TEST_TEST_TEST'
2978                '_TEST_TEST_TEST_TEST_TEST_TEST_TEST_TEST_TES.txt"'),
2979            "attachment;\n"
2980            " filename*0*=us-ascii''TEST_TEST_TEST_TEST_TEST_TEST"
2981                "_TEST_TEST_TEST_TEST_TEST;\n"
2982            " filename*1*=_TEST_TES.txt\n",
2983            )
2984
2985if __name__ == '__main__':
2986    unittest.main()
2987