From bcd5e158b5bfbe5ac5c18b92e721f165effaf41e Mon Sep 17 00:00:00 2001 From: X6 M Date: Sun, 8 Nov 2026 15:27:58 +0100 Subject: [PATCH] Parse repeated Tornado headers as separate values (fixes #1109) HTTPHeaders.get() joins repeated headers with commas, so a multi-value field such as List received the joined string split into characters. Use HTTPHeaders.get_list() for multi-value keys instead. --- AUTHORS.rst | 1 + CHANGELOG.rst | 6 ++++++ src/webargs/tornadoparser.py | 6 +++++- tests/test_tornadoparser.py | 15 +++++++++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index f487ea23..568d4821 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -60,3 +60,4 @@ Contributors (chronological) * Tumuer `@un4gt `_ * Marat Sharafutdinov `@decaz `_ * Felix Engelmann `@dorthrithil `_ +* X6M-Work `@X6M-Work `_ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dc165b6f..38335f36 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -34,6 +34,12 @@ Other changes: * Drop support for aiohttp < 3.13.0 (:pr:`1023`). * Drop support for marshmallow < 4.0.0 (:pr:`1023`). +Bug fixes: + +* `TornadoParser `: Repeated headers are + parsed as separate values for multi-value fields such as ``List``, instead of + being split into characters of the comma-joined header (:issue:`1109`). + 8.7.1 (2025-10-29) ****************** diff --git a/src/webargs/tornadoparser.py b/src/webargs/tornadoparser.py index 1b0c73d8..fcdeaf32 100644 --- a/src/webargs/tornadoparser.py +++ b/src/webargs/tornadoparser.py @@ -23,7 +23,7 @@ def get(self, args): import tornado.concurrent import tornado.web from tornado.escape import _unicode -from tornado.httputil import HTTPServerRequest +from tornado.httputil import HTTPHeaders, HTTPServerRequest from webargs import core from webargs.multidictproxy import MultiDictProxy @@ -55,6 +55,10 @@ def __getitem__(self, key: str) -> typing.Any: if value is core.missing: return core.missing if key in self.multiple_keys: + # HTTPHeaders.get() joins repeated headers with commas, + # get_list() returns each value separately + if isinstance(self.data, HTTPHeaders): + value = self.data.get_list(key) return [ _unicode(v) if isinstance(v, (str, bytes)) else v for v in value ] diff --git a/tests/test_tornadoparser.py b/tests/test_tornadoparser.py index ee92eadd..b842fc7f 100644 --- a/tests/test_tornadoparser.py +++ b/tests/test_tornadoparser.py @@ -171,6 +171,21 @@ def test_it_should_get_multiple_values(self): result = parser.load_headers(request, author_schema) assert result["works"] == ["Prometheus Bound", "Prometheus Unbound"] + def test_it_should_get_repeated_headers_as_multiple_values(self): + headers = tornado.httputil.HTTPHeaders() + headers.add("works", "Prometheus Bound") + headers.add("works", "Prometheus Unbound") + request = make_request(headers=headers) + result = parser.load_headers(request, author_schema) + assert result["works"] == ["Prometheus Bound", "Prometheus Unbound"] + + def test_it_should_get_a_single_header_as_one_value_list(self): + headers = tornado.httputil.HTTPHeaders() + headers.add("works", "Prometheus Bound") + request = make_request(headers=headers) + result = parser.load_headers(request, author_schema) + assert result["works"] == ["Prometheus Bound"] + def test_it_should_return_missing_if_not_present(self): request = make_request() result = parser.load_headers(request, author_schema)