Skip to content

Commit e0805fc

Browse files
authored
Fix #3940: 修复未正确解析 IPv6 服务器地址的问题 (#3942)
1 parent f223d2b commit e0805fc

5 files changed

Lines changed: 215 additions & 71 deletions

File tree

HMCL/src/main/java/org/jackhuang/hmcl/ui/construct/ServerAddressValidator.java

Lines changed: 0 additions & 62 deletions
This file was deleted.

HMCL/src/main/java/org/jackhuang/hmcl/ui/versions/VersionSettingsPage.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.jackhuang.hmcl.ui.decorator.DecoratorPage;
4646
import org.jackhuang.hmcl.util.Lang;
4747
import org.jackhuang.hmcl.util.Pair;
48+
import org.jackhuang.hmcl.util.ServerAddress;
49+
import org.jackhuang.hmcl.util.StringUtils;
4850
import org.jackhuang.hmcl.util.javafx.BindingMapping;
4951
import org.jackhuang.hmcl.util.javafx.PropertyUtils;
5052
import org.jackhuang.hmcl.util.javafx.SafeStringConverter;
@@ -451,6 +453,16 @@ public VersionSettingsPage(boolean globalSetting) {
451453

452454
txtServerIP = new JFXTextField();
453455
txtServerIP.setPromptText(i18n("settings.advanced.server_ip.prompt"));
456+
Validator.addTo(txtServerIP).accept(str -> {
457+
if (StringUtils.isBlank(str))
458+
return true;
459+
try {
460+
ServerAddress.parse(str);
461+
return true;
462+
} catch (Exception ignored) {
463+
return false;
464+
}
465+
});
454466
FXUtils.setLimitWidth(txtServerIP, 300);
455467
serverPane.addRow(0, new Label(i18n("settings.advanced.server_ip")), txtServerIP);
456468
}

HMCLCore/src/main/java/org/jackhuang/hmcl/launch/DefaultLauncher.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.jackhuang.hmcl.download.LibraryAnalyzer;
2222
import org.jackhuang.hmcl.game.*;
2323
import org.jackhuang.hmcl.util.Lang;
24+
import org.jackhuang.hmcl.util.ServerAddress;
2425
import org.jackhuang.hmcl.util.StringUtils;
2526
import org.jackhuang.hmcl.util.gson.UUIDTypeAdapter;
2627
import org.jackhuang.hmcl.util.io.FileUtils;
@@ -295,15 +296,21 @@ private Command generateCommandLine(File nativeFolder) throws IOException {
295296
res.addAll(Arguments.parseArguments(argumentsFromAuthInfo.getGame(), configuration, features));
296297

297298
if (StringUtils.isNotBlank(options.getServerIp())) {
298-
String[] args = options.getServerIp().split(":");
299-
if (GameVersionNumber.asGameVersion(gameVersion).compareTo("1.20") < 0) {
300-
res.add("--server");
301-
res.add(args[0]);
302-
res.add("--port");
303-
res.add(args.length > 1 ? args[1] : "25565");
304-
} else {
305-
res.add("--quickPlayMultiplayer");
306-
res.add(args[0] + ":" + (args.length > 1 ? args[1] : "25565"));
299+
String address = options.getServerIp();
300+
301+
try {
302+
ServerAddress parsed = ServerAddress.parse(address);
303+
if (GameVersionNumber.asGameVersion(gameVersion).compareTo("1.20") < 0) {
304+
res.add("--server");
305+
res.add(parsed.getHost());
306+
res.add("--port");
307+
res.add(parsed.getPort() >= 0 ? String.valueOf(parsed.getPort()) : "25565");
308+
} else {
309+
res.add("--quickPlayMultiplayer");
310+
res.add(parsed.getPort() < 0 ? address + ":25565" : address);
311+
}
312+
} catch (IllegalArgumentException e) {
313+
LOG.warning("Invalid server address: " + address, e);
307314
}
308315
}
309316

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Hello Minecraft! Launcher
3+
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package org.jackhuang.hmcl.util;
19+
20+
import org.jetbrains.annotations.NotNull;
21+
22+
import java.util.Objects;
23+
24+
/**
25+
* @author Glavo
26+
*/
27+
public final class ServerAddress {
28+
29+
private static final int UNKNOWN_PORT = -1;
30+
31+
private static IllegalArgumentException illegalAddress(String address) {
32+
return new IllegalArgumentException("Invalid server address: " + address);
33+
}
34+
35+
/**
36+
* @throws IllegalArgumentException if the address is not a valid server address
37+
*/
38+
public static @NotNull ServerAddress parse(@NotNull String address) {
39+
Objects.requireNonNull(address);
40+
41+
if (!address.startsWith("[")) {
42+
int colonPos = address.indexOf(':');
43+
if (colonPos >= 0) {
44+
if (colonPos == address.length() - 1)
45+
throw illegalAddress(address);
46+
47+
String host = address.substring(0, colonPos);
48+
int port;
49+
try {
50+
port = Integer.parseInt(address.substring(colonPos + 1));
51+
} catch (NumberFormatException e) {
52+
throw illegalAddress(address);
53+
}
54+
if (port < 0 || port > 0xFFFF)
55+
throw illegalAddress(address);
56+
return new ServerAddress(host, port);
57+
} else {
58+
return new ServerAddress(address);
59+
}
60+
} else {
61+
// Parse IPv6 address
62+
int colonIndex = address.indexOf(':');
63+
int closeBracketIndex = address.lastIndexOf(']');
64+
65+
if (colonIndex < 0 || closeBracketIndex < colonIndex)
66+
throw illegalAddress(address);
67+
68+
String host = address.substring(1, closeBracketIndex);
69+
if (closeBracketIndex == address.length() - 1)
70+
return new ServerAddress(host);
71+
72+
if (address.length() < closeBracketIndex + 3 || address.charAt(closeBracketIndex + 1) != ':')
73+
throw illegalAddress(address);
74+
75+
int port;
76+
try {
77+
port = Integer.parseInt(address.substring(closeBracketIndex + 2));
78+
} catch (NumberFormatException e) {
79+
throw illegalAddress(address);
80+
}
81+
82+
if (port < 0 || port > 0xFFFF)
83+
throw illegalAddress(address);
84+
85+
return new ServerAddress(host, port);
86+
}
87+
}
88+
89+
private final String host;
90+
private final int port;
91+
92+
public ServerAddress(@NotNull String host) {
93+
this(host, UNKNOWN_PORT);
94+
}
95+
96+
public ServerAddress(@NotNull String host, int port) {
97+
this.host = Objects.requireNonNull(host);
98+
this.port = port;
99+
}
100+
101+
public String getHost() {
102+
return host;
103+
}
104+
105+
public int getPort() {
106+
return port;
107+
}
108+
109+
@Override
110+
public boolean equals(Object o) {
111+
if (!(o instanceof ServerAddress)) return false;
112+
ServerAddress that = (ServerAddress) o;
113+
return port == that.port && Objects.equals(host, that.host);
114+
}
115+
116+
@Override
117+
public int hashCode() {
118+
return Objects.hash(host, port);
119+
}
120+
121+
@Override
122+
public String toString() {
123+
return String.format("ServerAddress[host='%s', port=%d]", host, port);
124+
}
125+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Hello Minecraft! Launcher
3+
* Copyright (C) 2025 huangyuhui <huanghongxun2008@126.com> and contributors
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
17+
*/
18+
package org.jackhuang.hmcl.util;
19+
20+
import org.junit.jupiter.api.Test;
21+
22+
import static org.junit.jupiter.api.Assertions.assertEquals;
23+
import static org.junit.jupiter.api.Assertions.assertThrows;
24+
25+
/**
26+
* @author Glavo
27+
*/
28+
public final class ServerAddressTest {
29+
30+
@Test
31+
public void testParse() {
32+
assertEquals(new ServerAddress("example.com"), ServerAddress.parse("example.com"));
33+
assertEquals(new ServerAddress("example.com", 25565), ServerAddress.parse("example.com:25565"));
34+
35+
assertEquals(new ServerAddress("127.0.0.0"), ServerAddress.parse("127.0.0.0"));
36+
assertEquals(new ServerAddress("127.0.0.0", 0), ServerAddress.parse("127.0.0.0:0"));
37+
assertEquals(new ServerAddress("127.0.0.0", 12345), ServerAddress.parse("127.0.0.0:12345"));
38+
39+
assertEquals(new ServerAddress("::1"), ServerAddress.parse("[::1]"));
40+
assertEquals(new ServerAddress("::1", 0), ServerAddress.parse("[::1]:0"));
41+
assertEquals(new ServerAddress("::1", 12345), ServerAddress.parse("[::1]:12345"));
42+
assertEquals(new ServerAddress("2001:db8::1"), ServerAddress.parse("[2001:db8::1]"));
43+
assertEquals(new ServerAddress("2001:db8::1", 0), ServerAddress.parse("[2001:db8::1]:0"));
44+
assertEquals(new ServerAddress("2001:db8::1", 12345), ServerAddress.parse("[2001:db8::1]:12345"));
45+
46+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("["));
47+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[]]"));
48+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[]:0"));
49+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[::1]:"));
50+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[::1]|"));
51+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[::1]|0"));
52+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[::1]:a"));
53+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[::1]:65536"));
54+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[::1]:-1"));
55+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[ ]:-1"));
56+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("[-]:-1"));
57+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("example.com:"));
58+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("example.com:a"));
59+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("example.com:65536"));
60+
assertThrows(IllegalArgumentException.class, () -> ServerAddress.parse("example.com:-1"));
61+
}
62+
}

0 commit comments

Comments
 (0)