From 1d7c5b856fc3849e5a1cfe082a6dc8a67139c1b7 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Tue, 16 Jun 2026 21:53:39 +0200 Subject: [PATCH] fix(items): harden item data lookups --- .../com/eu/habbo/habbohotel/items/Item.java | 47 ++++++----- .../habbo/habbohotel/items/ItemDataGuard.java | 82 +++++++++++++++++++ .../habbo/habbohotel/items/ItemManager.java | 39 +++++++-- .../habbohotel/items/ItemDataGuardTest.java | 26 ++++++ 4 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemDataGuard.java create mode 100644 Emulator/src/test/java/com/eu/habbo/habbohotel/items/ItemDataGuardTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java index 12478e75..063fbc1b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java @@ -45,7 +45,7 @@ public class Item implements ISerialize { } public static boolean isPet(Item item) { - return item.getName().toLowerCase().startsWith("a0 pet"); + return item != null && item.getName() != null && item.getName().toLowerCase().startsWith("a0 pet"); } public static boolean isBot(Item item) { @@ -121,26 +121,19 @@ public class Item implements ISerialize { this.customParams = set.getString("customparams"); this.clothingOnWalk = set.getString("clothing_on_walk"); - if (!set.getString("vending_ids").isEmpty()) { + int[] vendingIds = ItemDataGuard.parsePositiveIntList(set.getString("vending_ids")); + if (vendingIds.length > 0) { this.vendingItems = new TIntArrayList(); - String[] vendingIds = set.getString("vending_ids").replace(";", ",").replace(".", ",").split(","); - for (String s : vendingIds) { - this.vendingItems.add(Integer.parseInt(s.replace(" ", ""))); + for (int vendingId : vendingIds) { + this.vendingItems.add(vendingId); } + } else { + this.vendingItems = new TIntArrayList(); } //if(this.interactionType.getType() == InteractionMultiHeight.class || this.interactionType.getType().isAssignableFrom(InteractionMultiHeight.class)) { - if (set.getString("multiheight").contains(";")) { - String[] s = set.getString("multiheight").split(";"); - this.multiHeights = new double[s.length]; - - for (int i = 0; i < s.length; i++) { - this.multiHeights[i] = Double.parseDouble(s[i]); - } - } else { - this.multiHeights = new double[0]; - } + this.multiHeights = ItemDataGuard.parseHeights(set.getString("multiheight")); } this.rotations = 4; @@ -254,6 +247,10 @@ public class Item implements ISerialize { } public int getRandomVendingItem() { + if (this.vendingItems == null || this.vendingItems.isEmpty()) { + return 0; + } + return this.vendingItems.get(Emulator.getRandom().nextInt(this.vendingItems.size())); } @@ -273,21 +270,23 @@ public class Item implements ISerialize { @Override public void serialize(ServerMessage message) { - message.appendString(this.type.code.toLowerCase()); + message.appendString(this.type == null ? "" : this.type.code.toLowerCase()); if (type == FurnitureType.BADGE) { - message.appendString(this.customParams); + message.appendString(ItemDataGuard.safeString(this.customParams)); } else { message.appendInt(this.spriteId); - if (this.getName().contains("wallpaper_single") || this.getName().contains("floor_single") || this.getName().contains("landscape_single")) { - message.appendString(this.name.split("_")[2]); + String itemName = ItemDataGuard.safeString(this.getName()); + if (itemName.contains("wallpaper_single") || itemName.contains("floor_single") || itemName.contains("landscape_single")) { + String[] nameParts = itemName.split("_"); + message.appendString(nameParts.length > 2 ? nameParts[2] : ""); } else if (type == FurnitureType.ROBOT) { - message.appendString(this.customParams); - } else if (name.equalsIgnoreCase("poster")) { - message.appendString(this.customParams); - } else if (name.startsWith("SONG ")) { - message.appendString(this.customParams); + message.appendString(ItemDataGuard.safeString(this.customParams)); + } else if (itemName.equalsIgnoreCase("poster")) { + message.appendString(ItemDataGuard.safeString(this.customParams)); + } else if (itemName.startsWith("SONG ")) { + message.appendString(ItemDataGuard.safeString(this.customParams)); } else { message.appendString(""); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemDataGuard.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemDataGuard.java new file mode 100644 index 00000000..446a55e8 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemDataGuard.java @@ -0,0 +1,82 @@ +package com.eu.habbo.habbohotel.items; + +final class ItemDataGuard { + static final int MAX_EXTRA_DATA_LENGTH = 1000; + + private ItemDataGuard() { + } + + static String safeString(String value) { + return value == null ? "" : value; + } + + static String normalizeExtraData(String value) { + String safe = safeString(value); + return safe.length() > MAX_EXTRA_DATA_LENGTH ? safe.substring(0, MAX_EXTRA_DATA_LENGTH) : safe; + } + + static int parsePositiveInt(String value) { + try { + int parsed = Integer.parseInt(safeString(value).trim()); + return parsed > 0 ? parsed : 0; + } catch (NumberFormatException e) { + return 0; + } + } + + static int[] parsePositiveIntList(String value) { + String safe = safeString(value).replace(";", ",").replace(".", ","); + if (safe.isBlank()) { + return new int[0]; + } + + String[] parts = safe.split(","); + int[] parsed = new int[parts.length]; + int count = 0; + + for (String part : parts) { + int id = parsePositiveInt(part); + if (id > 0) { + parsed[count++] = id; + } + } + + if (count == parsed.length) { + return parsed; + } + + int[] compact = new int[count]; + System.arraycopy(parsed, 0, compact, 0, count); + return compact; + } + + static double[] parseHeights(String value) { + String safe = safeString(value); + if (safe.isBlank() || !safe.contains(";")) { + return new double[0]; + } + + String[] parts = safe.split(";"); + double[] parsed = new double[parts.length]; + int count = 0; + + for (String part : parts) { + try { + double height = Double.parseDouble(part.trim()); + if (Double.isFinite(height)) { + parsed[count++] = height; + } + } catch (NumberFormatException e) { + // Ignore malformed DB values and keep the remaining heights usable. + } + } + + if (count == parsed.length) { + return parsed; + } + + double[] compact = new double[count]; + System.arraycopy(parsed, 0, compact, 0, count); + return compact; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java index 9280c25d..d2b40685 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java @@ -566,6 +566,10 @@ public class ItemManager { public int calculateCrackState(int count, int max, Item baseItem) { + if (count <= 0 || max <= 0 || baseItem == null || baseItem.getStateCount() <= 0) { + return 0; + } + return (int) Math.floor((1.0D / ((double) max / (double) count) * baseItem.getStateCount())); } @@ -574,7 +578,8 @@ public class ItemManager { } public Item getCrackableReward(int itemId) { - return this.getItem(this.crackableRewards.get(itemId).getRandomReward()); + CrackableReward reward = this.crackableRewards.get(itemId); + return reward == null ? null : this.getItem(reward.getRandomReward()); } @@ -604,6 +609,12 @@ public class ItemManager { } public HabboItem createItem(int habboId, Item item, int limitedStack, int limitedSells, String extraData) { + if (habboId <= 0 || item == null) { + return null; + } + + extraData = ItemDataGuard.normalizeExtraData(extraData); + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO items (user_id, item_id, extra_data, limited_data) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS)) { statement.setInt(1, habboId); statement.setInt(2, item.getId()); @@ -673,6 +684,12 @@ public class ItemManager { } public HabboItem handleRecycle(Habbo habbo, String itemId) { + int rewardItemId = ItemDataGuard.parsePositiveInt(itemId); + if (habbo == null || habbo.getHabboInfo() == null || rewardItemId <= 0 + || Emulator.getGameEnvironment().getCatalogManager().ecotronItem == null) { + return null; + } + String extradata = Calendar.getInstance().get(Calendar.DAY_OF_MONTH) + "-" + (Calendar.getInstance().get(Calendar.MONTH) + 1) + "-" + Calendar.getInstance().get(Calendar.YEAR); HabboItem item = null; @@ -686,7 +703,7 @@ public class ItemManager { try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents (item_id, base_item_reward) VALUES (?, ?)")) { while (set.next() && item == null) { preparedStatement.setInt(1, set.getInt(1)); - preparedStatement.setInt(2, Integer.parseInt(itemId)); + preparedStatement.setInt(2, rewardItemId); preparedStatement.addBatch(); item = new InteractionDefault(set.getInt(1), habbo.getHabboInfo().getId(), Emulator.getGameEnvironment().getCatalogManager().ecotronItem, extradata, 0, 0); } @@ -829,6 +846,10 @@ public class ItemManager { } public HabboItem createGift(String username, Item item, String extraData, int limitedStack, int limitedSells) { + if (username == null || username.isBlank() || item == null) { + return null; + } + Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(username); int userId = 0; @@ -857,13 +878,13 @@ public class ItemManager { } public HabboItem createGift(int userId, Item item, String extraData, int limitedStack, int limitedSells) { - if (userId == 0) + if (userId <= 0 || item == null) return null; - if (extraData.length() > 1000) { + if (extraData != null && extraData.length() > ItemDataGuard.MAX_EXTRA_DATA_LENGTH) { LOGGER.error("Extradata exceeds maximum length of 1000 characters: {}", extraData); - extraData = extraData.substring(0, 1000); } + extraData = ItemDataGuard.normalizeExtraData(extraData); HabboItem gift = this.createItem(userId, item, limitedStack, limitedSells, extraData); @@ -879,7 +900,7 @@ public class ItemManager { } public Item getItem(int itemId) { - if (itemId < 0) + if (itemId <= 0) return null; return this.items.get(itemId); @@ -890,12 +911,16 @@ public class ItemManager { } public Item getItem(String itemName) { + if (itemName == null || itemName.isBlank()) { + return null; + } + TIntObjectIterator item = this.items.iterator(); for (int i = this.items.size(); i-- > 0; ) { try { item.advance(); - if (item.value().getName().equalsIgnoreCase(itemName)) { + if (item.value() != null && item.value().getName() != null && item.value().getName().equalsIgnoreCase(itemName)) { return item.value(); } } catch (NoSuchElementException e) { diff --git a/Emulator/src/test/java/com/eu/habbo/habbohotel/items/ItemDataGuardTest.java b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/ItemDataGuardTest.java new file mode 100644 index 00000000..cb978fed --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/habbohotel/items/ItemDataGuardTest.java @@ -0,0 +1,26 @@ +package com.eu.habbo.habbohotel.items; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ItemDataGuardTest { + + @Test + void normalizesExtraDataToDatabaseBound() { + assertEquals("", ItemDataGuard.normalizeExtraData(null)); + assertEquals(ItemDataGuard.MAX_EXTRA_DATA_LENGTH, + ItemDataGuard.normalizeExtraData("x".repeat(ItemDataGuard.MAX_EXTRA_DATA_LENGTH + 1)).length()); + } + + @Test + void parsesOnlyPositiveVendingIds() { + assertArrayEquals(new int[]{1, 2, 3}, ItemDataGuard.parsePositiveIntList("1; 2.bad,3,-4,0")); + } + + @Test + void ignoresMalformedMultiHeights() { + assertArrayEquals(new double[]{0.5, 1.25}, ItemDataGuard.parseHeights("0.5;nope;Infinity;1.25")); + } +}