Skip to content

Commit

Permalink
#20098: add mangle_from_ policy option.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bitdancer committed May 17, 2015
1 parent 224ef3e commit fdb23c2
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 5 deletions.
27 changes: 27 additions & 0 deletions Doc/library/email.policy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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`:

Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.5.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Lib/email/_policybase.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 8 additions & 5 deletions Lib/email/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -56,6 +56,9 @@ def __init__(self, outfp, mangle_from_=True, maxheaderlen=None, *,
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
Expand Down Expand Up @@ -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.
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_email/test_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,28 @@ def test_flatten_linesep_overrides_policy(self):
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):

Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_email/test_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down

0 comments on commit fdb23c2

Please sign in to comment.