From 07fd717972c24b97e6e786c83a0467fb0bb74b72 Mon Sep 17 00:00:00 2001 From: Riventh Date: Thu, 13 Nov 2025 21:55:59 +0100 Subject: [PATCH] feat: add IPv6 address support in URL parsing (fixes #400) --- winrm/__init__.py | 10 +++++++++- winrm/tests/test_session.py | 40 +++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/winrm/__init__.py b/winrm/__init__.py index ec31a32..407e83f 100644 --- a/winrm/__init__.py +++ b/winrm/__init__.py @@ -106,7 +106,15 @@ def _strip_namespace(self, xml: bytes) -> bytes: @staticmethod def _build_url(target: str, transport: str) -> str: - match = re.match(r"(?i)^((?Phttp[s]?)://)?(?P[0-9a-z-_.]+)(:(?P\d+))?(?P(/)?(wsman)?)?", target) # NOQA + # Try IPv6 pattern first (with brackets) + ipv6_pattern = r"(?i)^((?Phttp[s]?)://)?(?P\[[0-9a-f:]+\])(:(?P\d+))?(?P(/)?(wsman)?)?$" + match = re.match(ipv6_pattern, target) + + # Fall back to IPv4/hostname pattern + if not match: + ipv4_pattern = r"(?i)^((?Phttp[s]?)://)?(?P[0-9a-z-_.]+)(:(?P\d+))?(?P(/)?(wsman)?)?$" + match = re.match(ipv4_pattern, target) + if not match: raise ValueError("Invalid target URL: {0}".format(target)) diff --git a/winrm/tests/test_session.py b/winrm/tests/test_session.py index 4990932..3428bcb 100644 --- a/winrm/tests/test_session.py +++ b/winrm/tests/test_session.py @@ -89,3 +89,43 @@ def test_decode_clixml_invalid_xml(): actual = s._clean_error_msg(msg) assert actual == msg + + +def test_target_as_ipv6_address(): + s = Session("[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]", auth=("john.smith", "secret")) + assert s.url == "http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5985/wsman" + + +def test_target_as_ipv6_address_with_port(): + s = Session("[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:1111", auth=("john.smith", "secret")) + assert s.url == "http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:1111/wsman" + + +def test_target_as_schema_then_ipv6(): + s = Session("http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]", auth=("john.smith", "secret")) + assert s.url == "http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5985/wsman" + + +def test_target_as_schema_then_ipv6_with_port(): + s = Session("http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5985", auth=("john.smith", "secret")) + assert s.url == "http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5985/wsman" + + +def test_target_as_full_url_with_ipv6(): + s = Session("http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5985/wsman", auth=("john.smith", "secret")) + assert s.url == "http://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5985/wsman" + + +def test_target_as_https_ipv6(): + s = Session("https://[::1]:5986/wsman", auth=("john.smith", "secret")) + assert s.url == "https://[::1]:5986/wsman" + + +def test_target_as_ipv6_localhost(): + s = Session("[::1]", auth=("john.smith", "secret")) + assert s.url == "http://[::1]:5985/wsman" + + +def test_target_as_ipv6_with_ssl_transport(): + s = Session("[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]", auth=("john.smith", "secret"), transport="ssl") + assert s.url == "https://[2a05:d018:1961:ba00:ff5b:37ba:20c7:726a]:5986/wsman"