mirror of
https://github.com/python/cpython.git
synced 2026-02-05 09:25:35 +00:00
gh-144125: email: verify headers are sound in BytesGenerator
Co-authored-by: Denis Ledoux <dle@odoo.com> Co-authored-by: Denis Ledoux <5822488+beledouxdenis@users.noreply.github.com> Co-authored-by: Petr Viktorin <302922+encukou@users.noreply.github.com> Co-authored-by: Bas Bloemsaat <1586868+basbloemsaat@users.noreply.github.com>
This commit is contained in:
parent
f3dd0cae6c
commit
052e55e7d4
4 changed files with 23 additions and 3 deletions
|
|
@ -22,6 +22,7 @@
|
|||
NLCRE = re.compile(r'\r\n|\r|\n')
|
||||
fcre = re.compile(r'^From ', re.MULTILINE)
|
||||
NEWLINE_WITHOUT_FWSP = re.compile(r'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
|
||||
NEWLINE_WITHOUT_FWSP_BYTES = re.compile(br'\r\n[^ \t]|\r[^ \n\t]|\n[^ \t]')
|
||||
|
||||
|
||||
class Generator:
|
||||
|
|
@ -429,7 +430,16 @@ def _write_headers(self, msg):
|
|||
# This is almost the same as the string version, except for handling
|
||||
# strings with 8bit bytes.
|
||||
for h, v in msg.raw_items():
|
||||
self._fp.write(self.policy.fold_binary(h, v))
|
||||
folded = self.policy.fold_binary(h, v)
|
||||
if self.policy.verify_generated_headers:
|
||||
linesep = self.policy.linesep.encode()
|
||||
if not folded.endswith(linesep):
|
||||
raise HeaderWriteError(
|
||||
f'folded header does not end with {linesep!r}: {folded!r}')
|
||||
if NEWLINE_WITHOUT_FWSP_BYTES.search(folded.removesuffix(linesep)):
|
||||
raise HeaderWriteError(
|
||||
f'folded header contains newline: {folded!r}')
|
||||
self._fp.write(folded)
|
||||
# A blank line always separates headers from body
|
||||
self.write(self._NL)
|
||||
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ def test_flatten_unicode_linesep(self):
|
|||
self.assertEqual(s.getvalue(), self.typ(expected))
|
||||
|
||||
def test_verify_generated_headers(self):
|
||||
"""gh-121650: by default the generator prevents header injection"""
|
||||
# gh-121650: by default the generator prevents header injection
|
||||
class LiteralHeader(str):
|
||||
name = 'Header'
|
||||
def fold(self, **kwargs):
|
||||
|
|
@ -334,6 +334,8 @@ def fold(self, **kwargs):
|
|||
|
||||
with self.assertRaises(email.errors.HeaderWriteError):
|
||||
message.as_string()
|
||||
with self.assertRaises(email.errors.HeaderWriteError):
|
||||
message.as_bytes()
|
||||
|
||||
|
||||
class TestBytesGenerator(TestGeneratorBase, TestEmailBase):
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ def test_short_maxlen_error(self):
|
|||
policy.fold("Subject", subject)
|
||||
|
||||
def test_verify_generated_headers(self):
|
||||
"""Turning protection off allows header injection"""
|
||||
# Turning protection off allows header injection
|
||||
policy = email.policy.default.clone(verify_generated_headers=False)
|
||||
for text in (
|
||||
'Header: Value\r\nBad: Injection\r\n',
|
||||
|
|
@ -319,6 +319,10 @@ def fold(self, **kwargs):
|
|||
message.as_string(),
|
||||
f"{text}\nBody",
|
||||
)
|
||||
self.assertEqual(
|
||||
message.as_bytes(),
|
||||
f"{text}\nBody".encode(),
|
||||
)
|
||||
|
||||
# XXX: Need subclassing tests.
|
||||
# For adding subclassed objects, make sure the usual rules apply (subclass
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
:mod:`~email.generator.BytesGenerator` will now refuse to serialize (write) headers
|
||||
that are unsafely folded or delimited; see
|
||||
:attr:`~email.policy.Policy.verify_generated_headers`. (Contributed by Bas
|
||||
Bloemsaat and Petr Viktorin in :gh:`121650`).
|
||||
Loading…
Add table
Add a link
Reference in a new issue