diff --git a/networking/generate_network_config.py b/networking/generate_network_config.py index f4dbbfb..9201f9a 100644 --- a/networking/generate_network_config.py +++ b/networking/generate_network_config.py @@ -6,9 +6,35 @@ import os import stat import ipaddress +import re + +HEX_ESCAPE_RE = re.compile(r'\\x([0-9A-Fa-f]{2})') SYS_CON_DIR = "/etc/NetworkManager/system-connections" +def is_escaped_essid(essid: str) -> bool: + """True if the ESSID contains at least one \\xHH hex escape sequence.""" + return HEX_ESCAPE_RE.search(essid) is not None + +def bytes_to_nm_ssid_value(raw: bytes) -> str: + return ";".join(str(b) for b in raw) + ";" + +def unescape_regex(essid: str) -> bytes: + """ + Only touch \\xHH sequences with a targeted regex substitution, leave + every other character exactly as-is, then encode the result as Latin-1 + (a 1:1 char -> byte mapping) to recover the raw bytes. + """ + unescaped_chars = HEX_ESCAPE_RE.sub(lambda m: chr(int(m.group(1), 16)), essid) + return unescaped_chars.encode("latin-1") + +def escaped_hex_to_bytes_string(raw_essid_from_iwlist: str) -> str: + """Return the exact text to put after 'ssid=' in the .nmconnection file.""" + if is_escaped_essid(raw_essid_from_iwlist): + raw_bytes = unescape_regex(raw_essid_from_iwlist) + return bytes_to_nm_ssid_value(raw_bytes) + return raw_essid_from_iwlist + def calculate_brd_by_hand(address: str, netmask: str) -> str: return format(ipaddress.IPv4Network(f"{address}/{netmask}", strict=False).broadcast_address) @@ -119,6 +145,8 @@ def get_wireless_conn_file(config: ConfigGroup): psk = config.get("wireless-password") connect = "true" if config.get("wireless-network") else "false" + ssid = escaped_hex_to_bytes_string(ssid) + ssid = escape_backslashes_for_network_manager(ssid) psk = escape_backslashes_for_network_manager(psk) diff --git a/networking/test_generate_network_config.py b/networking/test_generate_network_config.py index b2164dd..c8a8463 100644 --- a/networking/test_generate_network_config.py +++ b/networking/test_generate_network_config.py @@ -226,7 +226,71 @@ def get(k): c.get = Mock(side_effect=get) template = get_wireless_conn_file(c) assert template == wireless_template.format("method=auto") - + + @mock.patch("generate_network_config.configure_static_network", side_effect=mock_csn) + def test_get_wireless_conn_file_ascii_ssid(self, csn_mock): + """Test ASCII-only SSID like 'MyNetwork' from iwlist.""" + c = Mock() + + def get(k): + if k == "wireless-type": + return "dhcp" + elif k == "wireless-ssid": + return "MyNetwork" # ASCII SSID + elif k == "wireless-password": + return "password123" + else: + return None + c.get = Mock(side_effect=get) + + template = get_wireless_conn_file(c) + # Verify ssid=MyNetwork appears in output (ASCII SSIDs pass through unchanged) + assert "ssid=MyNetwork" in template + assert "psk=password123" in template + assert "method=auto" in template + + @mock.patch("generate_network_config.configure_static_network", side_effect=mock_csn) + def test_get_wireless_conn_file_mixed_ssid(self, csn_mock): + """Test mixed ASCII and non-ASCII SSID.""" + c = Mock() + + def get(k): + if k == "wireless-type": + return "dhcp" + elif k == "wireless-ssid": + # München in UTF-8 hex escapes (as iwlist would output) + return r"M\xC3\xBCnchen" + elif k == "wireless-password": + return "password123" + else: + return None + c.get = Mock(side_effect=get) + + template = get_wireless_conn_file(c) + # Verify ssid=77;195;188;110;99;104;101;110; (byte values for München) + assert "ssid=77;195;188;110;99;104;101;110;" in template + + @mock.patch("generate_network_config.configure_static_network", side_effect=mock_csn) + def test_get_wireless_conn_file_non_ascii_ssid(self, csn_mock): + """Test non-ASCII SSID from iwlist hex-escaped output.""" + c = Mock() + + def get(k): + if k == "wireless-type": + return "dhcp" + elif k == "wireless-ssid": + # "😂" -(non-ASCII) + return r"\xF0\x9F\x98\x82" + elif k == "wireless-password": + return "password123" + else: + return None + c.get = Mock(side_effect=get) + + template = get_wireless_conn_file(c) + # Verify correct byte conversion for mixed SSID + assert "ssid=240;159;152;130;" in template + def test_calculate_brd_by_hand(self): brd = calculate_brd_by_hand("192.168.1.24", 8) assert brd == "192.255.255.255"