Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate timeout durations #7556

Merged
merged 2 commits into from
Oct 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions server/chat-plugins/announcements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,14 +96,17 @@ export const commands: ChatCommands = {
return this.add(this.tr("The announcement timer was turned off."));
}
const timeout = parseFloat(target);
if (isNaN(timeout) || timeout <= 0 || timeout > 0x7FFFFFFF) return this.errorReply(this.tr("Invalid time given."));
const timeoutMs = timeout * 60 * 1000;
if (isNaN(timeoutMs) || timeoutMs <= 0 || timeoutMs > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply(this.tr("Invalid time given."));
}
if (announcement.timeout) clearTimeout(announcement.timeout);
announcement.timeoutMins = timeout;
announcement.timeout = setTimeout(() => {
if (!room) return; // do nothing if the room doesn't exist anymore
if (announcement) announcement.end();
room.minorActivity = null;
}, (timeout * 60000));
}, timeoutMs);
room.add(`The announcement timer was turned on: the announcement will end in ${timeout} minute${Chat.plural(timeout)}.`);
this.modlog('ANNOUNCEMENT TIMER', null, `${timeout} minutes`);
return this.privateModAction(`The announcement timer was set to ${timeout} minute${Chat.plural(timeout)} by ${user.name}.`);
Expand Down
2 changes: 1 addition & 1 deletion server/chat-plugins/blackjack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,7 @@ export const commands: ChatCommands = {
if (room.game) return this.errorReply("There is already a game running in this room.");
if (room.settings.blackjackDisabled) return this.errorReply("Blackjack is currently disabled in this room.");
const autostartMinutes = target ? parseFloat(target) : 0;
if (isNaN(autostartMinutes)) {
if (isNaN(autostartMinutes) || autostartMinutes <= 0 || (autostartMinutes * 60 * 1000) > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply("Usage: /blackjack create [autostart] - where autostart is an integer");
}

Expand Down
4 changes: 3 additions & 1 deletion server/chat-plugins/poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,7 +444,9 @@ export const commands: ChatCommands = {
return this.add(this.tr("The poll timer was turned off."));
}
const timeout = parseFloat(target);
if (isNaN(timeout) || timeout <= 0 || timeout > 0x7FFFFFFF) return this.errorReply(this.tr("Invalid time given."));
if (isNaN(timeout) || timeout <= 0 || timeout > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply(this.tr("Invalid time given."));
}
if (poll.timeout) clearTimeout(poll.timeout);
poll.timeoutMins = timeout;
poll.timeout = setTimeout(() => {
Expand Down
11 changes: 7 additions & 4 deletions server/chat-plugins/scavengers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,7 @@ export class ScavengerHunt extends Rooms.RoomGame {
return true;
}

setTimer(minutes: string | number) {
minutes = Number(minutes);

setTimer(minutes: number) {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
Expand Down Expand Up @@ -1483,7 +1481,12 @@ const ScavengerCommands: ChatCommands = {
const game = room.getGame(ScavengerHunt);
if (!game) return this.errorReply(`There is no scavenger hunt currently running.`);

const result = game.setTimer(target);
const minutes = parseInt(target);
if (isNaN(minutes) || minutes <= 0 || (minutes * 60 * 1000) > Chat.MAX_TIMEOUT_DURATION) {
throw new Chat.ErrorMessage(`You must specify a timer length that is a postive number.`);
}

const result = game.setTimer(minutes);
const message = `The scavenger timer has been ${(result === 'off' ? "turned off" : `set to ${result} minutes`)}`;

room.add(message + '.');
Expand Down
4 changes: 2 additions & 2 deletions server/chat-plugins/trivia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2363,7 +2363,7 @@ const mastermindCommands: ChatCommands = {
}
const categoryName = ALL_CATEGORIES[CATEGORY_ALIASES[category] || category];
const timeout = parseInt(timeoutString);
if (isNaN(timeout) || timeout < 1) {
if (isNaN(timeout) || timeout < 1 || (timeout * 1000) > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply(this.tr`You must specify a round length of at least 1 second.`);
}

Expand All @@ -2386,7 +2386,7 @@ const mastermindCommands: ChatCommands = {
if (!target) return this.parse(`/help mastermind finals`);

const timeout = parseInt(target);
if (isNaN(timeout) || timeout < 1) {
if (isNaN(timeout) || timeout < 1 || (timeout * 1000) > Chat.MAX_TIMEOUT_DURATION) {
return this.errorReply(this.tr`You must specify a length of at least 1 second.`);
}

Expand Down
7 changes: 7 additions & 0 deletions server/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,13 @@ export const Chat = new class {
});
}
translationsLoaded = false;
/**
* As per the node.js documentation at https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_args,
* timers with durations that are too long for a 32-bit signed integer will be invoked after 1 millisecond,
* which tends to cause unexpected behavior.
*/
readonly MAX_TIMEOUT_DURATION = 2147483647;

readonly multiLinePattern = new PatternTester();

/*********************************************************
Expand Down
12 changes: 9 additions & 3 deletions server/tournaments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1659,9 +1659,12 @@ const commands: ChatCommands = {
target = 'off';
tournament.autostartcap = false;
}
const timeout = target.toLowerCase() === 'off' ? Infinity : target;
if (tournament.setAutoStartTimeout(Number(timeout) * 60 * 1000, this)) {
this.privateModAction(`The tournament auto start timer was set to ${target} by ${user.name}`);
const timeout = target.toLowerCase() === 'off' ? Infinity : Number(target) * 60 * 1000;
if (timeout <= 0 || (timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION)) {
return this.errorReply(`The automatic tournament start timer must be set to a positive number.`);
}
if (tournament.setAutoStartTimeout(timeout, this)) {
this.privateModAction(`The tournament auto start timer was set to ${target} by ${user.name}`);
this.modlog('TOUR AUTOSTART', null, timeout === Infinity ? 'off' : target);
}
}
Expand All @@ -1682,6 +1685,9 @@ const commands: ChatCommands = {
}
if (target.toLowerCase() === 'infinity' || target === '0') target = 'off';
const timeout = target.toLowerCase() === 'off' ? Infinity : Number(target) * 60 * 1000;
if (timeout <= 0 || (timeout !== Infinity && timeout > Chat.MAX_TIMEOUT_DURATION)) {
return this.errorReply(`The automatic disqualification timer must be set to a positive number.`);
}
if (timeout === tournament.autoDisqualifyTimeout) {
return this.errorReply(`The automatic tournament disqualify timer is already set to ${target} minute(s).`);
}
Expand Down