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.
This commit is contained in:
Gina Häußge 2016-09-08 15:18:09 +02:00
parent a7bd770180
commit 53b74f9caa
2 changed files with 177 additions and 83 deletions

View file

@ -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

View file

@ -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"])