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.
This commit is contained in:
simoleo89
2026-06-13 17:37:58 +02:00
parent eb41e3afb9
commit 8e21765676
4 changed files with 54 additions and 1 deletions
@@ -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) {
@@ -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);
@@ -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) {
@@ -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");
}
}