From d74d2da755f8bf5499b2804b8cac781d71b21ad5 Mon Sep 17 00:00:00 2001 From: matthias8422 Date: Thu, 24 Apr 2025 21:09:09 -0700 Subject: [PATCH] Added a new Couldnt connect to server error message Re wrote the URL parsing logic again, but due to increased complexity put it into a utility class Fixed desktop version opening up a lobby even when it did not connect to a server Wired up the new error message to mobile, and both error messages to Desktop --- .../home/online/CSubmenuOnlineLobby.java | 28 ++++++---- .../screens/online/OnlineLobbyScreen.java | 3 ++ forge-gui/res/languages/de-DE.properties | 1 + forge-gui/res/languages/en-US.properties | 1 + forge-gui/res/languages/es-ES.properties | 1 + forge-gui/res/languages/fr-FR.properties | 1 + forge-gui/res/languages/it-IT.properties | 1 + forge-gui/res/languages/ja-JP.properties | 1 + forge-gui/res/languages/pt-BR.properties | 1 + forge-gui/res/languages/zh-CN.properties | 1 + .../forge/gamemodes/net/NetConnectUtil.java | 23 ++++---- .../properties/ForgeConstants.java | 1 + .../main/java/forge/util/URLValidator.java | 52 +++++++++++++++++++ 13 files changed, 93 insertions(+), 22 deletions(-) create mode 100644 forge-gui/src/main/java/forge/util/URLValidator.java diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java index 827665b02bc..523b83d0cfe 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/online/CSubmenuOnlineLobby.java @@ -3,6 +3,7 @@ package forge.screens.home.online; import java.net.BindException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import javax.swing.JMenu; import javax.swing.SwingUtilities; @@ -16,12 +17,14 @@ import forge.gui.error.BugReporter; import forge.gui.framework.EDocID; import forge.gui.framework.ICDoc; import forge.gui.util.SOptionPane; +import forge.localinstance.properties.ForgeConstants; import forge.menus.IMenuProvider; import forge.menus.MenuUtil; import forge.screens.home.CHomeUI; import forge.screens.home.CLobby; import forge.screens.home.VLobby; import forge.screens.home.sanctioned.ConstructedGameMenu; +import forge.toolbox.FOptionPane; import forge.util.Localizer; public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { @@ -39,7 +42,7 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { if (url == null) { return; } FThreads.invokeInBackgroundThread(() -> { - if (url.length() > 0) { + if (!url.isEmpty()) { join(url); } else { @@ -83,16 +86,23 @@ public enum CSubmenuOnlineLobby implements ICDoc, IMenuProvider { }); final ChatMessage result = NetConnectUtil.join(url, VSubmenuOnlineLobby.SINGLETON_INSTANCE, FNetOverlay.SINGLETON_INSTANCE); - - SwingUtilities.invokeLater(() -> { + if(Objects.equals(result.getMessage(), ForgeConstants.CLOSE_CONN_COMMAND)) { + FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("UnableConnectToServer", url)); SOverlayUtils.hideOverlay(); - if (result instanceof ChatMessage) { - FNetOverlay.SINGLETON_INSTANCE.show(result); - if (CHomeUI.SINGLETON_INSTANCE.getCurrentDocID() == EDocID.HOME_NETWORK) { - VSubmenuOnlineLobby.SINGLETON_INSTANCE.populate(); + } else if (Objects.equals(result.getMessage(), ForgeConstants.INVALID_HOST_COMMAND)) { + FOptionPane.showErrorDialog(Localizer.getInstance().getMessage("lblDetectedInvalidHostAddress", url)); + SOverlayUtils.hideOverlay(); + } else { + SwingUtilities.invokeLater(() -> { + SOverlayUtils.hideOverlay(); + if (result instanceof ChatMessage) { + FNetOverlay.SINGLETON_INSTANCE.show(result); + if (CHomeUI.SINGLETON_INSTANCE.getCurrentDocID() == EDocID.HOME_NETWORK) { + VSubmenuOnlineLobby.SINGLETON_INSTANCE.populate(); + } } - } - }); + }); + } } @Override diff --git a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java index 72f5a55a165..e911d9f135b 100644 --- a/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java +++ b/forge-gui-mobile/src/forge/screens/online/OnlineLobbyScreen.java @@ -108,6 +108,9 @@ public class OnlineLobbyScreen extends LobbyScreen implements IOnlineLobby { if (joinServer) { result[0] = NetConnectUtil.join(url, OnlineLobbyScreen.this, chatInterface); if (result[0].getMessage() == ForgeConstants.CLOSE_CONN_COMMAND) { //this message is returned via netconnectutil on exception + closeConn(Forge.getLocalizer().getMessage("UnableConnectToServer", url)); + return; + } else if (result[0].getMessage() == ForgeConstants.INVALID_HOST_COMMAND) { closeConn(Forge.getLocalizer().getMessage("lblDetectedInvalidHostAddress", url)); return; } diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index b2243b5a215..1e372a25b05 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -281,6 +281,7 @@ OKresetWorkshopLayout=Workshop-Layout wurde zurückgesetzt. AresetMatchScreenLayout=Dies wird das Spielfeldlayout zurücksetzen.\nFalls du vorher noch das derzeitige Layout speichern möchtest, nutze bitte Forge->Layout->File->Save Current Layout in einem laufenden Spiel.\n\nSpielfeldlayout zurücksetzen? TresetMatchScreenLayout=Spielfeldlayout zurücksetzen OKresetMatchScreenLayout=Spielfeldlayout wurde zurückgesetzt. +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=Gültige Spielformate lblOnlineMultiplayer=Mehrspieler (online) diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index af858fe6fe1..9d21937f332 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -283,6 +283,7 @@ OKresetWorkshopLayout=Workshop layout has been reset. AresetMatchScreenLayout=This will reset the layout of the Match screen.\n If you want to save the current layout first, please use the Dock tab -> Save Layout option in the Match screen.\n\n Reset layout? TresetMatchScreenLayout=Reset Match Screen Layout OKresetMatchScreenLayout=Match Screen layout has been reset. +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=Sanctioned Formats lblOnlineMultiplayer=Online Multiplayer diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index a22ba8de6af..13c8d65abd3 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -283,6 +283,7 @@ OKresetWorkshopLayout=El diseño del taller se ha restablecido. AresetMatchScreenLayout=Esto restablecerá el diseño de la pantalla de juego.\n Si desea guardar primero el diseño actual, use la pestaña Dock -> Guardar opción de diseño en la pantalla de Juego\n \n ¿Restablecer diseño? TresetMatchScreenLayout=Restablecer diseño de pantalla de juego OKresetMatchScreenLayout=El diseño de la pantalla de juego se ha restablecido. +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=Formatos oficiales lblOnlineMultiplayer=Multijugador online diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index efedf5b2dfb..2e85cd0d6a8 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -281,6 +281,7 @@ OKresetWorkshopLayout=La disposition de l'atelier a été réinitialisée. AresetMatchScreenLayout=Ceci réinitialisera la mise en page de l'écran Match.\n Si vous souhaitez d'abord enregistrer la mise en page actuelle, veuillez utiliser l'onglet Dock -> Enregistrer la mise en page dans l'écran Match.\n\n Réinitialiser la mise en page ? TresetMatchScreenLayout=Réinitialiser la disposition de l'écran de correspondance OKresetMatchScreenLayout=La disposition de l'écran de correspondance a été réinitialisée. +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=Formats autorisés lblOnlineMultiplayer=Multijoueur en ligne diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index bb83f78c6ad..28f074c7cfd 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -280,6 +280,7 @@ OKresetWorkshopLayout=Il layout dell'officina è stato ripristinato. AresetMatchScreenLayout=Ciò ripristinerà il layout della schermata degli incontri. \n Se si desidera prima salvare il layout corrente, utilizzare la scheda Comandi -> opzione Salva layout della schermata di gioco. \n \n Ripristinare layout? TresetMatchScreenLayout=Ripristina il layout della schermata degli incontri OKresetMatchScreenLayout=Il layout della schermata degli incontri è stato ripristinato. +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=Formati sanzionati lblOnlineMultiplayer=Multigiocatore online diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 751e9e0ff9a..9e6a3c98b33 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -281,6 +281,7 @@ OKresetWorkshopLayout=ワークショップレイアウトのリセットされ AresetMatchScreenLayout=これにより、試合画面のレイアウトがリセットされます。\n 現在のレイアウトを最初に保存する場合は、[Dock]タブ-> [レイアウトの保存]オプションを[一致]画面で使用してください。\n\n レイアウトをリセットしますか? TresetMatchScreenLayout=試合画面レイアウトのリセット OKresetMatchScreenLayout=試合画面のレイアウトがリセットされました。 +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=フォーマット lblOnlineMultiplayer=オンラインマルチプレイ diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 890b257c718..92553de105a 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -293,6 +293,7 @@ AresetMatchScreenLayout=Isso irá redefinir o layout da tela da Partida.\n\ Redefinir o layout? TresetMatchScreenLayout=Redefinir Layout da Partida OKresetMatchScreenLayout=O layout da Tela da Partida foi redefinido. +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=Formatos Sancionados lblOnlineMultiplayer=Multijogador Online diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 430b67d3217..df7544357ae 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -283,6 +283,7 @@ OKresetWorkshopLayout=游戏界面已重置 AresetMatchScreenLayout=这将重置匹配屏幕的布局。\n如果要保存当前的布局,请使用“匹配”屏幕中的“保存布局”选项。\n\n重置布局? TresetMatchScreenLayout=重置匹配布局 OKresetMatchScreenLayout=匹配布局已重置。 +UnableConnectToServer=Unable to connect to server with host {0}. #EMenuGroup.java lblSanctionedFormats=单人游戏 lblOnlineMultiplayer=多人联机 diff --git a/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java b/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java index 238aded48dc..02bda81daa1 100644 --- a/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java +++ b/forge-gui/src/main/java/forge/gamemodes/net/NetConnectUtil.java @@ -21,9 +21,10 @@ import forge.localinstance.properties.ForgePreferences.FPref; import forge.model.FModel; import forge.player.GamePlayerUtil; import forge.util.Localizer; +import forge.util.URLValidator; import org.apache.commons.lang3.StringUtils; -import java.net.URI; +import static forge.util.URLValidator.parseURL; public class NetConnectUtil { private NetConnectUtil() { } @@ -125,21 +126,17 @@ public class NetConnectUtil { public static ChatMessage join(final String url, final IOnlineLobby onlineLobby, final IOnlineChatInterface chatInterface) { final IGuiGame gui = GuiBase.getInterface().getNewGuiGame(); String hostname; - int port = ForgeConstants.DEFAULT_SERVER_CONNECTION_PORT; + int port; - try { - // Check if the URL already has a protocol - String formattedUrl = url.matches("^[a-zA-Z][a-zA-Z0-9+.-]*://.*") ? url : "http://" + url; - // Parse the URI - URI uri = new URI(formattedUrl); - hostname = uri.getHost(); - if (uri.getPort() != -1) { // If a port is specified in the URL - port = uri.getPort(); - } - } catch (Exception ex) { - hostname = url; // Fallback to original URL if parsing fails + URLValidator.HostPort hostPort = parseURL(url); + if(hostPort == null) { + return new ChatMessage(null, ForgeConstants.INVALID_HOST_COMMAND); } + hostname = hostPort.host(); + port = hostPort.port(); + if(port == -1) port = ForgeConstants.DEFAULT_SERVER_CONNECTION_PORT; + final FGameClient client = new FGameClient(FModel.getPreferences().getPref(FPref.PLAYER_NAME), "0", gui, hostname, port); onlineLobby.setClient(client); diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 783594a2f05..e148705256e 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -274,6 +274,7 @@ public final class ForgeConstants { public static final String CONQUEST_PREFS_FILE = USER_PREFS_DIR + "conquest.preferences"; public static final String ITEM_VIEW_PREFS_FILE = USER_PREFS_DIR + "item_view.preferences"; public static final String CLOSE_CONN_COMMAND = "<<_EM_ESOLC_<<"; + public static final String INVALID_HOST_COMMAND = "<<_TSOH_DILAVNI_<<"; // data that has defaults in the program dir but overrides/additions in the user dir private static final String _DEFAULTS_DIR = RES_DIR + "defaults" + PATH_SEPARATOR; diff --git a/forge-gui/src/main/java/forge/util/URLValidator.java b/forge-gui/src/main/java/forge/util/URLValidator.java new file mode 100644 index 00000000000..51a0254ded5 --- /dev/null +++ b/forge-gui/src/main/java/forge/util/URLValidator.java @@ -0,0 +1,52 @@ +package forge.util; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.regex.Pattern; + +public class URLValidator { + + private static final Pattern DOMAIN_NAME_PATTERN = Pattern.compile("^(?!-)[A-Za-z0-9-]{1,63}(? 255) return null; + } catch (NumberFormatException e) { + return null; + } + } + return new HostPort(ip, null); // No port for raw IP + } + + + public static HostPort parseURL(String url) { + try { + //prepend http:// if a protocol is missing so URI parsing does not fail + String formattedUrl = url.matches("^[a-zA-Z][a-zA-Z0-9+.-]*://.*") ? url : "http://" + url; + URI uri = new URI(formattedUrl); + String host = uri.getHost(); + int port = uri.getPort(); // Returns -1 if port is not specified + if (host == null) return null; + HostPort hostPort; + if (parseIP(host) != null) { + hostPort = new HostPort(host, port == -1 ? null : port); + } else if (DOMAIN_NAME_PATTERN.matcher(host).matches()) { + hostPort = new HostPort(host, port == -1 ? null : port); + } else { + return null; + } + return hostPort; + } catch (URISyntaxException e) { + return null; + } + } + + //This is fine, Records were introduced in Java 16, its essentially a DTO class with implicit getters and constructors + public record HostPort(String host, Integer port) { + } + +}