From 53b74f9caaf492b005cbdbb93d20064851341cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Thu, 8 Sep 2016 15:18:09 +0200 Subject: [PATCH] Only construct HTTP_HOST header from SERVER_NAME and _PORT if it's unset If HTTP_HOST is set, following PEP333 it takes precedence over SERVER_NAME and _PORT, so we set those from it. --- src/octoprint/server/util/flask.py | 23 +-- tests/server/util/flask.py | 237 ++++++++++++++++++++--------- 2 files changed, 177 insertions(+), 83 deletions(-) diff --git a/src/octoprint/server/util/flask.py b/src/octoprint/server/util/flask.py index fe48c7f7..0afc0715 100644 --- a/src/octoprint/server/util/flask.py +++ b/src/octoprint/server/util/flask.py @@ -321,6 +321,14 @@ class ReverseProxiedEnvironment(object): environ["HTTP_HOST"] = host environ["SERVER_NAME"] = server environ["SERVER_PORT"] = port + + elif environ.get("HTTP_HOST", None) is not None: + # if we have a Host header, we use that and make sure our server name and port properties match it + host = environ["HTTP_HOST"] + server, port = host_to_server_and_port(host, url_scheme) + environ["SERVER_NAME"] = server + environ["SERVER_PORT"] = port + else: # else we take a look at the server and port headers and if we have # something there we derive the host from it @@ -335,15 +343,12 @@ class ReverseProxiedEnvironment(object): if port is not None: environ["SERVER_PORT"] = port - # make sure HTTP_HOST matches SERVER_NAME and SERVER_PORT - expected_server, expected_port = host_to_server_and_port(environ.get("HTTP_HOST", None), url_scheme) - if expected_server != environ["SERVER_NAME"] or expected_port != environ["SERVER_PORT"]: - # there's a difference, fix it! - if url_scheme == "http" and environ["SERVER_PORT"] == "80" or url_scheme == "https" and environ["SERVER_PORT"] == "443": - # default port for scheme, can be skipped - environ["HTTP_HOST"] = environ["SERVER_NAME"] - else: - environ["HTTP_HOST"] = environ["SERVER_NAME"] + ":" + environ["SERVER_PORT"] + # reconstruct host header + if url_scheme == "http" and environ["SERVER_PORT"] == "80" or url_scheme == "https" and environ["SERVER_PORT"] == "443": + # default port for scheme, can be skipped + environ["HTTP_HOST"] = environ["SERVER_NAME"] + else: + environ["HTTP_HOST"] = environ["SERVER_NAME"] + ":" + environ["SERVER_PORT"] # call wrapped app with rewritten environment return environ diff --git a/tests/server/util/flask.py b/tests/server/util/flask.py index e18e2dfb..52491243 100644 --- a/tests/server/util/flask.py +++ b/tests/server/util/flask.py @@ -103,27 +103,29 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): "wsgi.url_scheme": "https" }), - # server and port headers set -> host derived with port + # host set, server and port differ -> updated, standard port ({ - "HTTP_X_FORWARDED_SERVER": "example2.com", - "HTTP_X_FORWARDED_PORT": "444", - "HTTP_X_FORWARDED_PROTO": "https" - }, { - "HTTP_HOST": "example2.com:444", - "SERVER_NAME": "example2.com", - "SERVER_PORT": "444", - "wsgi.url_scheme": "https" - }), + "HTTP_HOST": "example.com", + "wsgi.url_scheme": "https", + "SERVER_NAME": "localhost", + "SERVER_PORT": "80" + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "443", + }), - # server and port headers set, standard port -> host derived, no port + # host set, server and port differ -> updated, non standard port ({ - "HTTP_X_FORWARDED_SERVER": "example.com", - "HTTP_X_FORWARDED_PORT": "80", - }, { - "HTTP_HOST": "example.com", + "HTTP_HOST": "example.com:444", + "wsgi.url_scheme": "https", + "SERVER_NAME": "localhost", + "SERVER_PORT": "80" + }, { + "HTTP_HOST": "example.com:444", "SERVER_NAME": "example.com", - "SERVER_PORT": "80", - }), + "SERVER_PORT": "444", + }), # multiple scheme entries -> only use first one ({ @@ -132,7 +134,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): "wsgi.url_scheme": "https" }), - # host = none -> should never happen but you never know... + # host = none (should never happen but you never know) -> server & port used for reconstruction ({ "HTTP_HOST": None, "HTTP_X_FORWARDED_SERVER": "example.com", @@ -158,6 +160,67 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): self.assertDictEqual(merged_expected, actual) + @data( + # server and port headers set -> host derived with port + ({ + "SERVER_NAME": "example2.com", + "SERVER_PORT": "444", + "HTTP_X_FORWARDED_PROTO": "https" + }, { + "HTTP_HOST": "example2.com:444", + "SERVER_NAME": "example2.com", + "SERVER_PORT": "444", + "wsgi.url_scheme": "https" + }), + + # server and port headers set, standard port -> host derived, no port + ({ + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + }), + + # server and port forwarded headers set -> host derived with port + ({ + "HTTP_X_FORWARDED_SERVER": "example2.com", + "HTTP_X_FORWARDED_PORT": "444", + "HTTP_X_FORWARDED_PROTO": "https" + }, { + "HTTP_HOST": "example2.com:444", + "SERVER_NAME": "example2.com", + "SERVER_PORT": "444", + "wsgi.url_scheme": "https" + }), + + # server and port forwarded headers set, standard port -> host derived, no port + ({ + "HTTP_X_FORWARDED_SERVER": "example.com", + "HTTP_X_FORWARDED_PORT": "80", + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "80", + }), + ) + @unpack + def test_nohost(self, environ, expected): + reverse_proxied = ReverseProxiedEnvironment() + + merged_environ = dict(standard_environ) + merged_environ.update(environ) + del merged_environ["HTTP_HOST"] + + actual = reverse_proxied(merged_environ) + + merged_expected = dict(standard_environ) + merged_expected.update(environ) + merged_expected.update(expected) + + self.assertDictEqual(merged_expected, actual) + @data( # prefix overridden ({ @@ -195,57 +258,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): "SERVER_PORT": "81" }), - # server overridden - ({ - "server": "example.com" - }, { - }, { - "HTTP_HOST": "example.com:5000", - "SERVER_NAME": "example.com", - "SERVER_PORT": "5000" - }), - - # port overridden, standard port - ({ - "port": "80" - }, { - }, { - "HTTP_HOST": "localhost", - "SERVER_PORT": "80" - }), - - # port overridden, non standard port - ({ - "port": "81" - }, { - }, { - "HTTP_HOST": "localhost:81", - "SERVER_PORT": "81" - }), - - # server and port overridden, default port - ({ - "server": "example.com", - "port": "80" - }, { - }, { - "HTTP_HOST": "example.com", - "SERVER_NAME": "example.com", - "SERVER_PORT": "80" - }), - - # server and port overridden, non default port - ({ - "server": "example.com", - "port": "81" - }, { - }, { - "HTTP_HOST": "example.com:81", - "SERVER_NAME": "example.com", - "SERVER_PORT": "81" - }), - - # prefix not really overridden + # prefix not really overridden, forwarded headers take precedence ({ "prefix": "/octoprint" }, { @@ -253,7 +266,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # scheme not really overridden + # scheme not really overridden, forwarded headers take precedence ({ "scheme": "https" }, { @@ -261,7 +274,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # scheme 2 not really overridden + # scheme 2 not really overridden, forwarded headers take precedence ({ "scheme": "https" }, { @@ -269,7 +282,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # host not really overridden + # host not really overridden, forwarded headers take precedence ({ "host": "example.com:444" }, { @@ -277,7 +290,7 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # server not really overridden + # server not really overridden, forwarded headers take precedence ({ "server": "example.com" }, { @@ -285,12 +298,20 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): }, { }), - # port not really overridden + # port not really overridden, forwarded headers take precedence ({ "port": "444" }, { "HTTP_X_FORWARDED_PORT": "5000" }, { + }), + + # server and port not really overridden, Host header wins + ({ + "server": "example.com", + "port": "80" + }, { + }, { }) ) @unpack @@ -308,6 +329,74 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase): self.assertDictEqual(merged_expected, actual) + @data( + # server overridden + ({ + "server": "example.com" + }, { + }, { + "HTTP_HOST": "example.com:5000", + "SERVER_NAME": "example.com", + "SERVER_PORT": "5000" + }), + + # port overridden, standard port + ({ + "port": "80" + }, { + }, { + "HTTP_HOST": "localhost", + "SERVER_PORT": "80" + }), + + # port overridden, non standard port + ({ + "port": "81" + }, { + }, { + "HTTP_HOST": "localhost:81", + "SERVER_PORT": "81" + }), + + # server and port overridden, default port + ({ + "server": "example.com", + "port": "80" + }, { + }, { + "HTTP_HOST": "example.com", + "SERVER_NAME": "example.com", + "SERVER_PORT": "80" + }), + + # server and port overridden, non default port + ({ + "server": "example.com", + "port": "81" + }, { + }, { + "HTTP_HOST": "example.com:81", + "SERVER_NAME": "example.com", + "SERVER_PORT": "81" + }), + + ) + @unpack + def test_fallbacks_nohost(self, fallbacks, environ, expected): + reverse_proxied = ReverseProxiedEnvironment(**fallbacks) + + merged_environ = dict(standard_environ) + merged_environ.update(environ) + del merged_environ["HTTP_HOST"] + + actual = reverse_proxied(merged_environ) + + merged_expected = dict(standard_environ) + merged_expected.update(environ) + merged_expected.update(expected) + + self.assertDictEqual(merged_expected, actual) + def test_header_config_ok(self): result = ReverseProxiedEnvironment.to_header_candidates(["prefix-header1", "prefix-header2"]) self.assertEquals(result, ["HTTP_PREFIX_HEADER1", "HTTP_PREFIX_HEADER2"])