From fbe9eddb37ee513949c73869766d163b2e945378 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 17 Jun 2026 20:13:54 +0200 Subject: [PATCH] fix(wired): bound trigger payloads --- .../wired/triggers/WiredTriggerAtSetTime.java | 53 ++++++++++++++----- .../triggers/WiredTriggerAtTimeLong.java | 53 ++++++++++++++----- .../wired/triggers/WiredTriggerRepeater.java | 50 +++++++++++------ .../triggers/WiredTriggerRepeaterLong.java | 48 ++++++++++++----- .../triggers/WiredTriggerScoreAchieved.java | 41 ++++++++++---- .../WiredTriggerPayloadGuardTest.java | 52 ++++++++++++++++++ 6 files changed, 228 insertions(+), 69 deletions(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerPayloadGuardTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java index 9f460464..4df86353 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java @@ -29,6 +29,9 @@ import java.util.List; */ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; + static final int DEFAULT_EXECUTE_TIME = 20 * 500; + static final int MIN_EXECUTE_TIME = 500; + static final int MAX_EXECUTE_TIME = 60 * 60 * 1000; /** The time in milliseconds until the trigger fires */ public int executeTime; @@ -67,25 +70,38 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); + this.executeTime = parseExecuteTime(wiredData); - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.executeTime = data.executeTime; - } else { - if (wiredData.length() >= 1) { - this.executeTime = (Integer.parseInt(wiredData)); - } - } - - if (this.executeTime < 500) { - this.executeTime = 20 * 500; - } - // Initialize for tick system - will be registered by RoomItemManager this.accumulatedTime = 0; this.hasFired = false; } + static int parseExecuteTime(String wiredData) { + if (wiredData == null || wiredData.isBlank()) { + return DEFAULT_EXECUTE_TIME; + } + + try { + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + return clampExecuteTime(data != null ? data.executeTime : DEFAULT_EXECUTE_TIME); + } + + return clampExecuteTime(Integer.parseInt(wiredData)); + } catch (RuntimeException e) { + return DEFAULT_EXECUTE_TIME; + } + } + + static int clampExecuteTime(int executeTime) { + if (executeTime < MIN_EXECUTE_TIME) { + return DEFAULT_EXECUTE_TIME; + } + + return Math.min(executeTime, MAX_EXECUTE_TIME); + } + @Override public void onPickUp() { this.executeTime = 0; @@ -134,13 +150,22 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - this.executeTime = settings.getIntParams()[0] * 500; + this.executeTime = clampExecuteTime(safeMultiply(settings.getIntParams()[0], MIN_EXECUTE_TIME)); this.resetTimer(); return true; } + private static int safeMultiply(int value, int factor) { + if (value <= 0) { + return DEFAULT_EXECUTE_TIME; + } + + long multiplied = (long) value * factor; + return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied; + } + // ========== WiredTickable Implementation ========== @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java index 8864f4c3..194345d1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java @@ -29,6 +29,9 @@ import java.util.List; */ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { private static final WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; + static final int DEFAULT_EXECUTE_TIME = 20 * 500; + static final int MIN_EXECUTE_TIME = 500; + static final int MAX_EXECUTE_TIME = 60 * 60 * 1000; /** The time in milliseconds until the trigger fires */ private int executeTime; @@ -67,25 +70,38 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); + this.executeTime = parseExecuteTime(wiredData); - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.executeTime = data.executeTime; - } else { - if (wiredData.length() >= 1) { - this.executeTime = (Integer.parseInt(wiredData)); - } - } - - if (this.executeTime < 500) { - this.executeTime = 20 * 500; - } - // Initialize for tick system this.accumulatedTime = 0; this.hasFired = false; } + static int parseExecuteTime(String wiredData) { + if (wiredData == null || wiredData.isBlank()) { + return DEFAULT_EXECUTE_TIME; + } + + try { + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + return clampExecuteTime(data != null ? data.executeTime : DEFAULT_EXECUTE_TIME); + } + + return clampExecuteTime(Integer.parseInt(wiredData)); + } catch (RuntimeException e) { + return DEFAULT_EXECUTE_TIME; + } + } + + static int clampExecuteTime(int executeTime) { + if (executeTime < MIN_EXECUTE_TIME) { + return DEFAULT_EXECUTE_TIME; + } + + return Math.min(executeTime, MAX_EXECUTE_TIME); + } + @Override public void onPickUp() { this.executeTime = 0; @@ -134,13 +150,22 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - this.executeTime = settings.getIntParams()[0] * 500; + this.executeTime = clampExecuteTime(safeMultiply(settings.getIntParams()[0], MIN_EXECUTE_TIME)); this.resetTimer(); return true; } + private static int safeMultiply(int value, int factor) { + if (value <= 0) { + return DEFAULT_EXECUTE_TIME; + } + + long multiplied = (long) value * factor; + return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied; + } + // ========== WiredTickable Implementation ========== @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java index 2ac70e88..6acfedf0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java @@ -30,6 +30,8 @@ import java.util.List; public class WiredTriggerRepeater extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final WiredTriggerType type = WiredTriggerType.PERIODICALLY; public static final int DEFAULT_DELAY = 10 * 500; // 5 seconds default + static final int MIN_DELAY = 500; + static final int MAX_DELAY = 60 * 60 * 1000; /** The interval in milliseconds between triggers */ protected int repeatTime = DEFAULT_DELAY; @@ -62,19 +64,32 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); + this.repeatTime = parseRepeatTime(wiredData); + } - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.repeatTime = data.repeatTime; - } else { - if (wiredData.length() >= 1) { - this.repeatTime = (Integer.parseInt(wiredData)); + static int parseRepeatTime(String wiredData) { + if (wiredData == null || wiredData.isBlank()) { + return DEFAULT_DELAY; + } + + try { + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + return normalizeRepeatTime(data != null ? data.repeatTime : DEFAULT_DELAY); } + + return normalizeRepeatTime(Integer.parseInt(wiredData)); + } catch (RuntimeException e) { + return DEFAULT_DELAY; + } + } + + static int normalizeRepeatTime(int repeatTime) { + if (repeatTime < MIN_DELAY) { + return DEFAULT_DELAY; } - if (this.repeatTime < 500) { - this.repeatTime = 20 * 500; - } + return Math.min(repeatTime, MAX_DELAY); } @Override @@ -123,17 +138,20 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - int newRepeatTime = settings.getIntParams()[0] * 500; - - if (newRepeatTime < 500) { - newRepeatTime = 500; - } - - this.repeatTime = newRepeatTime; + this.repeatTime = normalizeRepeatTime(safeMultiply(settings.getIntParams()[0], MIN_DELAY)); return true; } + private static int safeMultiply(int value, int factor) { + if (value <= 0) { + return DEFAULT_DELAY; + } + + long multiplied = (long) value * factor; + return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied; + } + // ========== WiredTickable Implementation ========== @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java index ffc1d92f..1e37d5e2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java @@ -29,6 +29,8 @@ import java.util.List; */ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final int DEFAULT_DELAY = 10 * 5000; // 50 seconds default + static final int MIN_DELAY = 5000; + static final int MAX_DELAY = 60 * 60 * 1000; private static final WiredTriggerType type = WiredTriggerType.PERIODICALLY_LONG; /** The interval in milliseconds between triggers */ @@ -62,19 +64,32 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); + this.repeatTime = parseRepeatTime(wiredData); + } - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.repeatTime = data.repeatTime; - } else { - if (wiredData.length() >= 1) { - this.repeatTime = (Integer.parseInt(wiredData)); + static int parseRepeatTime(String wiredData) { + if (wiredData == null || wiredData.isBlank()) { + return DEFAULT_DELAY; + } + + try { + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + return clampRepeatTime(data != null ? data.repeatTime : DEFAULT_DELAY); } + + return clampRepeatTime(Integer.parseInt(wiredData)); + } catch (RuntimeException e) { + return DEFAULT_DELAY; + } + } + + static int clampRepeatTime(int repeatTime) { + if (repeatTime < MIN_DELAY) { + return DEFAULT_DELAY; } - if (this.repeatTime < 5000) { - this.repeatTime = 20 * 5000; - } + return Math.min(repeatTime, MAX_DELAY); } @Override @@ -123,15 +138,20 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements @Override public boolean saveData(WiredSettings settings) { if (settings.getIntParams().length < 1) return false; - int interval = settings.getIntParams()[0]; - if (interval < 1) { - interval = 1; - } - this.repeatTime = interval * 5000; + this.repeatTime = clampRepeatTime(safeMultiply(settings.getIntParams()[0], MIN_DELAY)); // No accumulated time reset needed - using global tick count return true; } + private static int safeMultiply(int value, int factor) { + if (value <= 0) { + return DEFAULT_DELAY; + } + + long multiplied = (long) value * factor; + return multiplied > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) multiplied; + } + // ========== WiredTickable Implementation ========== @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java index 8bf9c7b2..ad31d0d8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java @@ -18,6 +18,7 @@ import java.sql.SQLException; public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { private static final WiredTriggerType type = WiredTriggerType.SCORE_ACHIEVED; + static final int MAX_SCORE = 1_000_000; private int score = 0; private int teamType = GameTeamColors.NONE.type; @@ -71,17 +72,27 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { String wiredData = set.getString("wired_data"); + JsonData data = parseData(wiredData); + this.score = data.score; + this.teamType = data.teamType; + } - if (wiredData.startsWith("{")) { - JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.score = data.score; - this.teamType = normalizeTeamType(data.teamType); - } else { - try { - this.score = Integer.parseInt(wiredData); - } catch (Exception e) { + static JsonData parseData(String wiredData) { + if (wiredData == null || wiredData.isBlank()) { + return new JsonData(0, GameTeamColors.NONE.type); + } + + try { + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + return data != null + ? new JsonData(clampScore(data.score), normalizeTeamType(data.teamType)) + : new JsonData(0, GameTeamColors.NONE.type); } - this.teamType = GameTeamColors.NONE.type; + + return new JsonData(clampScore(Integer.parseInt(wiredData)), GameTeamColors.NONE.type); + } catch (RuntimeException e) { + return new JsonData(0, GameTeamColors.NONE.type); } } @@ -116,7 +127,7 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { @Override public boolean saveData(WiredSettings settings) { if(settings.getIntParams().length < 1) return false; - this.score = settings.getIntParams()[0]; + this.score = clampScore(settings.getIntParams()[0]); this.teamType = (settings.getIntParams().length > 1) ? normalizeTeamType(settings.getIntParams()[1]) : GameTeamColors.NONE.type; @@ -128,7 +139,15 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { return true; } - private int normalizeTeamType(int value) { + static int clampScore(int value) { + if (value < 0) { + return 0; + } + + return Math.min(value, MAX_SCORE); + } + + static int normalizeTeamType(int value) { if (value >= GameTeamColors.RED.type && value <= GameTeamColors.YELLOW.type) { return value; } diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerPayloadGuardTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerPayloadGuardTest.java new file mode 100644 index 00000000..c350c335 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerPayloadGuardTest.java @@ -0,0 +1,52 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +import com.eu.habbo.habbohotel.games.GameTeamColors; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WiredTriggerPayloadGuardTest { + @Test + void repeaterPayloadsFallBackOnInvalidDataAndClampUpperBound() { + assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime(null)); + assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime("not-a-number")); + assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime("{broken")); + assertEquals(WiredTriggerRepeater.DEFAULT_DELAY, WiredTriggerRepeater.parseRepeatTime("{\"repeatTime\":0}")); + assertEquals(WiredTriggerRepeater.MAX_DELAY, WiredTriggerRepeater.parseRepeatTime("{\"repeatTime\":2147483647}")); + + assertEquals(WiredTriggerRepeaterLong.DEFAULT_DELAY, WiredTriggerRepeaterLong.parseRepeatTime(null)); + assertEquals(WiredTriggerRepeaterLong.DEFAULT_DELAY, WiredTriggerRepeaterLong.parseRepeatTime("1")); + assertEquals(WiredTriggerRepeaterLong.MAX_DELAY, WiredTriggerRepeaterLong.parseRepeatTime("2147483647")); + } + + @Test + void atTimePayloadsFallBackOnInvalidDataAndClampUpperBound() { + assertEquals(WiredTriggerAtSetTime.DEFAULT_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime(null)); + assertEquals(WiredTriggerAtSetTime.DEFAULT_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime("bad")); + assertEquals(WiredTriggerAtSetTime.DEFAULT_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime("{\"executeTime\":0}")); + assertEquals(WiredTriggerAtSetTime.MAX_EXECUTE_TIME, WiredTriggerAtSetTime.parseExecuteTime("{\"executeTime\":2147483647}")); + + assertEquals(WiredTriggerAtTimeLong.DEFAULT_EXECUTE_TIME, WiredTriggerAtTimeLong.parseExecuteTime("{broken")); + assertEquals(WiredTriggerAtTimeLong.DEFAULT_EXECUTE_TIME, WiredTriggerAtTimeLong.parseExecuteTime("1")); + assertEquals(WiredTriggerAtTimeLong.MAX_EXECUTE_TIME, WiredTriggerAtTimeLong.parseExecuteTime("2147483647")); + } + + @Test + void scorePayloadsNormalizeScoreAndTeam() { + WiredTriggerScoreAchieved.JsonData invalid = WiredTriggerScoreAchieved.parseData("{broken"); + assertEquals(0, invalid.score); + assertEquals(GameTeamColors.NONE.type, invalid.teamType); + + WiredTriggerScoreAchieved.JsonData legacy = WiredTriggerScoreAchieved.parseData("-10"); + assertEquals(0, legacy.score); + assertEquals(GameTeamColors.NONE.type, legacy.teamType); + + WiredTriggerScoreAchieved.JsonData capped = WiredTriggerScoreAchieved.parseData("{\"score\":2147483647,\"teamType\":999}"); + assertEquals(WiredTriggerScoreAchieved.MAX_SCORE, capped.score); + assertEquals(GameTeamColors.NONE.type, capped.teamType); + + WiredTriggerScoreAchieved.JsonData validTeam = WiredTriggerScoreAchieved.parseData("{\"score\":50,\"teamType\":" + GameTeamColors.RED.type + "}"); + assertEquals(50, validTeam.score); + assertEquals(GameTeamColors.RED.type, validTeam.teamType); + } +}