fix(items): harden item data lookups

This commit is contained in:
simoleo89
2026-06-16 21:53:39 +02:00
parent 416d0bb088
commit 1d7c5b856f
4 changed files with 163 additions and 31 deletions
@@ -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("");
}
@@ -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;
}
}
@@ -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> 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) {
@@ -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"));
}
}