diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/AlertUser.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/AlertUser.java index b5a23427..03ecfb28 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/AlertUser.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/AlertUser.java @@ -3,6 +3,9 @@ package com.eu.habbo.messages.rcon; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; public class AlertUser extends RCONMessage { @@ -16,6 +19,7 @@ public class AlertUser extends RCONMessage { if (habbo != null) { habbo.alert(object.message); + return; } this.status = RCONMessage.HABBO_NOT_FOUND; @@ -23,9 +27,12 @@ public class AlertUser extends RCONMessage { static class JSONAlertUser { + @Positive(message = "invalid user") int user_id; + @NotBlank(message = "invalid message") + @Size(max = 4096, message = "invalid message") String message; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/ForwardUser.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/ForwardUser.java index b6f18b6a..db3c98a4 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/ForwardUser.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/ForwardUser.java @@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; public class ForwardUser extends RCONMessage { @@ -26,8 +27,10 @@ public class ForwardUser extends RCONMessage { habbo.getClient().sendResponse(new ForwardToRoomComposer(object.room_id)); Emulator.getGameEnvironment().getRoomManager().enterRoom(habbo, object.room_id, "", true); + return; } else { this.status = RCONMessage.ROOM_NOT_FOUND; + return; } } @@ -36,9 +39,11 @@ public class ForwardUser extends RCONMessage { static class ForwardUserJSON { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid room") public int room_id; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/FriendRequest.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/FriendRequest.java index 59d0bf3b..d70dd794 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/FriendRequest.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/FriendRequest.java @@ -10,6 +10,7 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; import com.eu.habbo.messages.outgoing.friends.FriendRequestComposer; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; public class FriendRequest extends RCONMessage { public FriendRequest() { @@ -18,6 +19,18 @@ public class FriendRequest extends RCONMessage { @Override public void handle(Gson gson, JSON json) { + if (json.user_id == json.target_id) { + this.status = RCONMessage.STATUS_ERROR; + this.message = "cannot friend self"; + return; + } + + if (!RconUserLookup.userExists(json.user_id) || !RconUserLookup.userExists(json.target_id)) { + this.status = RCONMessage.HABBO_NOT_FOUND; + this.message = "user not found"; + return; + } + if (!Messenger.friendRequested(json.user_id, json.target_id)) { Messenger.makeFriendRequest(json.user_id, json.target_id); @@ -49,9 +62,11 @@ public class FriendRequest extends RCONMessage { static class JSON { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid target") public int target_id; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/IgnoreUser.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/IgnoreUser.java index 852a1764..7fc4ff56 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/IgnoreUser.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/IgnoreUser.java @@ -3,6 +3,7 @@ package com.eu.habbo.messages.rcon; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -19,13 +20,25 @@ public class IgnoreUser extends RCONMessage { @Override public void handle(Gson gson, JSONIgnoreUser object) { + if (object.user_id == object.target_id) { + this.status = RCONMessage.STATUS_ERROR; + this.message = "cannot ignore self"; + return; + } + + if (!RconUserLookup.userExists(object.user_id) || !RconUserLookup.userExists(object.target_id)) { + this.status = RCONMessage.HABBO_NOT_FOUND; + this.message = "user not found"; + return; + } + Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(object.user_id); if (habbo != null) { habbo.getHabboStats().ignoreUser(habbo.getClient(), object.target_id); } else { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("INSERT INTO users_ignored (user_id, target_id) VALUES (?, ?)")) { + PreparedStatement statement = connection.prepareStatement("INSERT IGNORE INTO users_ignored (user_id, target_id) VALUES (?, ?)")) { statement.setInt(1, object.user_id); statement.setInt(2, object.target_id); statement.execute(); @@ -39,8 +52,10 @@ public class IgnoreUser extends RCONMessage { static class JSONIgnoreUser { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid target") public int target_id; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/StalkUser.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/StalkUser.java index f72e5036..eed597db 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/StalkUser.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/StalkUser.java @@ -4,6 +4,7 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer; import com.google.gson.Gson; +import jakarta.validation.constraints.Positive; public class StalkUser extends RCONMessage { public StalkUser() { @@ -44,14 +45,19 @@ public class StalkUser extends RCONMessage { if (this.status == 0) { habbo.getClient().sendResponse(new ForwardToRoomComposer(target.getHabboInfo().getCurrentRoom().getId())); } + } else { + this.status = HABBO_NOT_FOUND; + this.message = "offline"; } } static class StalkUserJSON { + @Positive(message = "invalid user") public int user_id; + @Positive(message = "invalid target") public int follow_id; } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/TalkUser.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/TalkUser.java index 1f190cca..0c09d0a9 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/rcon/TalkUser.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/TalkUser.java @@ -4,6 +4,9 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; import com.eu.habbo.habbohotel.users.Habbo; import com.google.gson.Gson; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Positive; +import jakarta.validation.constraints.Size; public class TalkUser extends RCONMessage { public TalkUser() { @@ -38,15 +41,20 @@ public class TalkUser extends RCONMessage { static class JSON { + @NotBlank(message = "invalid type") + @Size(max = 16, message = "invalid type") public String type; + @Positive(message = "invalid user") public int user_id; public int bubble_id = -1; + @NotBlank(message = "invalid message") + @Size(max = 512, message = "invalid message") public String message; } } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/rcon/SocialRoomCommandGuardTest.java b/Emulator/src/test/java/com/eu/habbo/messages/rcon/SocialRoomCommandGuardTest.java new file mode 100644 index 00000000..1a743a10 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/rcon/SocialRoomCommandGuardTest.java @@ -0,0 +1,66 @@ +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 SocialRoomCommandGuardTest { + @Test + void forwardUserDoesNotOverwriteSuccessfulStatusWithNotFound() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/ForwardUser.java")); + + assertTrue(source.contains("enterRoom(habbo, object.room_id, \"\", true);") && source.contains("return;"), + "ForwardUser must return after a successful room forward instead of falling through to HABBO_NOT_FOUND"); + assertTrue(source.contains("@Positive(message = \"invalid user\")"), + "ForwardUser must reject invalid user ids before execution"); + assertTrue(source.contains("@Positive(message = \"invalid room\")"), + "ForwardUser must reject invalid room ids before execution"); + } + + @Test + void alertUserOnlyReportsNotFoundWhenTargetIsMissing() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/AlertUser.java")); + + assertTrue(source.contains("habbo.alert(object.message);") && source.contains("return;"), + "AlertUser must return after delivering the alert"); + assertTrue(source.contains("@NotBlank(message = \"invalid message\")"), + "AlertUser must reject blank alerts before execution"); + assertTrue(source.contains("@Size(max = 4096"), + "AlertUser must bound alert payload size"); + } + + @Test + void friendAndIgnoreRequestsValidateBothUsers() throws Exception { + String friend = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/FriendRequest.java")); + String ignore = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/IgnoreUser.java")); + + assertTrue(friend.contains("json.user_id == json.target_id"), + "FriendRequest must reject self-friend requests"); + assertTrue(friend.contains("RconUserLookup.userExists(json.user_id)") && friend.contains("RconUserLookup.userExists(json.target_id)"), + "FriendRequest must reject missing source or target users"); + assertTrue(ignore.contains("object.user_id == object.target_id"), + "IgnoreUser must reject self-ignore requests"); + assertTrue(ignore.contains("RconUserLookup.userExists(object.user_id)") && ignore.contains("RconUserLookup.userExists(object.target_id)"), + "IgnoreUser must reject missing source or target users"); + assertTrue(ignore.contains("INSERT IGNORE INTO users_ignored"), + "IgnoreUser offline writes must avoid duplicate rows"); + } + + @Test + void talkAndStalkPayloadsAreValidated() throws Exception { + String talk = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/TalkUser.java")); + String stalk = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/rcon/StalkUser.java")); + + assertTrue(talk.contains("@NotBlank(message = \"invalid type\")"), + "TalkUser must reject blank talk types before execution"); + assertTrue(talk.contains("@Size(max = 512"), + "TalkUser must bound impersonated chat message size"); + assertTrue(stalk.contains("@Positive(message = \"invalid target\")"), + "StalkUser must reject invalid target ids before execution"); + assertTrue(stalk.contains("this.status = HABBO_NOT_FOUND"), + "StalkUser must report missing source users"); + } +}