Skip to content

Commit 84cbd42

Browse files
fix: validate port range and reject empty hosts in _parse_address
Ports outside TCP range 1-65535 (including 0, negative, >65535) were silently accepted, producing confusing OS-level errors downstream. Empty hostnames (":9001") were also accepted. Additionally, unbracketed IPv6 addresses like "fe80::1:9001" were ambiguously misparsed. Add port range validation, empty hostname rejection, and a heuristic that detects unbracketed IPv6 (host contains multiple colons) and directs users to use bracket notation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4d36a67 commit 84cbd42

2 files changed

Lines changed: 49 additions & 0 deletions

File tree

src/dqliteclient/connection.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ def _parse_address(address: str) -> tuple[str, int]:
4343
f"Invalid port in address {address!r}: {port_str!r} is not a number"
4444
) from None
4545

46+
if not (1 <= port <= 65535):
47+
raise ValueError(f"Invalid port in address {address!r}: {port} is not in range 1-65535")
48+
if not host:
49+
raise ValueError(f"Invalid address format: empty hostname in {address!r}")
50+
if host.count(":") > 1 and not address.startswith("["):
51+
raise ValueError(
52+
f"IPv6 addresses must be bracketed: use '[{host}]:{port}' instead of {address!r}"
53+
)
54+
4655
return host, port
4756

4857

tests/test_connection.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,46 @@ def test_ipv6_no_port_raises(self) -> None:
4747
with pytest.raises(ValueError, match="expected.*host.*port"):
4848
_parse_address("[::1]")
4949

50+
def test_port_zero_raises(self) -> None:
51+
from dqliteclient.connection import _parse_address
52+
53+
with pytest.raises(ValueError, match="not in range"):
54+
_parse_address("host:0")
55+
56+
def test_port_negative_raises(self) -> None:
57+
from dqliteclient.connection import _parse_address
58+
59+
with pytest.raises(ValueError, match="not in range"):
60+
_parse_address("host:-1")
61+
62+
def test_port_too_large_raises(self) -> None:
63+
from dqliteclient.connection import _parse_address
64+
65+
with pytest.raises(ValueError, match="not in range"):
66+
_parse_address("host:65536")
67+
68+
def test_empty_host_raises(self) -> None:
69+
from dqliteclient.connection import _parse_address
70+
71+
with pytest.raises(ValueError, match="empty hostname"):
72+
_parse_address(":9001")
73+
74+
def test_unbracketed_ipv6_raises(self) -> None:
75+
from dqliteclient.connection import _parse_address
76+
77+
with pytest.raises(ValueError, match="must be bracketed"):
78+
_parse_address("fe80::1:9001")
79+
80+
def test_max_valid_port(self) -> None:
81+
from dqliteclient.connection import _parse_address
82+
83+
assert _parse_address("host:65535") == ("host", 65535)
84+
85+
def test_min_valid_port(self) -> None:
86+
from dqliteclient.connection import _parse_address
87+
88+
assert _parse_address("host:1") == ("host", 1)
89+
5090

5191
class TestDqliteConnection:
5292
def test_zero_timeout_raises(self) -> None:

0 commit comments

Comments
 (0)