From b488f338cf058f46cbf0255023ca1c1669b0eb44 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 12 Feb 2026 19:40:42 +0100 Subject: [PATCH 1/3] gh-135906: Test more internal headers in test_cext/test_cppext (#144751) --- Lib/test/test_cext/extension.c | 5 ++++- Lib/test/test_cppext/extension.cpp | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index a6b30fd627fe99..7555b78f18c2e6 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -15,9 +15,11 @@ #ifdef TEST_INTERNAL_C_API // gh-135906: Check for compiler warnings in the internal C API. - // - Cython uses pycore_frame.h. + // - Cython uses pycore_critical_section.h, pycore_frame.h and + // pycore_template.h. // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and // pycore_interpframe.h. +# include "internal/pycore_critical_section.h" # include "internal/pycore_frame.h" # include "internal/pycore_gc.h" # include "internal/pycore_interp.h" @@ -25,6 +27,7 @@ # include "internal/pycore_interpframe_structs.h" # include "internal/pycore_object.h" # include "internal/pycore_pystate.h" +# include "internal/pycore_template.h" #endif #ifndef MODULE_NAME diff --git a/Lib/test/test_cppext/extension.cpp b/Lib/test/test_cppext/extension.cpp index 92c4645039a03b..4db63df94f5233 100644 --- a/Lib/test/test_cppext/extension.cpp +++ b/Lib/test/test_cppext/extension.cpp @@ -15,11 +15,20 @@ #ifdef TEST_INTERNAL_C_API // gh-135906: Check for compiler warnings in the internal C API + // - Cython uses pycore_critical_section.h, pycore_frame.h and + // pycore_template.h. + // - greenlet uses pycore_frame.h, pycore_interpframe_structs.h and + // pycore_interpframe.h. # include "internal/pycore_frame.h" +# include "internal/pycore_interpframe_structs.h" +# include "internal/pycore_template.h" + // mimalloc emits compiler warnings on Windows. # if !defined(MS_WINDOWS) # include "internal/pycore_backoff.h" # include "internal/pycore_cell.h" +# include "internal/pycore_critical_section.h" +# include "internal/pycore_interpframe.h" # endif #endif From 66da7bf6fe7b81e3ecc9c0a25bd47d4616c8d1a6 Mon Sep 17 00:00:00 2001 From: Seth Michael Larson Date: Thu, 12 Feb 2026 14:40:21 -0600 Subject: [PATCH 2/3] gh-143916: Allow HTAB in wsgiref header values Co-authored-by: Victor Stinner --- Lib/test/test_wsgiref.py | 20 +++++++++++++------- Lib/wsgiref/headers.py | 35 ++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_wsgiref.py b/Lib/test/test_wsgiref.py index d24aaab1327409..a7a5c5ba33d493 100644 --- a/Lib/test/test_wsgiref.py +++ b/Lib/test/test_wsgiref.py @@ -504,14 +504,20 @@ def testExtras(self): ) def testRaisesControlCharacters(self): - headers = Headers() for c0 in control_characters_c0(): - self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") - self.assertRaises(ValueError, headers.__setitem__, "key", f"val{c0}") - self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") - self.assertRaises(ValueError, headers.add_header, "key", f"val{c0}", param="param") - self.assertRaises(ValueError, headers.add_header, "key", "val", param=f"param{c0}") - + with self.subTest(c0): + headers = Headers() + self.assertRaises(ValueError, headers.__setitem__, f"key{c0}", "val") + self.assertRaises(ValueError, headers.add_header, f"key{c0}", "val", param="param") + # 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}") + 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}") class ErrorHandler(BaseCGIHandler): """Simple handler subclass for testing BaseHandler""" diff --git a/Lib/wsgiref/headers.py b/Lib/wsgiref/headers.py index e180a623cb2c30..eb6ea6a412dcc9 100644 --- a/Lib/wsgiref/headers.py +++ b/Lib/wsgiref/headers.py @@ -9,7 +9,11 @@ # existence of which force quoting of the parameter value. import re tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]') -_control_chars_re = re.compile(r'[\x00-\x1F\x7F]') +# Disallowed characters for headers and values. +# HTAB (\x09) is allowed in header values, but +# not in header names. (RFC 9110 Section 5.5) +_name_disallowed_re = re.compile(r'[\x00-\x1F\x7F]') +_value_disallowed_re = re.compile(r'[\x00-\x08\x0A-\x1F\x7F]') def _formatparam(param, value=None, quote=1): """Convenience function to format and return a key=value pair. @@ -36,13 +40,14 @@ def __init__(self, headers=None): self._headers = headers if __debug__: for k, v in headers: - self._convert_string_type(k) - self._convert_string_type(v) + self._convert_string_type(k, name=True) + self._convert_string_type(v, name=False) - def _convert_string_type(self, value): + def _convert_string_type(self, value, *, name): """Convert/check value type.""" if type(value) is str: - if _control_chars_re.search(value): + regex = (_name_disallowed_re if name else _value_disallowed_re) + if regex.search(value): raise ValueError("Control characters not allowed in headers") return value raise AssertionError("Header names/values must be" @@ -56,14 +61,14 @@ def __setitem__(self, name, val): """Set the value of a header.""" del self[name] self._headers.append( - (self._convert_string_type(name), self._convert_string_type(val))) + (self._convert_string_type(name, name=True), self._convert_string_type(val, name=False))) def __delitem__(self,name): """Delete all occurrences of a header, if present. Does *not* raise an exception if the header is missing. """ - name = self._convert_string_type(name.lower()) + name = self._convert_string_type(name.lower(), name=True) self._headers[:] = [kv for kv in self._headers if kv[0].lower() != name] def __getitem__(self,name): @@ -90,13 +95,13 @@ def get_all(self, name): fields deleted and re-inserted are always appended to the header list. If no fields exist with the given name, returns an empty list. """ - name = self._convert_string_type(name.lower()) + name = self._convert_string_type(name.lower(), name=True) return [kv[1] for kv in self._headers if kv[0].lower()==name] def get(self,name,default=None): """Get the first header value for 'name', or return 'default'""" - name = self._convert_string_type(name.lower()) + name = self._convert_string_type(name.lower(), name=True) for k,v in self._headers: if k.lower()==name: return v @@ -151,8 +156,8 @@ def setdefault(self,name,value): and value 'value'.""" result = self.get(name) if result is None: - self._headers.append((self._convert_string_type(name), - self._convert_string_type(value))) + self._headers.append((self._convert_string_type(name, name=True), + self._convert_string_type(value, name=False))) return value else: return result @@ -175,13 +180,13 @@ def add_header(self, _name, _value, **_params): """ parts = [] if _value is not None: - _value = self._convert_string_type(_value) + _value = self._convert_string_type(_value, name=False) parts.append(_value) for k, v in _params.items(): - k = self._convert_string_type(k) + k = self._convert_string_type(k, name=True) if v is None: parts.append(k.replace('_', '-')) else: - v = self._convert_string_type(v) + v = self._convert_string_type(v, name=False) parts.append(_formatparam(k.replace('_', '-'), v)) - self._headers.append((self._convert_string_type(_name), "; ".join(parts))) + self._headers.append((self._convert_string_type(_name, name=True), "; ".join(parts))) From 945bf8ce1bf7ee3881752c2ecc129e35ab818477 Mon Sep 17 00:00:00 2001 From: Robsdedude Date: Fri, 13 Feb 2026 00:15:23 +0100 Subject: [PATCH 3/3] gh-144706: Warn against using synchronization primitives within signal handlers (GH-144736) --- Doc/library/signal.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst index d85d7138911016..c3fe9943ba9d76 100644 --- a/Doc/library/signal.rst +++ b/Doc/library/signal.rst @@ -68,6 +68,11 @@ the synchronization primitives from the :mod:`threading` module instead. Besides, only the main thread of the main interpreter is allowed to set a new signal handler. +.. warning:: + + Synchronization primitives such as :class:`threading.Lock` should not be used + within signal handlers. Doing so can lead to unexpected deadlocks. + Module contents ---------------