fix(rcon): harden gift creation requests

This commit is contained in:
simoleo89
2026-06-14 17:37:47 +02:00
parent 4747699656
commit 81c8dfc605
2 changed files with 104 additions and 39 deletions
@@ -3,19 +3,14 @@ package com.eu.habbo.messages.rcon;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboInfo;
import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.users.HabboManager;
import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer; import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
import com.google.gson.Gson; import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class SendGift extends RCONMessage<SendGift.SendGiftJSON> { public class SendGift extends RCONMessage<SendGift.SendGiftJSON> {
private static final Logger LOGGER = LoggerFactory.getLogger(SendGift.class); private static final int DEFAULT_MAX_MESSAGE_LENGTH = 300;
public SendGift() { public SendGift() {
super(SendGiftJSON.class); super(SendGiftJSON.class);
@@ -23,13 +18,13 @@ public class SendGift extends RCONMessage<SendGift.SendGiftJSON> {
@Override @Override
public void handle(Gson gson, SendGiftJSON json) { public void handle(Gson gson, SendGiftJSON json) {
if (json.user_id < 0) { if (json.user_id <= 0) {
this.status = RCONMessage.STATUS_ERROR; this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.user_not_found").replace("%username%", json.user_id + ""); this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.user_not_found").replace("%username%", json.user_id + "");
return; return;
} }
if (json.itemid < 0) { if (json.itemid <= 0) {
this.status = RCONMessage.STATUS_ERROR; this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.not_a_number"); this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.not_a_number");
return; return;
@@ -42,50 +37,74 @@ public class SendGift extends RCONMessage<SendGift.SendGiftJSON> {
return; return;
} }
boolean userFound; if (!baseItem.allowGift()) {
Habbo habbo;
habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(json.user_id);
userFound = habbo != null;
String username = "";
if (!userFound) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE id = ? LIMIT 1")) {
statement.setInt(1, json.user_id);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
username = set.getString("username");
userFound = true;
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
} else {
username = habbo.getHabboInfo().getUsername();
}
if (!userFound) {
this.status = RCONMessage.STATUS_ERROR; this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.user_not_found").replace("%username%", username); this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.not_found").replace("%itemid%", json.itemid + "");
return;
}
Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(json.user_id);
HabboInfo habboInfo = habbo != null ? habbo.getHabboInfo() : HabboManager.getOfflineHabboInfo(json.user_id);
if (habboInfo == null) {
this.status = RCONMessage.HABBO_NOT_FOUND;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.user_not_found").replace("%username%", json.user_id + "");
return; return;
} }
HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(0, baseItem, 0, 0, ""); HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(0, baseItem, 0, 0, "");
Item giftItem = Emulator.getGameEnvironment().getItemManager().getItem((Integer) Emulator.getGameEnvironment().getCatalogManager().giftFurnis.values().toArray()[Emulator.getRandom().nextInt(Emulator.getGameEnvironment().getCatalogManager().giftFurnis.size())]); Item giftItem = this.randomGiftItem();
if (item == null || giftItem == null) {
this.status = RCONMessage.SYSTEM_ERROR;
this.message = "gift configuration unavailable";
return;
}
String extraData = "1\t" + item.getId(); String extraData = "1\t" + item.getId();
extraData += "\t0\t0\t0\t" + json.message + "\t0\t0"; extraData += "\t0\t0\t0\t" + sanitizeGiftMessage(json.message) + "\t0\t0";
Emulator.getGameEnvironment().getItemManager().createGift(username, giftItem, extraData, 0, 0); if (Emulator.getGameEnvironment().getItemManager().createGift(habboInfo.getUsername(), giftItem, extraData, 0, 0) == null) {
this.status = RCONMessage.SYSTEM_ERROR;
this.message = "failed to create gift";
return;
}
this.message = Emulator.getTexts().getValue("commands.succes.cmd_gift").replace("%username%", username).replace("%itemname%", item.getBaseItem().getName()); this.message = Emulator.getTexts().getValue("commands.succes.cmd_gift").replace("%username%", habboInfo.getUsername()).replace("%itemname%", item.getBaseItem().getName());
if (habbo != null) { if (habbo != null) {
habbo.getClient().sendResponse(new InventoryRefreshComposer()); habbo.getClient().sendResponse(new InventoryRefreshComposer());
} }
} }
private Item randomGiftItem() {
synchronized (Emulator.getGameEnvironment().getCatalogManager().giftFurnis) {
int size = Emulator.getGameEnvironment().getCatalogManager().giftFurnis.size();
if (size == 0) {
return null;
}
Object[] giftIds = Emulator.getGameEnvironment().getCatalogManager().giftFurnis.values().toArray();
return Emulator.getGameEnvironment().getItemManager().getItem((Integer) giftIds[Emulator.getRandom().nextInt(size)]);
}
}
static String sanitizeGiftMessage(String message) {
int maxLength = Emulator.getConfig().getInt("hotel.gifts.length.max", DEFAULT_MAX_MESSAGE_LENGTH);
if (maxLength <= 0) {
maxLength = DEFAULT_MAX_MESSAGE_LENGTH;
}
if (message == null) {
return "";
}
String sanitized = message.replace('\t', ' ').replace('\r', ' ').replace('\n', ' ');
if (sanitized.length() > maxLength) {
return sanitized.substring(0, maxLength);
}
return sanitized;
}
static class SendGiftJSON { static class SendGiftJSON {
public int user_id = -1; public int user_id = -1;
@@ -0,0 +1,46 @@
package com.eu.habbo.messages.rcon;
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 SendGiftContractTest {
private static String source() throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/SendGift.java"));
}
@Test
void validatesGiftTargetsAndItemsBeforeCreatingInventoryRows() throws Exception {
String source = source();
assertTrue(source.contains("json.user_id <= 0"),
"RCON gifts must reject invalid target users");
assertTrue(source.contains("json.itemid <= 0"),
"RCON gifts must reject invalid item ids");
assertTrue(source.contains("baseItem.allowGift()"),
"RCON gifts must respect the item giftability flag");
assertTrue(source.contains("HabboManager.getOfflineHabboInfo(json.user_id)"),
"RCON gifts must resolve offline users through HabboManager");
assertTrue(source.contains("HABBO_NOT_FOUND"),
"RCON gifts must report missing users with the RCON missing-user status");
}
@Test
void sanitizesGiftMessageAndHandlesMissingGiftConfiguration() throws Exception {
String source = source();
assertTrue(source.contains("sanitizeGiftMessage(json.message)"),
"RCON gift extraData must use a sanitized message");
assertTrue(source.contains("replace('\\t', ' ').replace('\\r', ' ').replace('\\n', ' ')"),
"RCON gift messages must not inject gift extraData delimiters");
assertTrue(source.contains("hotel.gifts.length.max"),
"RCON gift messages must respect the configured gift length limit");
assertTrue(source.contains("giftFurnis.size()"),
"RCON gift creation must guard against empty gift wrapper configuration");
assertTrue(source.contains("createGift(habboInfo.getUsername()"),
"RCON gifts must create the wrapper for the canonical target username");
}
}