aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_rdap.py89
1 files changed, 89 insertions, 0 deletions
diff --git a/tests/test_rdap.py b/tests/test_rdap.py
index 7f2f0d5..815c311 100644
--- a/tests/test_rdap.py
+++ b/tests/test_rdap.py
@@ -445,5 +445,94 @@ class Query(unittest.TestCase):
self.assertEqual(len(calls), 5)
+class QuotedQueryComponent(unittest.TestCase):
+ """The interpolated component cannot escape its path segment.
+
+ contacts.is_queryable() is the admission point and validates every
+ candidate, but that guarantee has already leaked three times in this
+ module's history, each time the same shape: a validator applied to one
+ branch and forgotten on its sibling. These call query_ip and
+ query_domain DIRECTLY, bypassing contacts entirely, because a future
+ branch that skips worklist() is exactly the failure this layer exists
+ to survive.
+ """
+
+ IPV4 = {"services": [[["198.51.100.0/24"], ["https://rir.example.invalid/"]]]}
+ IPV6 = {"services": [[["fe80::/10"], ["https://rir.example.invalid/"]]]}
+ DNS = {"services": [[["invalid"], ["https://registry.example.invalid/"]]]}
+
+ def _recorder(self):
+ calls = []
+
+ def fetch(url):
+ calls.append(url)
+ return {"handle": "X", "entities": []}
+
+ return calls, fetch
+
+ def test_a_traversal_in_an_ip_stays_under_the_ip_segment(self):
+ """server_for_ip refuses this string today, so the base is forced.
+
+ Forcing it is the point: this asserts what query_ip does with a
+ component it was handed, not what today's lookup happens to reject.
+ """
+ calls, fetch = self._recorder()
+ original = rdap.server_for_ip
+ rdap.server_for_ip = lambda address, data: "https://rir.example.invalid/"
+ try:
+ rdap.query_ip("198.51.100.7/../../etc", self.IPV4, fetch=fetch)
+ finally:
+ rdap.server_for_ip = original
+
+ self.assertEqual(
+ calls,
+ ["https://rir.example.invalid/ip/198.51.100.7%2F..%2F..%2Fetc"],
+ )
+
+ def test_a_scope_id_in_an_ip_is_encoded_not_left_malformed(self):
+ """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.
+ """
+ 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"]
+ )
+
+ def test_a_query_and_fragment_in_a_host_are_not_live(self):
+ calls, fetch = self._recorder()
+ rdap.query_domain("victim?e=x#frag.example.invalid", self.DNS, fetch=fetch)
+
+ self.assertEqual(len(calls), 1)
+ url = calls[0]
+ self.assertNotIn("?", url)
+ self.assertNotIn("#", url)
+ self.assertIn("%3F", url)
+ self.assertIn("%23", url)
+
+ def test_a_normal_ip_url_is_unchanged(self):
+ calls, fetch = self._recorder()
+ rdap.query_ip("198.51.100.7", self.IPV4, fetch=fetch)
+ self.assertEqual(calls, ["https://rir.example.invalid/ip/198.51.100.7"])
+
+ def test_a_normal_host_url_is_unchanged(self):
+ calls, fetch = self._recorder()
+ rdap.query_domain("example.invalid", self.DNS, fetch=fetch)
+ self.assertEqual(
+ calls, ["https://registry.example.invalid/domain/example.invalid"]
+ )
+
+ def test_the_returned_candidate_is_unquoted(self):
+ """The manifest and the review dialog show the name, not the URL."""
+ calls, fetch = self._recorder()
+ _, queried = rdap.query_domain(
+ "victim?e=x#frag.example.invalid", self.DNS, fetch=fetch
+ )
+ self.assertEqual(queried, "victim?e=x#frag.example.invalid")
+
+
if __name__ == "__main__":
unittest.main()