diff --git a/taskuary/cli.py b/taskuary/cli.py index 8af78ba7..c9884afd 100644 --- a/taskuary/cli.py +++ b/taskuary/cli.py @@ -7,11 +7,14 @@ def public_url(host, port) -> str: """0.0.0.0 / :: are bind addresses, not a place a browser can go.""" shown = '127.0.0.1' if host in ('0.0.0.0', '::') else host + if ':' in shown and not shown.startswith('['): shown = f'[{shown}]' return f'http://{shown}:{port}' def _busy(host, port): probe = '127.0.0.1' if host in ('0.0.0.0', '::') else host - with socket.socket() as s: return s.connect_ex((probe, port)) == 0 + try: + with socket.create_connection((probe.strip('[]'), port), timeout=0.2): return True + except OSError: return False def _is_taskuary(url): try: diff --git a/tests/test_config.py b/tests/test_config.py index 69fe834a..0dea2454 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,7 +1,7 @@ """Config writer tests - the UI persists agents/settings, so save() must round-trip exactly through stdlib tomllib. """ -import os, unittest +import os, socket, unittest from unittest import mock try: import tomllib except ImportError: import tomli as tomllib @@ -91,6 +91,16 @@ def test_public_url_rewrites_wildcard_bind(self): self.assertEqual(cli.public_url('::', 7787), 'http://127.0.0.1:7787') self.assertEqual(cli.public_url('127.0.0.1', 7787), 'http://127.0.0.1:7787') self.assertEqual(cli.public_url('10.0.0.5', 9000), 'http://10.0.0.5:9000') + self.assertEqual(cli.public_url('::1', 7787), 'http://[::1]:7787') + try: + with socket.socket(socket.AF_INET6) as s: + s.bind(('::1', 0)) + port = s.getsockname()[1] + self.assertFalse(cli._busy('::1', port)) + s.listen(1) + self.assertTrue(cli._busy('::1', port)) + except OSError: + pass if __name__ == '__main__':