From ba80870df0e28495aa89b2053d251b7c5a8ec155 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 16 Jun 2026 21:01:00 +0200 Subject: [PATCH] fix(rooms): bound room item inputs --- .../rooms/items/AdvertisingSaveEvent.java | 16 +- .../items/FootballGateSaveLookEvent.java | 14 +- .../rooms/items/MannequinSaveLookEvent.java | 6 +- .../rooms/items/MannequinSaveNameEvent.java | 8 +- .../items/MoodLightSaveSettingsEvent.java | 3 + .../rooms/items/MoveWallItemEvent.java | 2 +- .../rooms/items/PostItDeleteEvent.java | 3 + .../rooms/items/PostItPlaceEvent.java | 3 + .../rooms/items/PostItSaveDataEvent.java | 3 + .../rooms/items/RedeemClothingEvent.java | 7 + .../incoming/rooms/items/RedeemItemEvent.java | 8 +- .../rooms/items/RoomItemInputGuard.java | 51 ++++++ .../rooms/items/RoomPickupItemEvent.java | 3 + .../rooms/items/RoomPlaceItemEvent.java | 22 ++- .../rooms/items/RotateMoveItemEvent.java | 4 +- .../items/SavePostItStickyPoleEvent.java | 10 +- .../rooms/items/ToggleFloorItemEvent.java | 3 + .../rooms/items/ToggleWallItemEvent.java | 3 + .../items/UpdateFurniturePositionEvent.java | 2 + .../jukebox/JukeBoxAddSoundTrackEvent.java | 12 +- .../jukebox/JukeBoxRemoveSoundTrackEvent.java | 11 +- .../jukebox/JukeBoxRequestPlayListEvent.java | 9 +- .../lovelock/LoveLockStartConfirmEvent.java | 4 + .../rentablespace/RentSpaceCancelEvent.java | 4 + .../items/rentablespace/RentSpaceEvent.java | 4 + .../youtube/YoutubeRequestPlaylistChange.java | 13 +- .../youtube/YoutubeRequestPlaylists.java | 6 +- .../youtube/YoutubeRequestStateChange.java | 6 +- .../items/RoomItemInputGuardContractTest.java | 154 ++++++++++++++++++ 29 files changed, 362 insertions(+), 32 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuard.java create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuardContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/AdvertisingSaveEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/AdvertisingSaveEvent.java index 7427d651..c70a0ce6 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/AdvertisingSaveEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/AdvertisingSaveEvent.java @@ -18,7 +18,11 @@ public class AdvertisingSaveEvent extends MessageHandler { if (!room.hasRights(this.client.getHabbo())) return; - HabboItem item = room.getHabboItem(this.packet.readInt()); + int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + + HabboItem item = room.getHabboItem(itemId); if (item == null) return; @@ -29,9 +33,15 @@ public class AdvertisingSaveEvent extends MessageHandler { if (item instanceof InteractionCustomValues) { THashMap oldValues = new THashMap<>(((InteractionCustomValues) item).values); int count = this.packet.readInt(); + if (!RoomItemInputGuard.isValidCustomValueCount(count)) + return; + for (int i = 0; i < count / 2; i++) { - String key = this.packet.readString(); - String value = this.packet.readString(); + String key = RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_CUSTOM_KEY_LENGTH); + String value = RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_CUSTOM_VALUE_LENGTH); + + if (key.isEmpty()) + continue; if (!Emulator.getConfig().getBoolean("camera.use.https")) { value = value.replace("https://", "http://"); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/FootballGateSaveLookEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/FootballGateSaveLookEvent.java index aa4d2f30..bb1eefd2 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/FootballGateSaveLookEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/FootballGateSaveLookEvent.java @@ -13,15 +13,21 @@ public class FootballGateSaveLookEvent extends MessageHandler { if (room == null || this.client.getHabbo().getHabboInfo().getId() != room.getOwnerId()) return; - HabboItem item = room.getHabboItem(this.packet.readInt()); + int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + + HabboItem item = room.getHabboItem(itemId); if (!(item instanceof InteractionFootballGate)) return; String gender = this.packet.readString(); - String look = this.packet.readString(); + String look = RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_LOOK_LENGTH); + + if (!RoomItemInputGuard.isValidGender(gender) || look.isEmpty()) + return; switch (gender.toLowerCase()) { - default: case "m": ((InteractionFootballGate) item).setFigureM(look); room.updateItem(item); @@ -33,4 +39,4 @@ public class FootballGateSaveLookEvent extends MessageHandler { break; } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveLookEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveLookEvent.java index c62c7ad4..2e37eb43 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveLookEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveLookEvent.java @@ -15,7 +15,11 @@ public class MannequinSaveLookEvent extends MessageHandler { if (room == null || !room.isOwner(habbo)) return; - HabboItem item = room.getHabboItem(this.packet.readInt()); + int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + + HabboItem item = room.getHabboItem(itemId); if (item == null) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveNameEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveNameEvent.java index c5c94204..61fcd1aa 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveNameEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MannequinSaveNameEvent.java @@ -12,12 +12,16 @@ public class MannequinSaveNameEvent extends MessageHandler { if (room == null || !room.isOwner(this.client.getHabbo())) return; - HabboItem item = room.getHabboItem(this.packet.readInt()); + int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + + HabboItem item = room.getHabboItem(itemId); if (item == null) return; String[] data = item.getExtradata().split(":"); - String name = this.packet.readString(); + String name = RoomItemInputGuard.trimToMax(this.packet.readString(), 32); if (name.length() < 3 || name.length() > 15) { name = Emulator.getTexts().getValue("hotel.mannequin.name.default", "My look"); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoodLightSaveSettingsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoodLightSaveSettingsEvent.java index 894e83c9..88e6f3aa 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoodLightSaveSettingsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoodLightSaveSettingsEvent.java @@ -21,6 +21,9 @@ public class MoodLightSaveSettingsEvent extends MessageHandler { public void handle() throws Exception { Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null) + return; + if ((room.getGuildId() <= 0 && room.getGuildRightLevel(this.client.getHabbo()).isLessThan(RoomRightLevels.GUILD_RIGHTS)) && !room.hasRights(this.client.getHabbo())) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoveWallItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoveWallItemEvent.java index 10397073..075d4620 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoveWallItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/MoveWallItemEvent.java @@ -25,7 +25,7 @@ public class MoveWallItemEvent extends MessageHandler { int itemId = this.packet.readInt(); String wallPosition = this.packet.readString(); - if (itemId <= 0 || wallPosition.length() <= 13) + if (!RoomItemInputGuard.isPositiveId(itemId) || wallPosition.length() <= 13) return; HabboItem item = room.getHabboItem(itemId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItDeleteEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItDeleteEvent.java index 821f92c8..a65cd8a8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItDeleteEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItDeleteEvent.java @@ -15,6 +15,9 @@ public class PostItDeleteEvent extends MessageHandler { public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room == null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItPlaceEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItPlaceEvent.java index 67f1d202..f8a006a4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItPlaceEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItPlaceEvent.java @@ -19,6 +19,9 @@ public class PostItPlaceEvent extends MessageHandler { int itemId = this.packet.readInt(); String location = this.packet.readString(); + if (!RoomItemInputGuard.isPositiveId(itemId) || location.length() <= 13) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java index 24f2cbf7..0752e14a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java @@ -20,6 +20,9 @@ public class PostItSaveDataEvent extends MessageHandler { String color = this.packet.readString(); String text = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString().replace(((char) 9) + "", ""), this.client.getHabbo()); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + if (text.length() > Emulator.getConfig().getInt("postit.charlimit")) { ScripterManager.scripterDetected(this.client, Emulator.getTexts().getValue("scripter.warning.sticky.size").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%amount%", text.length() + "").replace("%limit%", Emulator.getConfig().getInt("postit.charlimit") + "")); return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemClothingEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemClothingEvent.java index 2ab95a0d..5283e1e4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemClothingEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemClothingEvent.java @@ -26,6 +26,9 @@ public class RedeemClothingEvent extends MessageHandler { public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null && this.client.getHabbo().getHabboInfo().getCurrentRoom().hasRights(this.client.getHabbo())) { HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); @@ -42,6 +45,10 @@ public class RedeemClothingEvent extends MessageHandler { item.setRoomId(0); RoomTile tile = this.client.getHabbo().getHabboInfo().getCurrentRoom().getLayout().getTile(item.getX(), item.getY()); + if (tile == null) { + return; + } + this.client.getHabbo().getHabboInfo().getCurrentRoom().removeHabboItem(item); this.client.getHabbo().getHabboInfo().getCurrentRoom().updateTile(tile); this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new UpdateStackHeightComposer(tile.x, tile.y, tile.z, tile.relativeHeight()).compose()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemItemEvent.java index 929e7a95..c5a15521 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RedeemItemEvent.java @@ -19,6 +19,9 @@ public class RedeemItemEvent extends MessageHandler { public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room != null) { @@ -98,6 +101,9 @@ public class RedeemItemEvent extends MessageHandler { room.removeHabboItem(item); room.sendComposer(new RemoveFloorItemComposer(item).compose()); RoomTile t = room.getLayout().getTile(item.getX(), item.getY()); + if (t == null) + return; + t.setStackHeight(room.getStackHeight(item.getX(), item.getY(), false)); room.updateTile(t); room.sendComposer(new UpdateStackHeightComposer(item.getX(), item.getY(), t.z, t.relativeHeight()).compose()); @@ -124,4 +130,4 @@ public class RedeemItemEvent extends MessageHandler { } } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuard.java new file mode 100644 index 00000000..9172330b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuard.java @@ -0,0 +1,51 @@ +package com.eu.habbo.messages.incoming.rooms.items; + +public final class RoomItemInputGuard { + public static final int MAX_CUSTOM_VALUE_PAIRS = 20; + public static final int MAX_CUSTOM_KEY_LENGTH = 64; + public static final int MAX_CUSTOM_VALUE_LENGTH = 512; + public static final int MAX_LOOK_LENGTH = 512; + public static final int MAX_YOUTUBE_PLAYLIST_ID_LENGTH = 128; + public static final int MAX_STICKY_POLE_COMMANDS = 10; + public static final int MAX_STICKY_POLE_COMMAND_LENGTH = 255; + + private RoomItemInputGuard() { + } + + public static boolean isPositiveId(int id) { + return id > 0; + } + + public static boolean isValidCustomValueCount(int count) { + return count > 0 && count % 2 == 0 && count / 2 <= MAX_CUSTOM_VALUE_PAIRS; + } + + public static String trimToMax(String value, int maxLength) { + if (value == null) { + return ""; + } + + String trimmed = value.trim(); + return trimmed.length() > maxLength ? trimmed.substring(0, maxLength) : trimmed; + } + + public static boolean isValidGender(String gender) { + return "m".equalsIgnoreCase(gender) || "f".equalsIgnoreCase(gender); + } + + public static Integer parseInt(String value) { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + return null; + } + } + + public static Short parseShort(String value) { + try { + return Short.parseShort(value); + } catch (NumberFormatException e) { + return null; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPickupItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPickupItemEvent.java index b1797f8a..6241510c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPickupItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPickupItemEvent.java @@ -12,6 +12,9 @@ public class RoomPickupItemEvent extends MessageHandler { this.packet.readInt(); //10 = floorItem and 20 = wallItem int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room == null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java index 1b23813b..1c535783 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java @@ -18,9 +18,12 @@ public class RoomPlaceItemEvent extends MessageHandler { public void handle() throws Exception { String[] values = this.packet.readString().split(" "); - int itemId = -1; + if (values.length == 0) + return; - if (values.length != 0) itemId = Integer.parseInt(values[0]); + Integer itemId = RoomItemInputGuard.parseInt(values[0]); + if (itemId == null || !RoomItemInputGuard.isPositiveId(itemId)) + return; if (!this.client.getHabbo().getRoomUnit().isInRoom()) { this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, FurnitureMovementError.NO_RIGHTS.errorCode)); @@ -56,9 +59,15 @@ public class RoomPlaceItemEvent extends MessageHandler { } if (item.getBaseItem().getType() == FurnitureType.FLOOR) { - short x = Short.parseShort(values[1]); - short y = Short.parseShort(values[2]); - int rotation = Integer.parseInt(values[3]); + if (values.length < 4) + return; + + Short x = RoomItemInputGuard.parseShort(values[1]); + Short y = RoomItemInputGuard.parseShort(values[2]); + Integer rotation = RoomItemInputGuard.parseInt(values[3]); + + if (x == null || y == null || rotation == null) + return; RoomTile tile = room.getLayout().getTile(x, y); @@ -108,6 +117,9 @@ public class RoomPlaceItemEvent extends MessageHandler { return; } } else { + if (values.length < 4) + return; + FurnitureMovementError error = room.placeWallFurniAt(item, values[1] + " " + values[2] + " " + values[3], this.client.getHabbo()); if (!error.equals(FurnitureMovementError.NONE)) { this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, error.errorCode)); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RotateMoveItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RotateMoveItemEvent.java index 812fb6b2..895c0a5d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RotateMoveItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RotateMoveItemEvent.java @@ -16,6 +16,8 @@ public class RotateMoveItemEvent extends MessageHandler { if (room == null) return; int furniId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(furniId)) return; + HabboItem item = room.getHabboItem(furniId); if (item == null) return; @@ -39,4 +41,4 @@ public class RotateMoveItemEvent extends MessageHandler { this.client.sendResponse(new FloorItemUpdateComposer(item)); } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/SavePostItStickyPoleEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/SavePostItStickyPoleEvent.java index 9063b816..d1a7eb90 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/SavePostItStickyPoleEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/SavePostItStickyPoleEvent.java @@ -24,9 +24,14 @@ public class SavePostItStickyPoleEvent extends MessageHandler { if (itemId == -1234) { if (this.client.getHabbo().hasPermission("cmd_multi")) { String[] commands = this.packet.readString().split("\r"); + if (commands.length > RoomItemInputGuard.MAX_STICKY_POLE_COMMANDS) + return; for (String command : commands) { - command = command.replace("
", "\r"); + command = RoomItemInputGuard.trimToMax(command.replace("
", "\r"), RoomItemInputGuard.MAX_STICKY_POLE_COMMAND_LENGTH); + if (command.isEmpty()) + continue; + CommandHandler.handleCommand(this.client, command); } } else { @@ -38,6 +43,9 @@ public class SavePostItStickyPoleEvent extends MessageHandler { if (text.length() > Emulator.getConfig().getInt("postit.charlimit")) return; + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room == null) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java index 8bf4942b..d5706027 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java @@ -41,6 +41,9 @@ public class ToggleFloorItemEvent extends MessageHandler { int itemId = this.packet.readInt(); int state = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + HabboItem item = room.getHabboItem(itemId); if (item == null || item instanceof InteractionDice) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleWallItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleWallItemEvent.java index c620900b..c54865be 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleWallItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleWallItemEvent.java @@ -24,6 +24,9 @@ public class ToggleWallItemEvent extends MessageHandler { int itemId = this.packet.readInt(); int state = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + HabboItem item = room.getHabboItem(itemId); if (item == null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UpdateFurniturePositionEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UpdateFurniturePositionEvent.java index f5bce826..bbaae2d8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UpdateFurniturePositionEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UpdateFurniturePositionEvent.java @@ -20,6 +20,8 @@ public class UpdateFurniturePositionEvent extends MessageHandler { if (room == null) return; int furniId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(furniId)) return; + HabboItem item = room.getHabboItem(furniId); if (item == null) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxAddSoundTrackEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxAddSoundTrackEvent.java index 182d6862..79f6feb4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxAddSoundTrackEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxAddSoundTrackEvent.java @@ -1,26 +1,32 @@ package com.eu.habbo.messages.incoming.rooms.items.jukebox; import com.eu.habbo.habbohotel.items.interactions.InteractionMusicDisc; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; public class JukeBoxAddSoundTrackEvent extends MessageHandler { @Override public void handle() throws Exception { - if (!this.client.getHabbo().getHabboInfo().getCurrentRoom().hasRights(this.client.getHabbo())) return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null || !room.hasRights(this.client.getHabbo())) return; int itemId = this.packet.readInt(); this.packet.readInt(); // slotId + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Habbo habbo = this.client.getHabbo(); if (habbo != null) { HabboItem item = habbo.getInventory().getItemsComponent().getHabboItem(itemId); if (item instanceof InteractionMusicDisc && item.getRoomId() == 0) { - this.client.getHabbo().getHabboInfo().getCurrentRoom().getTraxManager().addSong((InteractionMusicDisc) item, habbo); + room.getTraxManager().addSong((InteractionMusicDisc) item, habbo); } } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRemoveSoundTrackEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRemoveSoundTrackEvent.java index 26f79aff..03757c7e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRemoveSoundTrackEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRemoveSoundTrackEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.rooms.items.jukebox; import com.eu.habbo.habbohotel.items.interactions.InteractionMusicDisc; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.messages.incoming.MessageHandler; public class JukeBoxRemoveSoundTrackEvent extends MessageHandler { @@ -8,12 +9,16 @@ public class JukeBoxRemoveSoundTrackEvent extends MessageHandler { public void handle() throws Exception { int index = this.packet.readInt(); - if (this.client.getHabbo().getHabboInfo().getCurrentRoom() == null) return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null) return; - InteractionMusicDisc musicDisc = this.client.getHabbo().getHabboInfo().getCurrentRoom().getTraxManager().getSongs().get(index); + if (index < 0 || index >= room.getTraxManager().getSongs().size()) + return; + + InteractionMusicDisc musicDisc = room.getTraxManager().getSongs().get(index); if (musicDisc != null) { - this.client.getHabbo().getHabboInfo().getCurrentRoom().getTraxManager().removeSong(musicDisc.getId()); + room.getTraxManager().removeSong(musicDisc.getId()); } } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRequestPlayListEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRequestPlayListEvent.java index a5468d59..908b7135 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRequestPlayListEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRequestPlayListEvent.java @@ -1,6 +1,7 @@ package com.eu.habbo.messages.incoming.rooms.items.jukebox; import com.eu.habbo.habbohotel.rooms.TraxManager; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.rooms.items.jukebox.JukeBoxMySongsComposer; import com.eu.habbo.messages.outgoing.rooms.items.jukebox.JukeBoxPlayListComposer; @@ -8,9 +9,13 @@ import com.eu.habbo.messages.outgoing.rooms.items.jukebox.JukeBoxPlayListCompose public class JukeBoxRequestPlayListEvent extends MessageHandler { @Override public void handle() throws Exception { - TraxManager traxManager = this.client.getHabbo().getHabboInfo().getCurrentRoom().getTraxManager(); + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null) + return; + + TraxManager traxManager = room.getTraxManager(); this.client.sendResponse(new JukeBoxPlayListComposer(traxManager.getSongs(), traxManager.totalLength())); this.client.sendResponse(new JukeBoxMySongsComposer(traxManager.myList(this.client.getHabbo()))); - this.client.getHabbo().getHabboInfo().getCurrentRoom().getTraxManager().updateCurrentPlayingSong(this.client.getHabbo()); + traxManager.updateCurrentPlayingSong(this.client.getHabbo()); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/lovelock/LoveLockStartConfirmEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/lovelock/LoveLockStartConfirmEvent.java index d4588cdf..8ce2f7e8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/lovelock/LoveLockStartConfirmEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/lovelock/LoveLockStartConfirmEvent.java @@ -4,6 +4,7 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionLoveLock; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; import com.eu.habbo.messages.outgoing.rooms.items.lovelock.LoveLockFurniFinishedComposer; import com.eu.habbo.messages.outgoing.rooms.items.lovelock.LoveLockFurniFriendConfirmedComposer; @@ -12,6 +13,9 @@ public class LoveLockStartConfirmEvent extends MessageHandler { public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + if (this.packet.readBoolean()) { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() == null) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceCancelEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceCancelEvent.java index bc6bd3d2..d20b51c4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceCancelEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceCancelEvent.java @@ -5,12 +5,16 @@ import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; public class RentSpaceCancelEvent extends MessageHandler { @Override public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room == null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceEvent.java index 1df3bd21..2ea0de14 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/rentablespace/RentSpaceEvent.java @@ -4,12 +4,16 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionRentableSpace; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; public class RentSpaceEvent extends MessageHandler { @Override public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room == null) diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java index fbc5b7e1..7dab9b38 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java @@ -8,6 +8,7 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; import com.eu.habbo.messages.outgoing.rooms.items.youtube.YoutubeVideoComposer; import com.eu.habbo.threading.runnables.YoutubeAdvanceVideo; @@ -17,7 +18,10 @@ public class YoutubeRequestPlaylistChange extends MessageHandler { @Override public void handle() throws Exception { int itemId = this.packet.readInt(); - String playlistId = this.packet.readString(); + String playlistId = RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_YOUTUBE_PLAYLIST_ID_LENGTH); + + if (!RoomItemInputGuard.isPositiveId(itemId) || playlistId.isEmpty()) + return; Habbo habbo = this.client.getHabbo(); @@ -30,13 +34,16 @@ public class YoutubeRequestPlaylistChange extends MessageHandler { if (!room.isOwner(habbo) && !habbo.hasPermission(Permission.ACC_ANYROOMOWNER)) return; - HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); + HabboItem item = room.getHabboItem(itemId); if (item == null || !(item instanceof InteractionYoutubeTV)) return; Optional playlist = Emulator.getGameEnvironment().getItemManager().getYoutubeManager().getPlaylistsForItemId(item.getId()).stream().filter(p -> p.getId().equals(playlistId)).findAny(); if (playlist.isPresent()) { + if (playlist.get().getVideos().isEmpty()) + return; + YoutubeManager.YoutubeVideo video = playlist.get().getVideos().get(0); if (video == null) return; @@ -52,4 +59,4 @@ public class YoutubeRequestPlaylistChange extends MessageHandler { item.needsUpdate(true); } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java index 8b480bf5..3f95a8e3 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java @@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.items.YoutubeManager; import com.eu.habbo.habbohotel.items.interactions.InteractionYoutubeTV; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer; import com.eu.habbo.messages.outgoing.rooms.items.youtube.YoutubeDisplayListComposer; import org.slf4j.Logger; @@ -19,6 +20,9 @@ public class YoutubeRequestPlaylists extends MessageHandler { public void handle() throws Exception { int itemId = this.packet.readInt(); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) { HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); @@ -38,4 +42,4 @@ public class YoutubeRequestPlaylists extends MessageHandler { } } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java index fd3740a7..e634e198 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java @@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; import com.eu.habbo.messages.outgoing.rooms.items.youtube.YoutubeStateChangeComposer; import com.eu.habbo.messages.outgoing.rooms.items.youtube.YoutubeVideoComposer; import com.eu.habbo.threading.runnables.YoutubeAdvanceVideo; @@ -49,6 +50,9 @@ public class YoutubeRequestStateChange extends MessageHandler { int itemId = this.packet.readInt(); YoutubeState state = YoutubeState.getByState(this.packet.readInt()); + if (!RoomItemInputGuard.isPositiveId(itemId)) + return; + if (state == null) return; Habbo habbo = this.client.getHabbo(); @@ -62,7 +66,7 @@ public class YoutubeRequestStateChange extends MessageHandler { if (!room.isOwner(habbo) && !habbo.hasPermission(Permission.ACC_ANYROOMOWNER)) return; - HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); + HabboItem item = room.getHabboItem(itemId); if (!(item instanceof InteractionYoutubeTV)) return; diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuardContractTest.java new file mode 100644 index 00000000..561a6543 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/items/RoomItemInputGuardContractTest.java @@ -0,0 +1,154 @@ +package com.eu.habbo.messages.incoming.rooms.items; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RoomItemInputGuardContractTest { + private static String source(String name) throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/" + name + ".java")); + } + + @Test + void itemMutationHandlersRejectInvalidIdsBeforeRoomLookups() throws Exception { + for (String handler : new String[]{"RoomPickupItemEvent", "RotateMoveItemEvent", "UpdateFurniturePositionEvent", "MoveWallItemEvent", "ToggleFloorItemEvent", "ToggleWallItemEvent", "AdvertisingSaveEvent", "MannequinSaveNameEvent", "MannequinSaveLookEvent", "FootballGateSaveLookEvent", "PostItSaveDataEvent", "PostItPlaceEvent", "PostItDeleteEvent"}) { + String source = source(handler); + int idRead = source.indexOf("this.packet.readInt()"); + int guard = source.indexOf("RoomItemInputGuard.isPositiveId", idRead); + int lookup = source.indexOf("getHabboItem", guard); + + assertTrue(guard > idRead, handler + " should validate item ids after reading them"); + assertTrue(lookup == -1 || guard < lookup, handler + " should validate item ids before room item lookups"); + } + } + + @Test + void specialItemHandlersRejectInvalidIdsBeforeLookups() throws Exception { + for (String handler : new String[]{ + "rentablespace/RentSpaceEvent", + "rentablespace/RentSpaceCancelEvent", + "lovelock/LoveLockStartConfirmEvent", + "youtube/YoutubeRequestPlaylistChange", + "youtube/YoutubeRequestPlaylists", + "youtube/YoutubeRequestStateChange", + "jukebox/JukeBoxAddSoundTrackEvent", + "RedeemItemEvent", + "RedeemClothingEvent" + }) { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/" + handler + ".java")); + int idRead = source.indexOf("this.packet.readInt()"); + int guard = source.indexOf("RoomItemInputGuard.isPositiveId", idRead); + + assertTrue(guard > idRead, handler + " should validate item ids after reading them"); + } + } + + @Test + void roomPlacementParsesClientPayloadSafely() throws Exception { + String source = source("RoomPlaceItemEvent"); + + assertTrue(source.contains("RoomItemInputGuard.parseInt(values[0])"), + "item placement should parse item id without throwing on malformed packets"); + assertTrue(source.contains("values.length < 4"), + "item placement should require complete coordinate payloads"); + assertTrue(source.contains("RoomItemInputGuard.parseShort(values[1])"), + "floor placement should parse x coordinate safely"); + assertTrue(source.contains("RoomItemInputGuard.parseShort(values[2])"), + "floor placement should parse y coordinate safely"); + assertTrue(source.contains("RoomItemInputGuard.parseInt(values[3])"), + "floor placement should parse rotation safely"); + } + + @Test + void advertisingCustomValuesAreBoundedBeforeMutation() throws Exception { + String source = source("AdvertisingSaveEvent"); + + int count = source.indexOf("int count = this.packet.readInt()"); + int guard = source.indexOf("RoomItemInputGuard.isValidCustomValueCount(count)", count); + int loop = source.indexOf("for (int i = 0; i < count / 2; i++)", guard); + int mutate = source.indexOf(".values.put(key, value)", loop); + + assertTrue(guard > count && guard < loop, + "custom value pair count should be bounded before reading key/value pairs"); + assertTrue(source.contains("RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_CUSTOM_KEY_LENGTH)"), + "custom value keys should be trimmed and capped"); + assertTrue(source.contains("RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_CUSTOM_VALUE_LENGTH)"), + "custom value values should be trimmed and capped"); + assertTrue(mutate > loop, + "custom values should only mutate after bounded reads"); + } + + @Test + void stickyPoleMultiCommandPayloadIsBounded() throws Exception { + String source = source("SavePostItStickyPoleEvent"); + + int split = source.indexOf("String[] commands = this.packet.readString().split"); + int countGuard = source.indexOf("commands.length > RoomItemInputGuard.MAX_STICKY_POLE_COMMANDS", split); + int trim = source.indexOf("RoomItemInputGuard.trimToMax(command.replace", countGuard); + int execute = source.indexOf("CommandHandler.handleCommand", trim); + + assertTrue(split > -1 && countGuard > split, + "sticky-pole multi-command packets should cap command count before looping"); + assertTrue(trim > countGuard && trim < execute, + "sticky-pole multi-command packets should cap each command before execution"); + } + + @Test + void specialLookPayloadsAreValidatedBeforeMutation() throws Exception { + String football = source("FootballGateSaveLookEvent"); + String mannequinName = source("MannequinSaveNameEvent"); + String moodlight = source("MoodLightSaveSettingsEvent"); + + assertTrue(football.contains("RoomItemInputGuard.isValidGender(gender)"), + "football gates should reject unknown gender keys instead of defaulting to male"); + assertTrue(football.contains("RoomItemInputGuard.trimToMax(this.packet.readString(), RoomItemInputGuard.MAX_LOOK_LENGTH)"), + "football gate looks should be capped before persistence"); + assertTrue(mannequinName.contains("RoomItemInputGuard.trimToMax(this.packet.readString(), 32)"), + "mannequin names should be capped before extradata persistence"); + assertTrue(moodlight.contains("if (room == null)"), + "moodlight saves should null-check current room before inspecting rights"); + } + + @Test + void youtubeAndJukeboxInputsAreBoundedBeforeListAccess() throws Exception { + String playlistChange = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java")); + String jukeboxRemove = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRemoveSoundTrackEvent.java")); + String jukeboxRequest = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/items/jukebox/JukeBoxRequestPlayListEvent.java")); + + int playlistTrim = playlistChange.indexOf("RoomItemInputGuard.trimToMax"); + int emptyVideos = playlistChange.indexOf("playlist.get().getVideos().isEmpty()"); + int getFirst = playlistChange.indexOf("playlist.get().getVideos().get(0)"); + + assertTrue(playlistTrim > -1, + "youtube playlist ids should be capped before lookup"); + assertTrue(emptyVideos > playlistTrim && emptyVideos < getFirst, + "youtube playlist changes should reject empty playlists before get(0)"); + assertTrue(jukeboxRemove.contains("index < 0 || index >= room.getTraxManager().getSongs().size()"), + "jukebox remove should bound client-provided indexes before list access"); + assertTrue(jukeboxRequest.contains("if (room == null)"), + "jukebox playlist requests should null-check current room"); + } + + @Test + void helperRejectsMalformedValues() { + assertFalse(RoomItemInputGuard.isPositiveId(0)); + assertTrue(RoomItemInputGuard.isPositiveId(1)); + assertFalse(RoomItemInputGuard.isValidCustomValueCount(0)); + assertFalse(RoomItemInputGuard.isValidCustomValueCount(3)); + assertTrue(RoomItemInputGuard.isValidCustomValueCount(RoomItemInputGuard.MAX_CUSTOM_VALUE_PAIRS * 2)); + assertFalse(RoomItemInputGuard.isValidCustomValueCount((RoomItemInputGuard.MAX_CUSTOM_VALUE_PAIRS + 1) * 2)); + assertEquals(123, RoomItemInputGuard.parseInt("123")); + assertNull(RoomItemInputGuard.parseInt("abc")); + assertEquals((short) 12, RoomItemInputGuard.parseShort("12")); + assertNull(RoomItemInputGuard.parseShort("40000")); + assertTrue(RoomItemInputGuard.isValidGender("m")); + assertTrue(RoomItemInputGuard.isValidGender("F")); + assertFalse(RoomItemInputGuard.isValidGender("x")); + } +}