diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index 32ef0ccf4e638d..b6a492781b93fc 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -510,15 +510,28 @@ def testRaisesControlCharacters(self): headers = Headers() self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") + self.assertRaises(ValueError, Headers, [(f"key{c0}", "val")]) # HTAB (\x09) is allowed in values, not names. if c0 == "\t": headers["key"] = f"val{c0}" headers.add_header("key", f"val{c0}") headers.setdefault(f"key", f"val{c0}") + Headers([("key", f"val{c0}")]) else: self.assertRaises(ValueError, headers.__setitem__, "key", f"val{c0}") self.assertRaises(ValueError, headers.add_header, "key", f"val{c0}", param="param") self.assertRaises(ValueError, headers.add_header, "key", "val", param=f"param{c0}") + self.assertRaises(ValueError, Headers, [("key", f"val{c0}")]) + + def testConstructorValidatesWithoutDebug(self): + # The constructor must reject control characters even under -O, where + # __debug__-guarded code is skipped; the headers it is given are stored + # and written to the response unchanged. + from test.support.script_helper import assert_python_failure + code = ("from wsgiref.headers import Headers\n" + "Headers([('Foo', 'bar\\r\\nSet-Cookie: evil')])\n") + rc, out, err = assert_python_failure('-O', '-c', code) + self.assertIn(b'ValueError', err) class ErrorHandler(BaseCGIHandler): """Simple handler subclass for testing BaseHandler""" diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py index eb6ea6a412dcc9..843c22822eb191 100644 --- a/Lib/wsgiref/headers.py +++ b/Lib/wsgiref/headers.py @@ -38,10 +38,9 @@ def __init__(self, headers=None): if type(headers) is not list: raise TypeError("Headers must be a list of name/value tuples") self._headers = headers - if __debug__: - for k, v in headers: - self._convert_string_type(k, name=True) - self._convert_string_type(v, name=False) + for k, v in headers: + self._convert_string_type(k, name=True) + self._convert_string_type(v, name=False) def _convert_string_type(self, value, *, name): """Convert/check value type.""" diff --git a/Misc/NEWS.d/next/Security/2026-06-01-12-30-00.gh-issue-150726.Wk4Tn7.rst b/Misc/NEWS.d/next/Security/2026-06-01-12-30-00.gh-issue-150726.Wk4Tn7.rst new file mode 100644 index 00000000000000..0451d715b87aef --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-06-01-12-30-00.gh-issue-150726.Wk4Tn7.rst @@ -0,0 +1,3 @@ +:class:`wsgiref.headers.Headers` now rejects control characters in header +names and values passed to its constructor even when Python is run with +:option:`-O`, where the check was previously skipped.