1
"""Exact host matching for requests to the unauthenticated file server."""
6
LOCAL_HOSTS = {"localhost", "127.0.0.1", "::1"}
7
UNSPECIFIED_HOSTS = {"0.0.0.0", "::"}
8
HOST_HEADER = re.compile(r"(?:\[([0-9a-fA-F:.]+)\]|([a-zA-Z0-9.-]+))(?::([0-9]{1,5}))?")
9
DNS_LABEL = re.compile(r"[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?")
12
def normalize_host(value):
13
"""Accept an IP or exact DNS name, without a scheme, port, or wildcard."""
15
1
1
return str(ipaddress.ip_address(value))
19
name = value.encode("idna").decode("ascii").lower().removesuffix(".")
25
or not all(DNS_LABEL.fullmatch(label) for label in name.split("."))
28
"Use an IP address or exact hostname, without a scheme, port, path, or wildcard."
33
def allowed_hosts_for(host, extra_hosts=()):
34
hosts = LOCAL_HOSTS | {normalize_host(name) for name in extra_hosts}
35
bound_host = normalize_host(host)
36
# Listening on every interface must not mean trusting every Host header.
37
if bound_host not in UNSPECIFIED_HOSTS:
39
return frozenset(hosts)
42
def is_allowed_host(headers, allowed_hosts):
43
values = [value for name, value in headers if name.lower() == b"host"]
47
1
1
match = HOST_HEADER.fullmatch(values[0].decode("ascii"))
50
ipv6, hostname, port = match.groups()
51
if port is not None and not 1 <= int(port) <= 65535:
53
2
3
host = str(ipaddress.IPv6Address(ipv6)) if ipv6 else normalize_host(hostname)
54
except (ValueError, UnicodeError):
56
return host in allowed_hosts