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.habbohotel.items.Item;
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.HabboManager;
import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer;
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> {
private static final Logger LOGGER = LoggerFactory.getLogger(SendGift.class);
private static final int DEFAULT_MAX_MESSAGE_LENGTH = 300;
public SendGift() {
super(SendGiftJSON.class);
@@ -23,13 +18,13 @@ public class SendGift extends RCONMessage<SendGift.SendGiftJSON> {
@Override
public void handle(Gson gson, SendGiftJSON json) {
if (json.user_id < 0) {
if (json.user_id <= 0) {
this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.user_not_found").replace("%username%", json.user_id + "");
return;
}
if (json.itemid < 0) {
if (json.itemid <= 0) {
this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.not_a_number");
return;
@@ -42,50 +37,74 @@ public class SendGift extends RCONMessage<SendGift.SendGiftJSON> {
return;
}
boolean userFound;
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 (!baseItem.allowGift()) {
this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.not_found").replace("%itemid%", json.itemid + "");
return;
}
if (!userFound) {
this.status = RCONMessage.STATUS_ERROR;
this.message = Emulator.getTexts().getValue("commands.error.cmd_gift.user_not_found").replace("%username%", username);
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;
}
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();
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) {
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 {
public int user_id = -1;
@@ -96,4 +115,4 @@ public class SendGift extends RCONMessage<SendGift.SendGiftJSON> {
public String message = "";
}
}
}
@@ -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");
}
}