Skip to content

Commit fc30017

Browse files
authored
Merge pull request #3451 from dave-shawley/allow-tab-in-header
Allow horizontal tabs in header values
2 parents 2d981ec + 049cab0 commit fc30017

File tree

2 files changed

+28
-2
lines changed

2 files changed

+28
-2
lines changed

tornado/test/web_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,26 @@ def get(self):
706706
raise
707707

708708

709+
class SetHeaderHandler(RequestHandler):
710+
def get(self):
711+
# tests the validity of web.RequestHandler._VALID_HEADER_CHARS
712+
illegal_chars = [chr(o) for o in range(0, 0x20)]
713+
illegal_chars.append(chr(0x7f))
714+
illegal_chars.remove('\t')
715+
for char in illegal_chars:
716+
try:
717+
self.set_header("X-Foo", "foo" + char + "bar")
718+
raise Exception("Didn't get expected exception")
719+
except ValueError as e:
720+
if "Unsafe header value" not in str(e):
721+
raise
722+
723+
# an empty header value is valid as well
724+
self.set_header("X-Foo", "")
725+
726+
self.finish(b"ok")
727+
728+
709729
class GetArgumentHandler(RequestHandler):
710730
def prepare(self):
711731
if self.get_argument("source", None) == "query":
@@ -790,6 +810,7 @@ def get_handlers(self):
790810
url("/header_injection", HeaderInjectionHandler),
791811
url("/get_argument", GetArgumentHandler),
792812
url("/get_arguments", GetArgumentsHandler),
813+
url("/set_header", SetHeaderHandler),
793814
]
794815
return urls
795816

@@ -938,6 +959,10 @@ def test_header_injection(self):
938959
response = self.fetch("/header_injection")
939960
self.assertEqual(response.body, b"ok")
940961

962+
def test_set_header(self):
963+
response = self.fetch("/set_header")
964+
self.assertEqual(response.body, b"ok")
965+
941966
def test_get_argument(self):
942967
response = self.fetch("/get_argument?foo=bar")
943968
self.assertEqual(response.body, b"bar")

tornado/web.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ def clear_header(self, name: str) -> None:
399399
if name in self._headers:
400400
del self._headers[name]
401401

402-
_INVALID_HEADER_CHAR_RE = re.compile(r"[\x00-\x1f]")
402+
# https://www.rfc-editor.org/rfc/rfc9110#name-field-values
403+
_VALID_HEADER_CHARS = re.compile(r"[\x09\x20-\x7e\x80-\xff]*")
403404

404405
def _convert_header_value(self, value: _HeaderTypes) -> str:
405406
# Convert the input value to a str. This type check is a bit
@@ -421,7 +422,7 @@ def _convert_header_value(self, value: _HeaderTypes) -> str:
421422
raise TypeError("Unsupported header value %r" % value)
422423
# If \n is allowed into the header, it is possible to inject
423424
# additional headers or split the request.
424-
if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval):
425+
if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None:
425426
raise ValueError("Unsafe header value %r", retval)
426427
return retval
427428

0 commit comments

Comments
 (0)