Do not hiccup on ipv6 addresses in the HTTP Host header
Fixed port extraction. Now supports hostname, hostname + port, ipv4, ipv4 + port, ipv6, ipv6 + port Fixes #1571
This commit is contained in:
parent
7efdeced62
commit
2952b2622e
2 changed files with 138 additions and 4 deletions
|
|
@ -232,6 +232,15 @@ class ReverseProxiedEnvironment(object):
|
|||
to_wsgi_format = lambda header: "HTTP_" + header.upper().replace("-", "_")
|
||||
return map(to_wsgi_format, values)
|
||||
|
||||
@staticmethod
|
||||
def valid_ip(address):
|
||||
import netaddr
|
||||
try:
|
||||
netaddr.IPAddress(address)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def __init__(self,
|
||||
header_prefix=None,
|
||||
header_scheme=None,
|
||||
|
|
@ -286,11 +295,43 @@ class ReverseProxiedEnvironment(object):
|
|||
if host is None:
|
||||
return None, None
|
||||
|
||||
default_port = "443" if scheme == "https" else "80"
|
||||
host = host.strip()
|
||||
|
||||
if ":" in host:
|
||||
server, port = host.split(":", 1)
|
||||
# we might have an ipv6 address here, or a port, or both
|
||||
|
||||
if host[0] == "[":
|
||||
# that looks like an ipv6 address with port, e.g. [fec1::1]:80
|
||||
address_end = host.find("]")
|
||||
if address_end == -1:
|
||||
# no ], that looks like a seriously broken address
|
||||
return None, None
|
||||
|
||||
# extract server ip, skip enclosing [ and ]
|
||||
server = host[1:address_end]
|
||||
tail = host[address_end + 1:]
|
||||
|
||||
# now check if there's also a port
|
||||
if len(tail) and tail[0] == ":":
|
||||
# port included as well
|
||||
port = tail[1:]
|
||||
else:
|
||||
# no port, use default one
|
||||
port = default_port
|
||||
|
||||
elif self.__class__.valid_ip(host):
|
||||
# ipv6 address without port
|
||||
server = host
|
||||
port = default_port
|
||||
|
||||
else:
|
||||
# ipv4 address with port
|
||||
server, port = host.rsplit(":", 1)
|
||||
|
||||
else:
|
||||
server = host
|
||||
port = "443" if scheme == "https" else "80"
|
||||
port = default_port
|
||||
|
||||
return server, port
|
||||
|
||||
|
|
@ -348,7 +389,12 @@ class ReverseProxiedEnvironment(object):
|
|||
# default port for scheme, can be skipped
|
||||
environ["HTTP_HOST"] = environ["SERVER_NAME"]
|
||||
else:
|
||||
environ["HTTP_HOST"] = environ["SERVER_NAME"] + ":" + environ["SERVER_PORT"]
|
||||
server_name_component = environ["SERVER_NAME"]
|
||||
if ":" in server_name_component and self.__class__.valid_ip(server_name_component):
|
||||
# this is an ipv6 address, we need to wrap that in [ and ] before appending the port
|
||||
server_name_component = "[" + server_name_component + "]"
|
||||
|
||||
environ["HTTP_HOST"] = server_name_component + ":" + environ["SERVER_PORT"]
|
||||
|
||||
# call wrapped app with rewritten environment
|
||||
return environ
|
||||
|
|
|
|||
|
|
@ -143,7 +143,95 @@ class ReverseProxiedEnvironmentTest(unittest.TestCase):
|
|||
"HTTP_HOST": "example.com",
|
||||
"SERVER_NAME": "example.com",
|
||||
"SERVER_PORT": "80"
|
||||
})
|
||||
}),
|
||||
|
||||
# host = none, default port -> server & port used for reconstruction (ipv4)
|
||||
({
|
||||
"HTTP_HOST": None,
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"SERVER_PORT": "80"
|
||||
}, {
|
||||
"HTTP_HOST": "127.0.0.1",
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"SERVER_PORT": "80"
|
||||
}),
|
||||
|
||||
# host = none, non standard port -> server & port used for reconstruction (ipv4)
|
||||
({
|
||||
"HTTP_HOST": None,
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"SERVER_PORT": "444"
|
||||
}, {
|
||||
"HTTP_HOST": "127.0.0.1:444",
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"SERVER_PORT": "444"
|
||||
}),
|
||||
|
||||
# host = none, default port -> server & port used for reconstruction (ipv6)
|
||||
({
|
||||
"HTTP_HOST": None,
|
||||
"SERVER_NAME": "fec1::1",
|
||||
"SERVER_PORT": "80"
|
||||
}, {
|
||||
"HTTP_HOST": "fec1::1",
|
||||
"SERVER_NAME": "fec1::1",
|
||||
"SERVER_PORT": "80"
|
||||
}),
|
||||
|
||||
# host = none, non standard port -> server & port used for reconstruction (ipv6)
|
||||
({
|
||||
"HTTP_HOST": None,
|
||||
"SERVER_NAME": "fec1::1",
|
||||
"SERVER_PORT": "444"
|
||||
}, {
|
||||
"HTTP_HOST": "[fec1::1]:444",
|
||||
"SERVER_NAME": "fec1::1",
|
||||
"SERVER_PORT": "444"
|
||||
}),
|
||||
|
||||
# host set, server and port not, default port -> server & port derived from host (ipv4)
|
||||
({
|
||||
"HTTP_HOST": "127.0.0.1",
|
||||
"SERVER_NAME": None,
|
||||
"SERVER_PORT": None
|
||||
}, {
|
||||
"HTTP_HOST": "127.0.0.1",
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"SERVER_PORT": "80"
|
||||
}),
|
||||
|
||||
# host set, server and port not, non standard port -> server & port derived from host (ipv4)
|
||||
({
|
||||
"HTTP_HOST": "127.0.0.1:444",
|
||||
"SERVER_NAME": None,
|
||||
"SERVER_PORT": None
|
||||
}, {
|
||||
"HTTP_HOST": "127.0.0.1:444",
|
||||
"SERVER_NAME": "127.0.0.1",
|
||||
"SERVER_PORT": "444"
|
||||
}),
|
||||
|
||||
# host set, server and port not, default port -> server & port derived from host (ipv6)
|
||||
({
|
||||
"HTTP_HOST": "fec1::1",
|
||||
"SERVER_NAME": None,
|
||||
"SERVER_PORT": None
|
||||
}, {
|
||||
"HTTP_HOST": "fec1::1",
|
||||
"SERVER_NAME": "fec1::1",
|
||||
"SERVER_PORT": "80"
|
||||
}),
|
||||
|
||||
# host set, server and port not, non standard port -> server & port derived from host (ipv6)
|
||||
({
|
||||
"HTTP_HOST": "[fec1::1]:444",
|
||||
"SERVER_NAME": None,
|
||||
"SERVER_PORT": None
|
||||
}, {
|
||||
"HTTP_HOST": "[fec1::1]:444",
|
||||
"SERVER_NAME": "fec1::1",
|
||||
"SERVER_PORT": "444"
|
||||
}),
|
||||
)
|
||||
@unpack
|
||||
def test_stock(self, environ, expected):
|
||||
|
|
|
|||
Loading…
Reference in a new issue