Files

86 lines
2.0 KiB
Python
Raw Permalink Normal View History

2025-12-25 11:16:59 +08:00
"""
Server specs are used to describe an upstream proxy or server.
"""
import re
from functools import cache
from typing import Literal
from mitmproxy.net import check
ServerSpec = tuple[
Literal["http", "https", "http3", "tls", "dtls", "tcp", "udp", "dns", "quic"],
tuple[str, int],
]
server_spec_re = re.compile(
r"""
^
(?:(?P<scheme>\w+)://)? # scheme is optional
(?P<host>[^:/]+|\[.+\]) # hostname can be DNS name, IPv4, or IPv6 address.
(?::(?P<port>\d+))? # port is optional
/? # we allow a trailing backslash, but no path
$
""",
re.VERBOSE,
)
@cache
def parse(server_spec: str, default_scheme: str) -> ServerSpec:
"""
Parses a server mode specification, e.g.:
- http://example.com/
- example.org
- example.com:443
*Raises:*
- ValueError, if the server specification is invalid.
"""
m = server_spec_re.match(server_spec)
if not m:
raise ValueError(f"Invalid server specification: {server_spec}")
if m.group("scheme"):
scheme = m.group("scheme")
else:
scheme = default_scheme
if scheme not in (
"http",
"https",
"http3",
"tls",
"dtls",
"tcp",
"udp",
"dns",
"quic",
):
raise ValueError(f"Invalid server scheme: {scheme}")
host = m.group("host")
# IPv6 brackets
if host.startswith("[") and host.endswith("]"):
host = host[1:-1]
if not check.is_valid_host(host):
raise ValueError(f"Invalid hostname: {host}")
if m.group("port"):
port = int(m.group("port"))
else:
try:
port = {
"http": 80,
"https": 443,
"quic": 443,
"http3": 443,
"dns": 53,
}[scheme]
except KeyError:
raise ValueError(f"Port specification missing.")
if not check.is_valid_port(port):
raise ValueError(f"Invalid port: {port}")
return scheme, (host, port) # type: ignore