From fdb23c2fe5499d26701fa34873c1cdc347adcb80 Mon Sep 17 00:00:00 2001 From: R David Murray Date: Sun, 17 May 2015 14:24:33 -0400 Subject: #20098: add mangle_from_ policy option. This defaults to True in the compat32 policy for backward compatibility, but to False for all new policies. Patch by Milan Oberkirch, with a few tweaks. --- Doc/library/email.policy.rst | 27 +++++++++++++++++++++++++++ Doc/whatsnew/3.5.rst | 6 ++++++ Lib/email/_policybase.py | 8 ++++++++ Lib/email/generator.py | 13 ++++++++----- Lib/test/test_email/test_generator.py | 22 ++++++++++++++++++++++ Lib/test/test_email/test_policy.py | 2 ++ Misc/NEWS | 3 +++ 7 files changed, 76 insertions(+), 5 deletions(-) diff --git a/Doc/library/email.policy.rst b/Doc/library/email.policy.rst index 9fadfb3..6e13186 100644 --- a/Doc/library/email.policy.rst +++ b/Doc/library/email.policy.rst @@ -187,6 +187,18 @@ added matters. To illustrate:: :const:`False` (the default), defects will be passed to the :meth:`register_defect` method. + + + .. attribute:: mangle_from\_ + + If :const:`True`, lines starting with *"From "* in the body are + escaped by putting a ``>`` in front of them. This parameter is used when + the message is being serialized by a generator. + Default: :const:`False`. + + .. versionadded:: 3.5 + The *mangle_from_* parameter. + The following :class:`Policy` method is intended to be called by code using the email library to create policy instances with custom settings: @@ -319,6 +331,13 @@ added matters. To illustrate:: :const:`compat32`, that is used as the default policy. Thus the default behavior of the email package is to maintain compatibility with Python 3.2. + The following attributes have values that are different from the + :class:`Policy` default: + + .. attribute:: mangle_from_ + + The default is ``True``. + The class provides the following concrete implementations of the abstract methods of :class:`Policy`: @@ -356,6 +375,14 @@ added matters. To illustrate:: line breaks and any (RFC invalid) binary data it may contain. +An instance of :class:`Compat32` is provided as a module constant: + +.. data:: compat32 + + An instance of :class:`Compat32`, providing backward compatibility with the + behavior of the email package in Python 3.2. + + .. note:: The documentation below describes new policies that are included in the diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst index 51a3aa3..1f8d90f 100644 --- a/Doc/whatsnew/3.5.rst +++ b/Doc/whatsnew/3.5.rst @@ -351,6 +351,12 @@ doctest email ----- +* A new policy option :attr:`~email.policy.Policy.mangle_from_` controls + whether or not lines that start with "From " in email bodies are prefixed with + a '>' character by generators. The default is ``True`` for + :attr:`~email.policy.compat32` and ``False`` for all other policies. + (Contributed by Milan Oberkirch in :issue:`20098`.) + * A new method :meth:`~email.message.Message.get_content_disposition` provides easy access to a canonical value for the :mailheader:`Content-Disposition` header (``None`` if there is no such header). (Contributed by Abhilash Raj diff --git a/Lib/email/_policybase.py b/Lib/email/_policybase.py index 8106114..c0d98a4 100644 --- a/Lib/email/_policybase.py +++ b/Lib/email/_policybase.py @@ -149,12 +149,18 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta): during serialization. None or 0 means no line wrapping is done. Default is 78. + mangle_from_ -- a flag that, when True escapes From_ lines in the + body of the message by putting a `>' in front of + them. This is used when the message is being + serialized by a generator. Default: True. + """ raise_on_defect = False linesep = '\n' cte_type = '8bit' max_line_length = 78 + mangle_from_ = False def handle_defect(self, obj, defect): """Based on policy, either raise defect or call register_defect. @@ -266,6 +272,8 @@ class Compat32(Policy): replicates the behavior of the email package version 5.1. """ + mangle_from_ = True + def _sanitize_header(self, name, value): # If the header value contains surrogates, return a Header using # the unknown-8bit charset to encode the bytes as encoded words. diff --git a/Lib/email/generator.py b/Lib/email/generator.py index 4735721..11ff16d 100644 --- a/Lib/email/generator.py +++ b/Lib/email/generator.py @@ -32,16 +32,16 @@ class Generator: # Public interface # - def __init__(self, outfp, mangle_from_=True, maxheaderlen=None, *, + def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, *, policy=None): """Create the generator for message flattening. outfp is the output file-like object for writing the message to. It must have a write() method. - Optional mangle_from_ is a flag that, when True (the default), escapes - From_ lines in the body of the message by putting a `>' in front of - them. + Optional mangle_from_ is a flag that, when True (the default if policy + is not set), escapes From_ lines in the body of the message by putting + a `>' in front of them. Optional maxheaderlen specifies the longest length for a non-continued header. When a header line is longer (in characters, with tabs @@ -56,6 +56,9 @@ class Generator: flatten method is used. """ + + if mangle_from_ is None: + mangle_from_ = True if policy is None else policy.mangle_from_ self._fp = outfp self._mangle_from_ = mangle_from_ self.maxheaderlen = maxheaderlen @@ -449,7 +452,7 @@ class DecodedGenerator(Generator): Like the Generator base class, except that non-text parts are substituted with a format string representing the part. """ - def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, fmt=None): + def __init__(self, outfp, mangle_from_=None, maxheaderlen=78, fmt=None): """Like Generator.__init__() except that an additional optional argument is allowed. diff --git a/Lib/test/test_email/test_generator.py b/Lib/test/test_email/test_generator.py index 920f870..b1cbce2 100644 --- a/Lib/test/test_email/test_generator.py +++ b/Lib/test/test_email/test_generator.py @@ -140,6 +140,28 @@ class TestGeneratorBase: g.flatten(msg, linesep='\n') self.assertEqual(s.getvalue(), self.typ(expected)) + def test_set_mangle_from_via_policy(self): + source = textwrap.dedent("""\ + Subject: test that + from is mangeld in the body! + + From time to time I write a rhyme. + """) + variants = ( + (None, True), + (policy.compat32, True), + (policy.default, False), + (policy.default.clone(mangle_from_=True), True), + ) + for p, mangle in variants: + expected = source.replace('From ', '>From ') if mangle else source + with self.subTest(policy=p, mangle_from_=mangle): + msg = self.msgmaker(self.typ(source)) + s = self.ioclass() + g = self.genclass(s, policy=p) + g.flatten(msg) + self.assertEqual(s.getvalue(), self.typ(expected)) + class TestGenerator(TestGeneratorBase, TestEmailBase): diff --git a/Lib/test/test_email/test_policy.py b/Lib/test/test_email/test_policy.py index 4b0a04e..9bb32f0 100644 --- a/Lib/test/test_email/test_policy.py +++ b/Lib/test/test_email/test_policy.py @@ -22,6 +22,7 @@ class PolicyAPITests(unittest.TestCase): 'linesep': '\n', 'cte_type': '8bit', 'raise_on_defect': False, + 'mangle_from_': True, } # These default values are the ones set on email.policy.default. # If any of these defaults change, the docs must be updated. @@ -32,6 +33,7 @@ class PolicyAPITests(unittest.TestCase): 'header_factory': email.policy.EmailPolicy.header_factory, 'refold_source': 'long', 'content_manager': email.policy.EmailPolicy.content_manager, + 'mangle_from_': False, }) # For each policy under test, we give here what we expect the defaults to diff --git a/Misc/NEWS b/Misc/NEWS index 5ae6031..586baa1 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -47,6 +47,9 @@ Core and Builtins Library ------- +- Issue #20098: New mangle_from_ policy option for email, default True + for compat32, but False for all other policies. + - Issue #24211: The email library now supports RFC 6532: it can generate headers using utf-8 instead of encoded words. -- cgit v0.12