diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumDataEvent.java index d1d64e82..0c821d5a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumDataEvent.java @@ -8,6 +8,7 @@ import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys; import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer; +import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer; public class GuildForumDataEvent extends MessageHandler { @Override @@ -19,6 +20,11 @@ public class GuildForumDataEvent extends MessageHandler { public void handle() throws Exception { int guildId = packet.readInt(); + if (!GuildForumInputGuard.isPositiveId(guildId)) { + this.client.sendResponse(new ConnectionErrorComposer(400)); + return; + } + Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId); if (guild == null) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuard.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuard.java index 36f8876f..d81eec50 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuard.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuard.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.guilds.forums; final class GuildForumInputGuard { static final int MAX_PAGE_LIMIT = 50; + static final int MAX_THREAD_INDEX = 1000; static final int MAX_MARK_READ_BATCH = 50; private GuildForumInputGuard() { @@ -19,6 +20,10 @@ final class GuildForumInputGuard { return index >= 0 && limit > 0 && limit <= MAX_PAGE_LIMIT; } + static boolean isValidThreadIndex(int index) { + return index >= 0 && index <= MAX_THREAD_INDEX; + } + static boolean isValidMarkReadBatch(int count) { return count > 0 && count <= MAX_MARK_READ_BATCH; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumMarkAsReadEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumMarkAsReadEvent.java index fa83390b..0dfca47f 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumMarkAsReadEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumMarkAsReadEvent.java @@ -1,6 +1,9 @@ package com.eu.habbo.messages.incoming.guilds.forums; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.guilds.Guild; +import com.eu.habbo.habbohotel.guilds.GuildMember; +import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer; import org.slf4j.Logger; @@ -37,6 +40,17 @@ public class GuildForumMarkAsReadEvent extends MessageHandler { continue; } + Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId); + if (guild == null || !guild.hasForum()) { + continue; + } + + GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId); + boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q); + if (!guild.canHabboReadForum(userId, member, staff)) { + continue; + } + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement( "INSERT INTO `guild_forum_views` (`user_id`, `guild_id`, `timestamp`) VALUES (?, ?, ?) " + "ON DUPLICATE KEY UPDATE `timestamp` = ?" diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumThreadsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumThreadsEvent.java index b4c4cfe6..c7878663 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumThreadsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumThreadsEvent.java @@ -22,7 +22,7 @@ public class GuildForumThreadsEvent extends MessageHandler { int guildId = packet.readInt(); int index = packet.readInt(); - if (!GuildForumInputGuard.isPositiveId(guildId) || index < 0) { + if (!GuildForumInputGuard.isPositiveId(guildId) || !GuildForumInputGuard.isValidThreadIndex(index)) { this.client.sendResponse(new ConnectionErrorComposer(400)); return; } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardContractTest.java index d7c32f8b..11bc5330 100644 --- a/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardContractTest.java +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardContractTest.java @@ -15,6 +15,7 @@ class GuildForumInputGuardContractTest { for (String handler : List.of( "GuildForumPostThreadEvent.java", + "GuildForumDataEvent.java", "GuildForumModerateMessageEvent.java", "GuildForumModerateThreadEvent.java", "GuildForumThreadUpdateEvent.java", @@ -39,9 +40,12 @@ class GuildForumInputGuardContractTest { String settings = Files.readString(base.resolve("GuildForumUpdateSettingsEvent.java")); String moderateThread = Files.readString(base.resolve("GuildForumModerateThreadEvent.java")); String moderateMessage = Files.readString(base.resolve("GuildForumModerateMessageEvent.java")); + String threads = Files.readString(base.resolve("GuildForumThreadsEvent.java")); assertTrue(messages.contains("GuildForumInputGuard.isValidPage(index, limit)"), "thread message reads must bound index/limit before fetching comments"); + assertTrue(threads.contains("GuildForumInputGuard.isValidThreadIndex(index)"), + "thread list reads must bound the client-provided index before composing results"); assertTrue(markRead.contains("GuildForumInputGuard.isValidMarkReadBatch(count)"), "mark-as-read must bound the client-provided batch count before DB writes"); assertTrue(settings.contains("GuildForumInputGuard.isSettingsState"), @@ -59,4 +63,16 @@ class GuildForumInputGuardContractTest { assertTrue(source.contains("GuildForumInputGuard.normalize(this.packet.readString())"), "forum post subject and body should be normalized before word filtering and length checks"); } + + @Test + void markAsReadRequiresForumReadAccessBeforeWritingViews() throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumMarkAsReadEvent.java")); + + int guildLookup = source.indexOf("Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId)"); + int readGuard = source.indexOf("guild.canHabboReadForum(userId, member, staff)"); + int insert = source.indexOf("INSERT INTO `guild_forum_views`"); + + assertTrue(guildLookup > -1 && readGuard > guildLookup && readGuard < insert, + "mark-as-read should confirm the user can read the forum before inserting view rows"); + } } diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardTest.java index 47189ba6..e39bff1c 100644 --- a/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardTest.java +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/guilds/forums/GuildForumInputGuardTest.java @@ -21,6 +21,8 @@ class GuildForumInputGuardTest { assertFalse(GuildForumInputGuard.isValidPage(0, 0)); assertTrue(GuildForumInputGuard.isValidPage(0, GuildForumInputGuard.MAX_PAGE_LIMIT)); assertFalse(GuildForumInputGuard.isValidPage(0, GuildForumInputGuard.MAX_PAGE_LIMIT + 1)); + assertTrue(GuildForumInputGuard.isValidThreadIndex(GuildForumInputGuard.MAX_THREAD_INDEX)); + assertFalse(GuildForumInputGuard.isValidThreadIndex(GuildForumInputGuard.MAX_THREAD_INDEX + 1)); } @Test