Skip to content

Commit 05ee2e6

Browse files
committed
support X-Forwarded-Context header when xheaders are trusted
allows proxies that forward /prefix/foo -> /foo to be handled under their original url
1 parent 4f1ebe4 commit 05ee2e6

File tree

2 files changed

+31
-1
lines changed

2 files changed

+31
-1
lines changed

tornado/httpserver.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,8 +378,29 @@ def headers_received(
378378
# TODO: either make context an official part of the
379379
# HTTPConnection interface or figure out some other way to do this.
380380
self.connection.context._apply_xheaders(headers) # type: ignore
381+
start_line, headers = self.apply_forwarded_context(start_line, headers)
382+
381383
return self.delegate.headers_received(start_line, headers)
382384

385+
def apply_forwarded_context(
386+
self,
387+
start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
388+
headers: httputil.HTTPHeaders,
389+
) -> Tuple[
390+
Union[httputil.RequestStartLine, httputil.ResponseStartLine],
391+
httputil.HTTPHeaders,
392+
]:
393+
"""Apply X-Forwarded-Context header to requested uri"""
394+
if isinstance(start_line, httputil.RequestStartLine):
395+
# get path from X-Forwarded-Context
396+
proxy_path = headers.get("X-Forwarded-Context", None)
397+
if proxy_path:
398+
# preserve only the path part
399+
path = proxy_path.split("?", 1)[0]
400+
start_line = start_line._replace(path=path)
401+
402+
return start_line, headers
403+
383404
def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
384405
return self.delegate.data_received(chunk)
385406

tornado/test/httpserver_test.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def finish(self):
7373

7474
class HandlerBaseTestCase(AsyncHTTPTestCase):
7575
def get_app(self):
76-
return Application([("/", self.__class__.Handler)])
76+
return Application([("/.*", self.__class__.Handler)])
7777

7878
def fetch_json(self, *args, **kwargs):
7979
response = self.fetch(*args, **kwargs)
@@ -554,6 +554,7 @@ def get(self):
554554
dict(
555555
remote_ip=self.request.remote_ip,
556556
remote_protocol=self.request.protocol,
557+
path=self.request.path,
557558
)
558559
)
559560

@@ -640,6 +641,14 @@ def test_scheme_headers(self):
640641
self.fetch_json("/", headers=bad_forwarded)["remote_protocol"], "http"
641642
)
642643

644+
def test_forwarded_context(self):
645+
self.assertEqual(self.fetch_json("/")["path"], "/")
646+
647+
self.assertEqual(
648+
self.fetch_json("/", headers={"X-Forwarded-Context": "/prefix"})["path"],
649+
"/prefix",
650+
)
651+
643652

644653
class SSLXHeaderTest(AsyncHTTPSTestCase, HandlerBaseTestCase):
645654
def get_app(self):

0 commit comments

Comments
 (0)