From c73eb491453bb325065cd5602aa0e77678163863 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Wed, 17 Jun 2026 21:45:46 +0200 Subject: [PATCH] fix(pets): bound package names --- .../rooms/pets/PetPackageNameEvent.java | 41 ++++++++++++--- .../pets/PetPackageNameContractTest.java | 52 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameEvent.java index 9fa2f4cb..3a3bfef8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameEvent.java @@ -5,12 +5,15 @@ import com.eu.habbo.habbohotel.pets.Pet; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.messages.incoming.catalog.CheckPetNameEvent; import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.incoming.rooms.items.RoomItemInputGuard; import com.eu.habbo.messages.outgoing.catalog.AlertPurchaseFailedComposer; import com.eu.habbo.messages.outgoing.rooms.UpdateStackHeightComposer; import com.eu.habbo.messages.outgoing.rooms.items.RemoveFloorItemComposer; import com.eu.habbo.messages.outgoing.rooms.pets.PetPackageNameValidationComposer; import com.eu.habbo.threading.runnables.QueryDeleteHabboItem; +import org.apache.commons.lang3.StringUtils; public class PetPackageNameEvent extends MessageHandler { @Override @@ -18,13 +21,24 @@ public class PetPackageNameEvent extends MessageHandler { int itemId = this.packet.readInt(); String name = this.packet.readString(); + if (!RoomItemInputGuard.isPositiveId(itemId)) { + return; + } + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room != null) { HabboItem item = room.getHabboItem(itemId); if (item != null) { if (item.getUserId() == this.client.getHabbo().getHabboInfo().getId()) { - if (name.matches("^[a-zA-Z0-9]*$")) { + int nameError = validatePetPackageName(name); + if (nameError == PetPackageNameValidationComposer.CLOSE_WIDGET) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile == null) { + this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR)); + return; + } + Pet pet = null; if (item.getBaseItem().getName().equalsIgnoreCase("val11_present")) { @@ -55,20 +69,19 @@ public class PetPackageNameEvent extends MessageHandler { room.placePet(pet, item.getX(), item.getY(), item.getZ(), item.getRotation()); pet.setUserId(this.client.getHabbo().getHabboInfo().getId()); pet.needsUpdate = true; - pet.getRoomUnit().setLocation(room.getLayout().getTile(item.getX(), item.getY())); + pet.getRoomUnit().setLocation(tile); pet.getRoomUnit().setZ(item.getZ()); Emulator.getThreading().run(new QueryDeleteHabboItem(item.getId())); room.removeHabboItem(item); room.sendComposer(new RemoveFloorItemComposer(item).compose()); - RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); - room.updateTile(room.getLayout().getTile(item.getX(), item.getY())); + room.updateTile(tile); room.sendComposer(new UpdateStackHeightComposer(tile.x, tile.y, tile.z, tile.relativeHeight()).compose()); item.setUserId(0); } else { this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR)); } } else { - this.client.sendResponse(new PetPackageNameValidationComposer(itemId, PetPackageNameValidationComposer.CONTAINS_INVALID_CHARS, name.replaceAll("^[a-zA-Z0-9]*$", ""))); + this.client.sendResponse(new PetPackageNameValidationComposer(itemId, nameError, name == null ? "" : name)); return; } } @@ -78,4 +91,20 @@ public class PetPackageNameEvent extends MessageHandler { this.client.sendResponse(new PetPackageNameValidationComposer(itemId, PetPackageNameValidationComposer.CLOSE_WIDGET, "")); } -} \ No newline at end of file + + static int validatePetPackageName(String name) { + if (name == null || name.length() < CheckPetNameEvent.PET_NAME_LENGTH_MINIMUM) { + return PetPackageNameValidationComposer.NAME_TOO_SHORT; + } + + if (name.length() > CheckPetNameEvent.PET_NAME_LENGTH_MAXIMUM) { + return PetPackageNameValidationComposer.NAME_TOO_LONG; + } + + if (!StringUtils.isAlphanumeric(name)) { + return PetPackageNameValidationComposer.CONTAINS_INVALID_CHARS; + } + + return PetPackageNameValidationComposer.CLOSE_WIDGET; + } +} diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameContractTest.java new file mode 100644 index 00000000..08cb726c --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameContractTest.java @@ -0,0 +1,52 @@ +package com.eu.habbo.messages.incoming.rooms.pets; + +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PetPackageNameContractTest { + private static String source() throws Exception { + return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPackageNameEvent.java")); + } + + @Test + void validatesItemIdBeforeRoomItemLookup() throws Exception { + String source = source(); + + int itemIdRead = source.indexOf("int itemId = this.packet.readInt()"); + int idGuard = source.indexOf("RoomItemInputGuard.isPositiveId(itemId)", itemIdRead); + int lookup = source.indexOf("HabboItem item = room.getHabboItem(itemId)", idGuard); + + assertTrue(itemIdRead > -1, "pet package handler should read an item id"); + assertTrue(idGuard > itemIdRead, "pet package handler should reject invalid item ids"); + assertTrue(lookup > idGuard, "item id validation must happen before room item lookup"); + } + + @Test + void validatesNameAndTileBeforeCreatingPet() throws Exception { + String source = source(); + + int nameValidation = source.indexOf("int nameError = validatePetPackageName(name)"); + int tileLookup = source.indexOf("RoomTile tile = room.getLayout().getTile", nameValidation); + int createPet = source.indexOf("createPet", tileLookup); + + assertTrue(nameValidation > -1, "pet package handler should validate package pet names"); + assertTrue(tileLookup > nameValidation, "tile lookup should happen after name validation"); + assertTrue(createPet > tileLookup, "pet creation must happen after validating the target tile"); + } + + @Test + void packageNamesUseNormalPetNamePolicy() throws Exception { + String source = source(); + + assertTrue(source.contains("CheckPetNameEvent.PET_NAME_LENGTH_MINIMUM"), + "package pet names should use the configured minimum pet-name length"); + assertTrue(source.contains("CheckPetNameEvent.PET_NAME_LENGTH_MAXIMUM"), + "package pet names should use the configured maximum pet-name length"); + assertTrue(source.contains("StringUtils.isAlphanumeric(name)"), + "package pet names should use alphanumeric validation"); + } +}