Skip to content

Commit

Permalink
Do not hiccup on ipv6 addresses in the HTTP Host header
Browse files Browse the repository at this point in the history
Fixed port extraction. Now supports hostname, hostname + port,
ipv4, ipv4 + port, ipv6, ipv6 + port

Fixes #1571
  • Loading branch information
foosel committed Nov 7, 2016
1 parent 7efdece commit 2952b26
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 4 deletions.
52 changes: 49 additions & 3 deletions src/octoprint/server/util/flask.py
Expand Up @@ -232,6 +232,15 @@ def to_header_candidates(values):
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,
Expand Down Expand Up @@ -286,11 +295,43 @@ def host_to_server_and_port(host, scheme):
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

Expand Down Expand Up @@ -348,7 +389,12 @@ def host_to_server_and_port(host, scheme):
# 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
Expand Down
90 changes: 89 additions & 1 deletion tests/server/util/flask.py
Expand Up @@ -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):
Expand Down

0 comments on commit 2952b26

Please sign in to comment.