diff options
| -rw-r--r-- | abusectl/redact.py | 14 | ||||
| -rw-r--r-- | tests/test_redact.py | 12 |
2 files changed, 24 insertions, 2 deletions
diff --git a/abusectl/redact.py b/abusectl/redact.py index 5e7c4d3..15b1345 100644 --- a/abusectl/redact.py +++ b/abusectl/redact.py @@ -105,8 +105,18 @@ def _redact_fragment(fragment: str) -> str: def _netloc_without_userinfo(parts) -> str: - """Return scheme://host[:port] worth of netloc, dropping any userinfo.""" - netloc = parts.hostname or "" + """Return scheme://host[:port] worth of netloc, dropping any userinfo. + + ``parts.hostname`` returns an IPv6 literal WITHOUT its brackets + (``2001:db8::1``, not ``[2001:db8::1]``), so re-bracketing it here is + required, not cosmetic: without it, appending ``:8080`` produces + ``2001:db8::1:8080``, which no consumer can parse back into a host and + a port, and which is not even the same address any more as text. + """ + host = parts.hostname or "" + if ":" in host: + host = f"[{host}]" + netloc = host if parts.port is not None: netloc = f"{netloc}:{parts.port}" return netloc diff --git a/tests/test_redact.py b/tests/test_redact.py index e6d65c0..553ffec 100644 --- a/tests/test_redact.py +++ b/tests/test_redact.py @@ -65,6 +65,18 @@ class TestRedactUrl(unittest.TestCase): "http://a.invalid/p#REDACTED", ) + def test_an_ipv6_host_survives_userinfo_stripping(self): + self.assertEqual( + redact.url("http://victim@[2001:db8::1]:8080/p?x=1"), + "http://[2001:db8::1]:8080/p?x=REDACTED", + ) + + def test_an_ipv6_host_without_userinfo_is_untouched(self): + self.assertEqual( + redact.url("http://[2001:db8::1]:8080/p?x=1"), + "http://[2001:db8::1]:8080/p?x=REDACTED", + ) + class TestSuspectPathSegments(unittest.TestCase): def test_a_base64_looking_segment_is_flagged(self): |
