]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
httputil: Enforce RFC rules for header names 3469/head
authorBen Darnell <ben@bendarnell.com>
Thu, 20 Mar 2025 21:59:56 +0000 (17:59 -0400)
committerBen Darnell <ben@bendarnell.com>
Thu, 20 Mar 2025 21:59:56 +0000 (17:59 -0400)
Previously we allowed nearly any string in header names, but
RFC 9110 restricts them to certain printable ASCII characters.

Fixes #3310
Fixes #2790

tornado/httputil.py
tornado/test/httputil_test.py

index 899d76aeb5f9e1e85585caf2df9b2b80bdb48ae9..536b14551f9bda05cbe0d29dbdb6b542bbda90f2 100644 (file)
@@ -71,6 +71,8 @@ else:
 # To be used with str.strip() and related methods.
 HTTP_WHITESPACE = " \t"
 
+HTTP_TOKEN_RE = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")
+
 
 @lru_cache(1000)
 def _normalize_header(name: str) -> str:
@@ -143,6 +145,8 @@ class HTTPHeaders(StrMutableMapping):
 
     def add(self, name: str, value: str) -> None:
         """Adds a new value for the given key."""
+        if not HTTP_TOKEN_RE.match(name):
+            raise HTTPInputError("Invalid header name %r" % name)
         norm_name = _normalize_header(name)
         self._last_key = norm_name
         if norm_name in self:
@@ -859,7 +863,7 @@ def parse_multipart_form_data(
 
 
 def format_timestamp(
-    ts: Union[int, float, tuple, time.struct_time, datetime.datetime]
+    ts: Union[int, float, tuple, time.struct_time, datetime.datetime],
 ) -> str:
     """Formats a timestamp in the format used by HTTP.
 
index 46a28bc69cd6953c564c67709e3f34a82d75a885..53ae46076fcb943f7717a9cdf6db1f8f8a42cb90 100644 (file)
@@ -409,6 +409,22 @@ Foo: even
         headers2 = HTTPHeaders.parse(str(headers))
         self.assertEqual(headers, headers2)
 
+    def test_invalid_header_names(self):
+        invalid_names = [
+            "",
+            "foo bar",
+            "foo\tbar",
+            "foo\nbar",
+            "foo\x00bar",
+            "foo ",
+            " foo",
+            "é",
+        ]
+        for name in invalid_names:
+            headers = HTTPHeaders()
+            with self.assertRaises(HTTPInputError):
+                headers.add(name, "bar")
+
 
 class FormatTimestampTest(unittest.TestCase):
     # Make sure that all the input types are supported.