aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_rdap.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-09 09:47:51 +0200
committerDanilo M. <danix@danix.xyz>2026-09-09 09:47:51 +0200
commit745473b0edecc16915f761b15e58b23bc5bd69b9 (patch)
tree08565af9a952603047f45e8082fb0d990e996e70 /tests/test_rdap.py
parent7fbd109ed8e00ca1184015ba50c30ee92cce1ad8 (diff)
downloadabusectl-745473b0edecc16915f761b15e58b23bc5bd69b9.tar.gz
abusectl-745473b0edecc16915f761b15e58b23bc5bd69b9.zip
fix: keep ":" literal so an IPv6 query is not over-encoded
6d5822b quoted the interpolated component with safe="", which encoded the colons of every IPv6 address: 2001:db8::1 went on the wire as 2001%3Adb8%3A%3A1. RFC 9082 specifies the IPv6 object path segment as the address in its text form, and RFC 3986 pchar permits ":" in a segment, so the colon must stay literal. The failure mode was the bad one for this tool: a registry that does not normalise before matching answers 404, which reads here as "this netblock publishes no abuse desk" rather than "we asked the wrong question", losing the abuse contact silently on exactly the IPv6 hops the tool exists to report. safe=":" keeps everything the original commit was for. "/" is still encoded, so the traversal containment is unchanged, and "%" is still encoded, so a scope id stays contained. The regression got through because the happy-path guard used an IPv4 address only. The new test asserts a documentation-range IPv6 address round-trips into the URL unencoded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
Diffstat (limited to 'tests/test_rdap.py')
-rw-r--r--tests/test_rdap.py23
1 files changed, 19 insertions, 4 deletions
diff --git a/tests/test_rdap.py b/tests/test_rdap.py
index 815c311..1045160 100644
--- a/tests/test_rdap.py
+++ b/tests/test_rdap.py
@@ -459,6 +459,7 @@ class QuotedQueryComponent(unittest.TestCase):
IPV4 = {"services": [[["198.51.100.0/24"], ["https://rir.example.invalid/"]]]}
IPV6 = {"services": [[["fe80::/10"], ["https://rir.example.invalid/"]]]}
+ DOCV6 = {"services": [[["2001:db8::/32"], ["https://r6.example.invalid/"]]]}
DNS = {"services": [[["invalid"], ["https://registry.example.invalid/"]]]}
def _recorder(self):
@@ -493,14 +494,28 @@ class QuotedQueryComponent(unittest.TestCase):
"""A bare % in a URL is a truncated escape, not a literal percent.
server_for_ip accepts fe80::1%eth0 today, so this one reaches the
- wire through the normal path.
+ wire through the normal path. The colons stay literal; only the
+ scope separator is encoded.
"""
calls, fetch = self._recorder()
rdap.query_ip("fe80::1%eth0", self.IPV6, fetch=fetch)
- self.assertEqual(
- calls, ["https://rir.example.invalid/ip/fe80%3A%3A1%25eth0"]
- )
+ self.assertEqual(calls, ["https://rir.example.invalid/ip/fe80::1%25eth0"])
+
+ def test_a_normal_ipv6_address_is_not_encoded_at_all(self):
+ """RFC 9082 wants the address in its TEXT form, and pchar allows ":".
+
+ Encoding the colon does not fail loudly. A registry that does not
+ normalise before matching answers 404, which this tool reads as
+ "this netblock publishes no abuse desk" rather than "we asked the
+ wrong question", and the abuse contact for an IPv6 hop is lost
+ silently. The happy-path guard was IPv4 only, so an over-broad
+ safe="" got through once already.
+ """
+ calls, fetch = self._recorder()
+ rdap.query_ip("2001:db8::1", self.DOCV6, fetch=fetch)
+
+ self.assertEqual(calls, ["https://r6.example.invalid/ip/2001:db8::1"])
def test_a_query_and_fragment_in_a_host_are_not_live(self):
calls, fetch = self._recorder()