fix(housekeeping): bound staff supplied text

This commit is contained in:
simoleo89
2026-06-14 22:14:41 +02:00
parent 31027095ec
commit 93c4565660
11 changed files with 144 additions and 30 deletions
@@ -30,10 +30,10 @@ public class HousekeepingBanUserEvent extends MessageHandler {
}
int userId = this.packet.readInt();
String reason = this.packet.readString();
String reason = HousekeepingInputGuard.normalize(this.packet.readString());
int hours = this.packet.readInt();
if (userId <= 0 || hours <= 0) {
if (userId <= 0 || hours <= 0 || !HousekeepingInputGuard.isWithinLimit(reason, HousekeepingInputGuard.MAX_REASON_LENGTH)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
return;
}
@@ -46,7 +46,7 @@ public class HousekeepingBanUserEvent extends MessageHandler {
int duration = HousekeepingSanctionDuration.secondsFromHours(hours);
List<ModToolBan> bans = Emulator.getGameEnvironment().getModToolManager()
.ban(userId, this.client.getHabbo(), reason != null ? reason : "", duration, ModToolBanType.ACCOUNT, 0);
.ban(userId, this.client.getHabbo(), reason, duration, ModToolBanType.ACCOUNT, 0);
if (bans == null || bans.isEmpty()) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.ban_failed"));
@@ -60,7 +60,7 @@ public class HousekeepingBanUserEvent extends MessageHandler {
com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log(
this.client.getHabbo().getHabboInfo().getId(),
this.client.getHabbo().getHabboInfo().getUsername(),
ACTION_KEY, userId, "hours=" + hours + " reason=" + (reason != null ? reason : ""),
ACTION_KEY, userId, "hours=" + hours + " reason=" + HousekeepingInputGuard.auditValue(reason),
this.client.getHabbo().getHabboInfo().getIpLogin());
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
}
@@ -20,9 +20,9 @@ public class HousekeepingFindUserByNameEvent extends MessageHandler {
return;
}
String username = this.packet.readString();
String username = HousekeepingInputGuard.normalize(this.packet.readString());
if (username == null || username.isEmpty()) {
if (username.isEmpty() || !HousekeepingInputGuard.isWithinLimit(username, HousekeepingInputGuard.MAX_LOOKUP_LENGTH)) {
this.client.sendResponse(new HousekeepingUserDetailComposer(null));
return;
}
@@ -26,9 +26,9 @@ public class HousekeepingForceDisconnectUserEvent extends MessageHandler {
}
int userId = this.packet.readInt();
String reason = this.packet.readString();
String reason = HousekeepingInputGuard.normalize(this.packet.readString());
if (userId <= 0) {
if (userId <= 0 || !HousekeepingInputGuard.isWithinLimit(reason, HousekeepingInputGuard.MAX_REASON_LENGTH)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
return;
}
@@ -45,7 +45,7 @@ public class HousekeepingForceDisconnectUserEvent extends MessageHandler {
return;
}
if (reason != null && !reason.isEmpty()) {
if (!reason.isEmpty()) {
target.alert(reason);
}
@@ -55,7 +55,7 @@ public class HousekeepingForceDisconnectUserEvent extends MessageHandler {
com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log(
this.client.getHabbo().getHabboInfo().getId(),
this.client.getHabbo().getHabboInfo().getUsername(),
ACTION_KEY, userId, "reason=" + (reason != null ? reason : ""),
ACTION_KEY, userId, "reason=" + HousekeepingInputGuard.auditValue(reason),
this.client.getHabbo().getHabboInfo().getIpLogin());
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
@@ -0,0 +1,27 @@
package com.eu.habbo.messages.incoming.housekeeping;
final class HousekeepingInputGuard {
static final int MAX_LOOKUP_LENGTH = 64;
static final int MAX_REASON_LENGTH = 500;
static final int MAX_ALERT_LENGTH = 1000;
private HousekeepingInputGuard() {
}
static String normalize(String value) {
return value == null ? "" : value.trim();
}
static boolean isWithinLimit(String value, int maxLength) {
return value != null && value.length() <= maxLength;
}
static String auditValue(String value) {
String normalized = normalize(value)
.replace('\r', ' ')
.replace('\n', ' ')
.replace('\t', ' ');
return normalized.length() > MAX_REASON_LENGTH ? normalized.substring(0, MAX_REASON_LENGTH) : normalized;
}
}
@@ -28,9 +28,9 @@ public class HousekeepingKickUserEvent extends MessageHandler {
}
int userId = this.packet.readInt();
String reason = this.packet.readString();
String reason = HousekeepingInputGuard.normalize(this.packet.readString());
if (userId <= 0) {
if (userId <= 0 || !HousekeepingInputGuard.isWithinLimit(reason, HousekeepingInputGuard.MAX_REASON_LENGTH)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
return;
}
@@ -56,14 +56,14 @@ public class HousekeepingKickUserEvent extends MessageHandler {
Emulator.getGameEnvironment().getRoomManager().leaveRoom(target, target.getHabboInfo().getCurrentRoom());
}
if (reason != null && !reason.isEmpty()) {
if (!reason.isEmpty()) {
target.alert(reason);
}
com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log(
this.client.getHabbo().getHabboInfo().getId(),
this.client.getHabbo().getHabboInfo().getUsername(),
ACTION_KEY, userId, "reason=" + (reason != null ? reason : ""),
ACTION_KEY, userId, "reason=" + HousekeepingInputGuard.auditValue(reason),
this.client.getHabbo().getHabboInfo().getIpLogin());
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
}
@@ -28,10 +28,10 @@ public class HousekeepingMuteUserEvent extends MessageHandler {
}
int userId = this.packet.readInt();
String reason = this.packet.readString();
String reason = HousekeepingInputGuard.normalize(this.packet.readString());
int minutes = this.packet.readInt();
if (userId <= 0 || minutes <= 0) {
if (userId <= 0 || minutes <= 0 || !HousekeepingInputGuard.isWithinLimit(reason, HousekeepingInputGuard.MAX_REASON_LENGTH)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
return;
}
@@ -50,14 +50,14 @@ public class HousekeepingMuteUserEvent extends MessageHandler {
target.mute(HousekeepingSanctionDuration.secondsFromMinutes(minutes), false);
if (reason != null && !reason.isEmpty()) {
if (!reason.isEmpty()) {
target.alert(reason);
}
com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log(
this.client.getHabbo().getHabboInfo().getId(),
this.client.getHabbo().getHabboInfo().getUsername(),
ACTION_KEY, userId, "minutes=" + minutes + " reason=" + (reason != null ? reason : ""),
ACTION_KEY, userId, "minutes=" + minutes + " reason=" + HousekeepingInputGuard.auditValue(reason),
this.client.getHabbo().getHabboInfo().getIpLogin());
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
}
@@ -36,14 +36,11 @@ public class HousekeepingSearchRoomsEvent extends MessageHandler {
return;
}
String query = this.packet.readString();
String query = HousekeepingInputGuard.normalize(this.packet.readString());
boolean exactMatch = this.packet.readBoolean();
int limit = Math.min(Math.max(this.packet.readInt(), 1), HARD_LIMIT);
if (query == null) query = "";
query = query.trim();
if (query.isEmpty()) {
if (query.isEmpty() || !HousekeepingInputGuard.isWithinLimit(query, HousekeepingInputGuard.MAX_LOOKUP_LENGTH)) {
this.client.sendResponse(new HousekeepingRoomListComposer(new ArrayList<>()));
return;
}
@@ -31,13 +31,18 @@ public class HousekeepingSendHotelAlertEvent extends MessageHandler {
return;
}
String message = this.packet.readString();
String message = HousekeepingInputGuard.normalize(this.packet.readString());
if (message == null || message.trim().isEmpty()) {
if (message.isEmpty()) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.alert_empty"));
return;
}
if (!HousekeepingInputGuard.isWithinLimit(message, HousekeepingInputGuard.MAX_ALERT_LENGTH)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.input_too_long"));
return;
}
String body = message + "\r\n-" + this.client.getHabbo().getHabboInfo().getUsername();
ServerMessage broadcast = new StaffAlertWithLinkComposer(body, "").compose();
@@ -56,7 +61,7 @@ public class HousekeepingSendHotelAlertEvent extends MessageHandler {
com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log(
this.client.getHabbo().getHabboInfo().getId(),
this.client.getHabbo().getHabboInfo().getUsername(),
ACTION_KEY, 0, "reached=" + reached + " message=" + message,
ACTION_KEY, 0, "reached=" + reached + " message=" + HousekeepingInputGuard.auditValue(message),
this.client.getHabbo().getHabboInfo().getIpLogin());
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, reached, ""));
}
@@ -34,9 +34,9 @@ public class HousekeepingTradeLockUserEvent extends MessageHandler {
int userId = this.packet.readInt();
int hours = this.packet.readInt();
String reason = this.packet.readString();
String reason = HousekeepingInputGuard.normalize(this.packet.readString());
if (userId <= 0 || hours <= 0) {
if (userId <= 0 || hours <= 0 || !HousekeepingInputGuard.isWithinLimit(reason, HousekeepingInputGuard.MAX_REASON_LENGTH)) {
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, false, 0, "housekeeping.error.invalid_input"));
return;
}
@@ -69,7 +69,7 @@ public class HousekeepingTradeLockUserEvent extends MessageHandler {
if (online != null) {
online.getHabboStats().setAllowTrade(false);
if (reason != null && !reason.isEmpty()) {
if (!reason.isEmpty()) {
online.alert(reason);
}
}
@@ -77,7 +77,7 @@ public class HousekeepingTradeLockUserEvent extends MessageHandler {
com.eu.habbo.habbohotel.modtool.HousekeepingAuditLog.log(
this.client.getHabbo().getHabboInfo().getId(),
this.client.getHabbo().getHabboInfo().getUsername(),
ACTION_KEY, userId, "hours=" + hours + " lockedUntil=" + lockedUntil + " reason=" + (reason != null ? reason : ""),
ACTION_KEY, userId, "hours=" + hours + " lockedUntil=" + lockedUntil + " reason=" + HousekeepingInputGuard.auditValue(reason),
this.client.getHabbo().getHabboInfo().getIpLogin());
this.client.sendResponse(new HousekeepingActionResultComposer(ACTION_KEY, true, userId, ""));
}
@@ -0,0 +1,53 @@
package com.eu.habbo.messages.incoming.housekeeping;
import org.junit.jupiter.api.Test;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HousekeepingInputGuardContractTest {
@Test
void stringDrivenHousekeepingHandlersUseSharedLimits() throws Exception {
Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping");
for (String handler : List.of(
"HousekeepingBanUserEvent.java",
"HousekeepingForceDisconnectUserEvent.java",
"HousekeepingKickUserEvent.java",
"HousekeepingMuteUserEvent.java",
"HousekeepingTradeLockUserEvent.java",
"HousekeepingSendHotelAlertEvent.java",
"HousekeepingSearchRoomsEvent.java",
"HousekeepingFindUserByNameEvent.java"
)) {
String source = Files.readString(base.resolve(handler));
assertTrue(source.contains("HousekeepingInputGuard.normalize"),
handler + " must normalize client-provided strings before use");
assertTrue(source.contains("HousekeepingInputGuard.isWithinLimit"),
handler + " must bound client-provided strings before expensive work or broadcast");
}
}
@Test
void auditedFreeTextIsSanitizedBeforePersistence() throws Exception {
Path base = Path.of("src/main/java/com/eu/habbo/messages/incoming/housekeeping");
for (String handler : List.of(
"HousekeepingBanUserEvent.java",
"HousekeepingForceDisconnectUserEvent.java",
"HousekeepingKickUserEvent.java",
"HousekeepingMuteUserEvent.java",
"HousekeepingTradeLockUserEvent.java",
"HousekeepingSendHotelAlertEvent.java"
)) {
String source = Files.readString(base.resolve(handler));
assertTrue(source.contains("HousekeepingInputGuard.auditValue"),
handler + " must collapse control whitespace before writing free text to audit detail");
}
}
}
@@ -0,0 +1,32 @@
package com.eu.habbo.messages.incoming.housekeeping;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HousekeepingInputGuardTest {
@Test
void normalizesNullableText() {
assertEquals("", HousekeepingInputGuard.normalize(null));
assertEquals("hello", HousekeepingInputGuard.normalize(" hello "));
}
@Test
void enforcesInclusiveLengthLimits() {
assertTrue(HousekeepingInputGuard.isWithinLimit("abc", 3));
assertFalse(HousekeepingInputGuard.isWithinLimit("abcd", 3));
assertFalse(HousekeepingInputGuard.isWithinLimit(null, 3));
}
@Test
void auditValuesCollapseControlWhitespaceAndCapLength() {
String value = HousekeepingInputGuard.auditValue(" one\r\ntwo\tthree ");
assertEquals("one two three", value);
String oversized = "x".repeat(HousekeepingInputGuard.MAX_REASON_LENGTH + 1);
assertEquals(HousekeepingInputGuard.MAX_REASON_LENGTH, HousekeepingInputGuard.auditValue(oversized).length());
}
}