From 8e21765676b1b9a52becb7ee795695b2cf530ea0 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 13 Jun 2026 17:37:58 +0200 Subject: [PATCH] fix(polls): scope answers to active room poll Require poll answer, cancel, and question-data packets to match the poll configured on the caller's current room. Previously a crafted packet could target any loaded poll id and submit the final question directly, including badge-reward polls, without being in a room where that poll was active. Keep word quiz handling null-safe and add a contract test covering current-room poll scoping for all poll handlers. --- .../incoming/polls/AnswerPollEvent.java | 11 ++++++- .../incoming/polls/CancelPollEvent.java | 5 +++ .../incoming/polls/GetPollDataEvent.java | 6 ++++ .../polls/PollRoomScopeContractTest.java | 33 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 Emulator/src/test/java/com/eu/habbo/messages/incoming/polls/PollRoomScopeContractTest.java diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/AnswerPollEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/AnswerPollEvent.java index 180b0a29..a419f63d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/AnswerPollEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/AnswerPollEvent.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.polls; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.polls.Poll; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboBadge; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.users.AddUserBadgeComposer; @@ -31,12 +32,20 @@ public class AnswerPollEvent extends MessageHandler { if(answer.length() <= 0) return; if (pollId == 0 && questionId <= 0) { - this.client.getHabbo().getHabboInfo().getCurrentRoom().handleWordQuiz(this.client.getHabbo(), answer.toString()); + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room != null) { + room.handleWordQuiz(this.client.getHabbo(), answer.toString()); + } return; } answer = new StringBuilder(answer.substring(1)); + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null || room.getPollId() != pollId) { + return; + } + Poll poll = Emulator.getGameEnvironment().getPollManager().getPoll(pollId); if (poll != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/CancelPollEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/CancelPollEvent.java index a38b5a03..3d261fe3 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/CancelPollEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/CancelPollEvent.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.polls; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.polls.Poll; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.messages.incoming.MessageHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,6 +18,10 @@ public class CancelPollEvent extends MessageHandler { public void handle() throws Exception { int pollId = this.packet.readInt(); + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null || room.getPollId() != pollId) { + return; + } Poll poll = Emulator.getGameEnvironment().getPollManager().getPoll(pollId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/GetPollDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/GetPollDataEvent.java index 491b20d1..e16bd0d8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/GetPollDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/polls/GetPollDataEvent.java @@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.polls; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.polls.Poll; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.polls.PollQuestionsComposer; @@ -10,6 +11,11 @@ public class GetPollDataEvent extends MessageHandler { public void handle() throws Exception { int pollId = this.packet.readInt(); + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + if (room == null || room.getPollId() != pollId) { + return; + } + Poll poll = Emulator.getGameEnvironment().getPollManager().getPoll(pollId); if (poll != null) { diff --git a/Emulator/src/test/java/com/eu/habbo/messages/incoming/polls/PollRoomScopeContractTest.java b/Emulator/src/test/java/com/eu/habbo/messages/incoming/polls/PollRoomScopeContractTest.java new file mode 100644 index 00000000..32fa2668 --- /dev/null +++ b/Emulator/src/test/java/com/eu/habbo/messages/incoming/polls/PollRoomScopeContractTest.java @@ -0,0 +1,33 @@ +package com.eu.habbo.messages.incoming.polls; + +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 PollRoomScopeContractTest { + @Test + void pollHandlersRequireMatchingCurrentRoomPoll() throws Exception { + assertRequiresMatchingRoomPoll("AnswerPollEvent.java"); + assertRequiresMatchingRoomPoll("CancelPollEvent.java"); + assertRequiresMatchingRoomPoll("GetPollDataEvent.java"); + } + + private void assertRequiresMatchingRoomPoll(String fileName) throws Exception { + String source = Files.readString(Path.of("src/main/java/com/eu/habbo/messages/incoming/polls/" + fileName)); + int packetPollId = source.indexOf("int pollId = this.packet.readInt();"); + int pollLookup = source.indexOf("getPoll(pollId)"); + + assertTrue(packetPollId >= 0, fileName + " must read the poll id from the packet"); + assertTrue(pollLookup >= 0, fileName + " must look up the requested poll explicitly"); + + String guardedSection = source.substring(packetPollId, pollLookup); + + assertTrue(guardedSection.contains("getCurrentRoom()"), + fileName + " must bind poll actions to the caller's current room"); + assertTrue(guardedSection.contains("room == null || room.getPollId() != pollId"), + fileName + " must reject poll ids that are not active in the current room"); + } +}