Merge pull request #223 from simoleo89/fix/guild-badge-guards

fix(guilds): bound guild management inputs
This commit is contained in:
DuckieTM
2026-06-17 09:56:28 +02:00
committed by GitHub
9 changed files with 170 additions and 6 deletions
@@ -0,0 +1,59 @@
package com.eu.habbo.messages.incoming.guilds;
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 GuildManagementInputGuardContractTest {
private static String source(String file) throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/guilds/" + file));
}
@Test
void guildCreateAndRenameShareNameAndDescriptionBounds() throws Exception {
String limits = source("GuildInputLimits.java");
String buy = source("RequestGuildBuyEvent.java");
String rename = source("GuildChangeNameDescEvent.java");
assertTrue(limits.contains("MAX_GUILD_NAME_LENGTH = 29"),
"Guild names should keep the existing 29 character protocol bound");
assertTrue(limits.contains("MAX_GUILD_DESCRIPTION_LENGTH = 254"),
"Guild descriptions should keep the existing database/protocol bound");
assertTrue(limits.contains("!name.isBlank()"),
"Guild names must not be empty or whitespace-only");
assertTrue(buy.contains("GuildInputLimits.isValidGuildName(name)"),
"Guild purchase should use the shared name guard");
assertTrue(buy.contains("GuildInputLimits.isValidGuildDescription(description)"),
"Guild purchase should use the shared description guard");
assertTrue(rename.contains("GuildInputLimits.isValidGuildName(newName)"),
"Guild rename should reject invalid client names before plugin events");
assertTrue(rename.contains("GuildInputLimits.isValidGuildName(nameEvent.name)"),
"Guild rename should reject invalid plugin-mutated names before persistence");
}
@Test
void guildColorInputsAreCheckedAgainstLoadedPalette() throws Exception {
String buy = source("RequestGuildBuyEvent.java");
String colors = source("GuildChangeColorsEvent.java");
assertTrue(buy.contains("symbolColor(colorOne)") && buy.contains("backgroundColor(colorTwo)"),
"Guild purchase should reject color ids that are not in the loaded guild palette");
assertTrue(colors.contains("symbolColor(colorOne)") && colors.contains("backgroundColor(colorTwo)"),
"Guild color changes should reject invalid client color ids");
assertTrue(colors.contains("symbolColor(colorsEvent.colorOne)") && colors.contains("backgroundColor(colorsEvent.colorTwo)"),
"Guild color changes should reject invalid plugin-mutated color ids");
}
@Test
void guildStateInputsStayInsideKnownEnumRange() throws Exception {
String settings = source("GuildChangeSettingsEvent.java");
assertTrue(settings.contains("state < 0 || state >= GuildState.values().length"),
"Guild settings should reject invalid client state ids before event dispatch");
assertTrue(settings.contains("settingsEvent.state < 0 || settingsEvent.state >= GuildState.values().length"),
"Guild settings should reject invalid plugin-mutated state ids before applying them");
}
}
@@ -0,0 +1,55 @@
package com.eu.habbo.messages.incoming.guilds;
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 GuildMembersInputGuardContractTest {
private static String eventSource() throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/guilds/RequestGuildMembersEvent.java"));
}
private static String managerSource() throws Exception {
return Files.readString(Path.of("src/main/java/com/eu/habbo/habbohotel/guilds/GuildManager.java"));
}
@Test
void guildMemberListInputsAreBoundedBeforeManagerQueries() throws Exception {
String source = eventSource();
int pageRead = source.indexOf("int pageId = this.packet.readInt()");
int queryRead = source.indexOf("String query = this.packet.readString()", pageRead);
int levelRead = source.indexOf("int levelId = this.packet.readInt()", queryRead);
int guard = source.indexOf("pageId < 0 || pageId > MAX_PAGE_ID", levelRead);
int managerCall = source.indexOf("getGuildMembers(g, pageId, levelId, query)", guard);
assertTrue(source.contains("MAX_PAGE_ID = 1000"),
"Guild member pagination should have a server-side upper bound");
assertTrue(source.contains("MAX_QUERY_LENGTH = 32"),
"Guild member search query should have a server-side length bound");
assertTrue(source.contains("MAX_LEVEL_ID = 2"),
"Guild member rank filter should be bounded to known levels");
assertTrue(pageRead > -1 && queryRead > pageRead && levelRead > queryRead,
"Guild member handler must read page/query/level from the packet");
assertTrue(guard > levelRead && guard < managerCall,
"Guild member handler must validate inputs before querying the manager");
}
@Test
void guildMemberCountEscapesLikeQuerySameAsListQuery() throws Exception {
String source = managerSource();
int listMethod = source.indexOf("public ArrayList<GuildMember> getGuildMembers(Guild guild, int page, int levelId, String query)");
int listEscape = source.indexOf("SqlLikeEscaper.escape(query)", listMethod);
int countMethod = source.indexOf("public int getGuildMembersCount(Guild guild, int page, int levelId, String query)");
int countEscape = source.indexOf("SqlLikeEscaper.escape(query)", countMethod);
assertTrue(listEscape > listMethod,
"Guild member list query should escape SQL LIKE wildcards");
assertTrue(countEscape > countMethod,
"Guild member count query should escape SQL LIKE wildcards too");
}
}