17db96d56Sopenharmony_ciimport io
27db96d56Sopenharmony_ciimport textwrap
37db96d56Sopenharmony_ciimport unittest
47db96d56Sopenharmony_cifrom email import message_from_string, message_from_bytes
57db96d56Sopenharmony_cifrom email.message import EmailMessage
67db96d56Sopenharmony_cifrom email.generator import Generator, BytesGenerator
77db96d56Sopenharmony_cifrom email.headerregistry import Address
87db96d56Sopenharmony_cifrom email import policy
97db96d56Sopenharmony_cifrom test.test_email import TestEmailBase, parameterize
107db96d56Sopenharmony_ci
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ci@parameterize
137db96d56Sopenharmony_ciclass TestGeneratorBase:
147db96d56Sopenharmony_ci
157db96d56Sopenharmony_ci    policy = policy.default
167db96d56Sopenharmony_ci
177db96d56Sopenharmony_ci    def msgmaker(self, msg, policy=None):
187db96d56Sopenharmony_ci        policy = self.policy if policy is None else policy
197db96d56Sopenharmony_ci        return self.msgfunc(msg, policy=policy)
207db96d56Sopenharmony_ci
217db96d56Sopenharmony_ci    refold_long_expected = {
227db96d56Sopenharmony_ci        0: textwrap.dedent("""\
237db96d56Sopenharmony_ci            To: whom_it_may_concern@example.com
247db96d56Sopenharmony_ci            From: nobody_you_want_to_know@example.com
257db96d56Sopenharmony_ci            Subject: We the willing led by the unknowing are doing the
267db96d56Sopenharmony_ci             impossible for the ungrateful. We have done so much for so long with so little
277db96d56Sopenharmony_ci             we are now qualified to do anything with nothing.
287db96d56Sopenharmony_ci
297db96d56Sopenharmony_ci            None
307db96d56Sopenharmony_ci            """),
317db96d56Sopenharmony_ci        40: textwrap.dedent("""\
327db96d56Sopenharmony_ci            To: whom_it_may_concern@example.com
337db96d56Sopenharmony_ci            From:
347db96d56Sopenharmony_ci             nobody_you_want_to_know@example.com
357db96d56Sopenharmony_ci            Subject: We the willing led by the
367db96d56Sopenharmony_ci             unknowing are doing the impossible for
377db96d56Sopenharmony_ci             the ungrateful. We have done so much
387db96d56Sopenharmony_ci             for so long with so little we are now
397db96d56Sopenharmony_ci             qualified to do anything with nothing.
407db96d56Sopenharmony_ci
417db96d56Sopenharmony_ci            None
427db96d56Sopenharmony_ci            """),
437db96d56Sopenharmony_ci        20: textwrap.dedent("""\
447db96d56Sopenharmony_ci            To:
457db96d56Sopenharmony_ci             whom_it_may_concern@example.com
467db96d56Sopenharmony_ci            From:
477db96d56Sopenharmony_ci             nobody_you_want_to_know@example.com
487db96d56Sopenharmony_ci            Subject: We the
497db96d56Sopenharmony_ci             willing led by the
507db96d56Sopenharmony_ci             unknowing are doing
517db96d56Sopenharmony_ci             the impossible for
527db96d56Sopenharmony_ci             the ungrateful. We
537db96d56Sopenharmony_ci             have done so much
547db96d56Sopenharmony_ci             for so long with so
557db96d56Sopenharmony_ci             little we are now
567db96d56Sopenharmony_ci             qualified to do
577db96d56Sopenharmony_ci             anything with
587db96d56Sopenharmony_ci             nothing.
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci            None
617db96d56Sopenharmony_ci            """),
627db96d56Sopenharmony_ci        }
637db96d56Sopenharmony_ci    refold_long_expected[100] = refold_long_expected[0]
647db96d56Sopenharmony_ci
657db96d56Sopenharmony_ci    refold_all_expected = refold_long_expected.copy()
667db96d56Sopenharmony_ci    refold_all_expected[0] = (
677db96d56Sopenharmony_ci            "To: whom_it_may_concern@example.com\n"
687db96d56Sopenharmony_ci            "From: nobody_you_want_to_know@example.com\n"
697db96d56Sopenharmony_ci            "Subject: We the willing led by the unknowing are doing the "
707db96d56Sopenharmony_ci              "impossible for the ungrateful. We have done so much for "
717db96d56Sopenharmony_ci              "so long with so little we are now qualified to do anything "
727db96d56Sopenharmony_ci              "with nothing.\n"
737db96d56Sopenharmony_ci              "\n"
747db96d56Sopenharmony_ci              "None\n")
757db96d56Sopenharmony_ci    refold_all_expected[100] = (
767db96d56Sopenharmony_ci            "To: whom_it_may_concern@example.com\n"
777db96d56Sopenharmony_ci            "From: nobody_you_want_to_know@example.com\n"
787db96d56Sopenharmony_ci            "Subject: We the willing led by the unknowing are doing the "
797db96d56Sopenharmony_ci                "impossible for the ungrateful. We have\n"
807db96d56Sopenharmony_ci              " done so much for so long with so little we are now qualified "
817db96d56Sopenharmony_ci                "to do anything with nothing.\n"
827db96d56Sopenharmony_ci              "\n"
837db96d56Sopenharmony_ci              "None\n")
847db96d56Sopenharmony_ci
857db96d56Sopenharmony_ci    length_params = [n for n in refold_long_expected]
867db96d56Sopenharmony_ci
877db96d56Sopenharmony_ci    def length_as_maxheaderlen_parameter(self, n):
887db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
897db96d56Sopenharmony_ci        s = self.ioclass()
907db96d56Sopenharmony_ci        g = self.genclass(s, maxheaderlen=n, policy=self.policy)
917db96d56Sopenharmony_ci        g.flatten(msg)
927db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
937db96d56Sopenharmony_ci
947db96d56Sopenharmony_ci    def length_as_max_line_length_policy(self, n):
957db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
967db96d56Sopenharmony_ci        s = self.ioclass()
977db96d56Sopenharmony_ci        g = self.genclass(s, policy=self.policy.clone(max_line_length=n))
987db96d56Sopenharmony_ci        g.flatten(msg)
997db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
1007db96d56Sopenharmony_ci
1017db96d56Sopenharmony_ci    def length_as_maxheaderlen_parm_overrides_policy(self, n):
1027db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
1037db96d56Sopenharmony_ci        s = self.ioclass()
1047db96d56Sopenharmony_ci        g = self.genclass(s, maxheaderlen=n,
1057db96d56Sopenharmony_ci                          policy=self.policy.clone(max_line_length=10))
1067db96d56Sopenharmony_ci        g.flatten(msg)
1077db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[n]))
1087db96d56Sopenharmony_ci
1097db96d56Sopenharmony_ci    def length_as_max_line_length_with_refold_none_does_not_fold(self, n):
1107db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
1117db96d56Sopenharmony_ci        s = self.ioclass()
1127db96d56Sopenharmony_ci        g = self.genclass(s, policy=self.policy.clone(refold_source='none',
1137db96d56Sopenharmony_ci                                                      max_line_length=n))
1147db96d56Sopenharmony_ci        g.flatten(msg)
1157db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[0]))
1167db96d56Sopenharmony_ci
1177db96d56Sopenharmony_ci    def length_as_max_line_length_with_refold_all_folds(self, n):
1187db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
1197db96d56Sopenharmony_ci        s = self.ioclass()
1207db96d56Sopenharmony_ci        g = self.genclass(s, policy=self.policy.clone(refold_source='all',
1217db96d56Sopenharmony_ci                                                      max_line_length=n))
1227db96d56Sopenharmony_ci        g.flatten(msg)
1237db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(self.refold_all_expected[n]))
1247db96d56Sopenharmony_ci
1257db96d56Sopenharmony_ci    def test_crlf_control_via_policy(self):
1267db96d56Sopenharmony_ci        source = "Subject: test\r\n\r\ntest body\r\n"
1277db96d56Sopenharmony_ci        expected = source
1287db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(source))
1297db96d56Sopenharmony_ci        s = self.ioclass()
1307db96d56Sopenharmony_ci        g = self.genclass(s, policy=policy.SMTP)
1317db96d56Sopenharmony_ci        g.flatten(msg)
1327db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(expected))
1337db96d56Sopenharmony_ci
1347db96d56Sopenharmony_ci    def test_flatten_linesep_overrides_policy(self):
1357db96d56Sopenharmony_ci        source = "Subject: test\n\ntest body\n"
1367db96d56Sopenharmony_ci        expected = source
1377db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(source))
1387db96d56Sopenharmony_ci        s = self.ioclass()
1397db96d56Sopenharmony_ci        g = self.genclass(s, policy=policy.SMTP)
1407db96d56Sopenharmony_ci        g.flatten(msg, linesep='\n')
1417db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(expected))
1427db96d56Sopenharmony_ci
1437db96d56Sopenharmony_ci    def test_set_mangle_from_via_policy(self):
1447db96d56Sopenharmony_ci        source = textwrap.dedent("""\
1457db96d56Sopenharmony_ci            Subject: test that
1467db96d56Sopenharmony_ci             from is mangled in the body!
1477db96d56Sopenharmony_ci
1487db96d56Sopenharmony_ci            From time to time I write a rhyme.
1497db96d56Sopenharmony_ci            """)
1507db96d56Sopenharmony_ci        variants = (
1517db96d56Sopenharmony_ci            (None, True),
1527db96d56Sopenharmony_ci            (policy.compat32, True),
1537db96d56Sopenharmony_ci            (policy.default, False),
1547db96d56Sopenharmony_ci            (policy.default.clone(mangle_from_=True), True),
1557db96d56Sopenharmony_ci            )
1567db96d56Sopenharmony_ci        for p, mangle in variants:
1577db96d56Sopenharmony_ci            expected = source.replace('From ', '>From ') if mangle else source
1587db96d56Sopenharmony_ci            with self.subTest(policy=p, mangle_from_=mangle):
1597db96d56Sopenharmony_ci                msg = self.msgmaker(self.typ(source))
1607db96d56Sopenharmony_ci                s = self.ioclass()
1617db96d56Sopenharmony_ci                g = self.genclass(s, policy=p)
1627db96d56Sopenharmony_ci                g.flatten(msg)
1637db96d56Sopenharmony_ci                self.assertEqual(s.getvalue(), self.typ(expected))
1647db96d56Sopenharmony_ci
1657db96d56Sopenharmony_ci    def test_compat32_max_line_length_does_not_fold_when_none(self):
1667db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(self.refold_long_expected[0]))
1677db96d56Sopenharmony_ci        s = self.ioclass()
1687db96d56Sopenharmony_ci        g = self.genclass(s, policy=policy.compat32.clone(max_line_length=None))
1697db96d56Sopenharmony_ci        g.flatten(msg)
1707db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(self.refold_long_expected[0]))
1717db96d56Sopenharmony_ci
1727db96d56Sopenharmony_ci    def test_rfc2231_wrapping(self):
1737db96d56Sopenharmony_ci        # This is pretty much just to make sure we don't have an infinite
1747db96d56Sopenharmony_ci        # loop; I don't expect anyone to hit this in the field.
1757db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(textwrap.dedent("""\
1767db96d56Sopenharmony_ci            To: nobody
1777db96d56Sopenharmony_ci            Content-Disposition: attachment;
1787db96d56Sopenharmony_ci             filename="afilenamelongenoghtowraphere"
1797db96d56Sopenharmony_ci
1807db96d56Sopenharmony_ci            None
1817db96d56Sopenharmony_ci            """)))
1827db96d56Sopenharmony_ci        expected = textwrap.dedent("""\
1837db96d56Sopenharmony_ci            To: nobody
1847db96d56Sopenharmony_ci            Content-Disposition: attachment;
1857db96d56Sopenharmony_ci             filename*0*=us-ascii''afilename;
1867db96d56Sopenharmony_ci             filename*1*=longenoghtowraphere
1877db96d56Sopenharmony_ci
1887db96d56Sopenharmony_ci            None
1897db96d56Sopenharmony_ci            """)
1907db96d56Sopenharmony_ci        s = self.ioclass()
1917db96d56Sopenharmony_ci        g = self.genclass(s, policy=self.policy.clone(max_line_length=33))
1927db96d56Sopenharmony_ci        g.flatten(msg)
1937db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(expected))
1947db96d56Sopenharmony_ci
1957db96d56Sopenharmony_ci    def test_rfc2231_wrapping_switches_to_default_len_if_too_narrow(self):
1967db96d56Sopenharmony_ci        # This is just to make sure we don't have an infinite loop; I don't
1977db96d56Sopenharmony_ci        # expect anyone to hit this in the field, so I'm not bothering to make
1987db96d56Sopenharmony_ci        # the result optimal (the encoding isn't needed).
1997db96d56Sopenharmony_ci        msg = self.msgmaker(self.typ(textwrap.dedent("""\
2007db96d56Sopenharmony_ci            To: nobody
2017db96d56Sopenharmony_ci            Content-Disposition: attachment;
2027db96d56Sopenharmony_ci             filename="afilenamelongenoghtowraphere"
2037db96d56Sopenharmony_ci
2047db96d56Sopenharmony_ci            None
2057db96d56Sopenharmony_ci            """)))
2067db96d56Sopenharmony_ci        expected = textwrap.dedent("""\
2077db96d56Sopenharmony_ci            To: nobody
2087db96d56Sopenharmony_ci            Content-Disposition:
2097db96d56Sopenharmony_ci             attachment;
2107db96d56Sopenharmony_ci             filename*0*=us-ascii''afilenamelongenoghtowraphere
2117db96d56Sopenharmony_ci
2127db96d56Sopenharmony_ci            None
2137db96d56Sopenharmony_ci            """)
2147db96d56Sopenharmony_ci        s = self.ioclass()
2157db96d56Sopenharmony_ci        g = self.genclass(s, policy=self.policy.clone(max_line_length=20))
2167db96d56Sopenharmony_ci        g.flatten(msg)
2177db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), self.typ(expected))
2187db96d56Sopenharmony_ci
2197db96d56Sopenharmony_ci
2207db96d56Sopenharmony_ciclass TestGenerator(TestGeneratorBase, TestEmailBase):
2217db96d56Sopenharmony_ci
2227db96d56Sopenharmony_ci    msgfunc = staticmethod(message_from_string)
2237db96d56Sopenharmony_ci    genclass = Generator
2247db96d56Sopenharmony_ci    ioclass = io.StringIO
2257db96d56Sopenharmony_ci    typ = str
2267db96d56Sopenharmony_ci
2277db96d56Sopenharmony_ci
2287db96d56Sopenharmony_ciclass TestBytesGenerator(TestGeneratorBase, TestEmailBase):
2297db96d56Sopenharmony_ci
2307db96d56Sopenharmony_ci    msgfunc = staticmethod(message_from_bytes)
2317db96d56Sopenharmony_ci    genclass = BytesGenerator
2327db96d56Sopenharmony_ci    ioclass = io.BytesIO
2337db96d56Sopenharmony_ci    typ = lambda self, x: x.encode('ascii')
2347db96d56Sopenharmony_ci
2357db96d56Sopenharmony_ci    def test_cte_type_7bit_handles_unknown_8bit(self):
2367db96d56Sopenharmony_ci        source = ("Subject: Maintenant je vous présente mon "
2377db96d56Sopenharmony_ci                 "collègue\n\n").encode('utf-8')
2387db96d56Sopenharmony_ci        expected = ('Subject: Maintenant je vous =?unknown-8bit?q?'
2397db96d56Sopenharmony_ci                    'pr=C3=A9sente_mon_coll=C3=A8gue?=\n\n').encode('ascii')
2407db96d56Sopenharmony_ci        msg = message_from_bytes(source)
2417db96d56Sopenharmony_ci        s = io.BytesIO()
2427db96d56Sopenharmony_ci        g = BytesGenerator(s, policy=self.policy.clone(cte_type='7bit'))
2437db96d56Sopenharmony_ci        g.flatten(msg)
2447db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), expected)
2457db96d56Sopenharmony_ci
2467db96d56Sopenharmony_ci    def test_cte_type_7bit_transforms_8bit_cte(self):
2477db96d56Sopenharmony_ci        source = textwrap.dedent("""\
2487db96d56Sopenharmony_ci            From: foo@bar.com
2497db96d56Sopenharmony_ci            To: Dinsdale
2507db96d56Sopenharmony_ci            Subject: Nudge nudge, wink, wink
2517db96d56Sopenharmony_ci            Mime-Version: 1.0
2527db96d56Sopenharmony_ci            Content-Type: text/plain; charset="latin-1"
2537db96d56Sopenharmony_ci            Content-Transfer-Encoding: 8bit
2547db96d56Sopenharmony_ci
2557db96d56Sopenharmony_ci            oh là là, know what I mean, know what I mean?
2567db96d56Sopenharmony_ci            """).encode('latin1')
2577db96d56Sopenharmony_ci        msg = message_from_bytes(source)
2587db96d56Sopenharmony_ci        expected =  textwrap.dedent("""\
2597db96d56Sopenharmony_ci            From: foo@bar.com
2607db96d56Sopenharmony_ci            To: Dinsdale
2617db96d56Sopenharmony_ci            Subject: Nudge nudge, wink, wink
2627db96d56Sopenharmony_ci            Mime-Version: 1.0
2637db96d56Sopenharmony_ci            Content-Type: text/plain; charset="iso-8859-1"
2647db96d56Sopenharmony_ci            Content-Transfer-Encoding: quoted-printable
2657db96d56Sopenharmony_ci
2667db96d56Sopenharmony_ci            oh l=E0 l=E0, know what I mean, know what I mean?
2677db96d56Sopenharmony_ci            """).encode('ascii')
2687db96d56Sopenharmony_ci        s = io.BytesIO()
2697db96d56Sopenharmony_ci        g = BytesGenerator(s, policy=self.policy.clone(cte_type='7bit',
2707db96d56Sopenharmony_ci                                                       linesep='\n'))
2717db96d56Sopenharmony_ci        g.flatten(msg)
2727db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), expected)
2737db96d56Sopenharmony_ci
2747db96d56Sopenharmony_ci    def test_smtputf8_policy(self):
2757db96d56Sopenharmony_ci        msg = EmailMessage()
2767db96d56Sopenharmony_ci        msg['From'] = "Páolo <főo@bar.com>"
2777db96d56Sopenharmony_ci        msg['To'] = 'Dinsdale'
2787db96d56Sopenharmony_ci        msg['Subject'] = 'Nudge nudge, wink, wink \u1F609'
2797db96d56Sopenharmony_ci        msg.set_content("oh là là, know what I mean, know what I mean?")
2807db96d56Sopenharmony_ci        expected = textwrap.dedent("""\
2817db96d56Sopenharmony_ci            From: Páolo <főo@bar.com>
2827db96d56Sopenharmony_ci            To: Dinsdale
2837db96d56Sopenharmony_ci            Subject: Nudge nudge, wink, wink \u1F609
2847db96d56Sopenharmony_ci            Content-Type: text/plain; charset="utf-8"
2857db96d56Sopenharmony_ci            Content-Transfer-Encoding: 8bit
2867db96d56Sopenharmony_ci            MIME-Version: 1.0
2877db96d56Sopenharmony_ci
2887db96d56Sopenharmony_ci            oh là là, know what I mean, know what I mean?
2897db96d56Sopenharmony_ci            """).encode('utf-8').replace(b'\n', b'\r\n')
2907db96d56Sopenharmony_ci        s = io.BytesIO()
2917db96d56Sopenharmony_ci        g = BytesGenerator(s, policy=policy.SMTPUTF8)
2927db96d56Sopenharmony_ci        g.flatten(msg)
2937db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), expected)
2947db96d56Sopenharmony_ci
2957db96d56Sopenharmony_ci    def test_smtp_policy(self):
2967db96d56Sopenharmony_ci        msg = EmailMessage()
2977db96d56Sopenharmony_ci        msg["From"] = Address(addr_spec="foo@bar.com", display_name="Páolo")
2987db96d56Sopenharmony_ci        msg["To"] = Address(addr_spec="bar@foo.com", display_name="Dinsdale")
2997db96d56Sopenharmony_ci        msg["Subject"] = "Nudge nudge, wink, wink"
3007db96d56Sopenharmony_ci        msg.set_content("oh boy, know what I mean, know what I mean?")
3017db96d56Sopenharmony_ci        expected = textwrap.dedent("""\
3027db96d56Sopenharmony_ci            From: =?utf-8?q?P=C3=A1olo?= <foo@bar.com>
3037db96d56Sopenharmony_ci            To: Dinsdale <bar@foo.com>
3047db96d56Sopenharmony_ci            Subject: Nudge nudge, wink, wink
3057db96d56Sopenharmony_ci            Content-Type: text/plain; charset="utf-8"
3067db96d56Sopenharmony_ci            Content-Transfer-Encoding: 7bit
3077db96d56Sopenharmony_ci            MIME-Version: 1.0
3087db96d56Sopenharmony_ci
3097db96d56Sopenharmony_ci            oh boy, know what I mean, know what I mean?
3107db96d56Sopenharmony_ci            """).encode().replace(b"\n", b"\r\n")
3117db96d56Sopenharmony_ci        s = io.BytesIO()
3127db96d56Sopenharmony_ci        g = BytesGenerator(s, policy=policy.SMTP)
3137db96d56Sopenharmony_ci        g.flatten(msg)
3147db96d56Sopenharmony_ci        self.assertEqual(s.getvalue(), expected)
3157db96d56Sopenharmony_ci
3167db96d56Sopenharmony_ci
3177db96d56Sopenharmony_ciif __name__ == '__main__':
3187db96d56Sopenharmony_ci    unittest.main()
319