diff --git a/backend/src/plugins/Utility/search.ts b/backend/src/plugins/Utility/search.ts index c369e7ba..682765ee 100644 --- a/backend/src/plugins/Utility/search.ts +++ b/backend/src/plugins/Utility/search.ts @@ -10,7 +10,7 @@ import { banSearchSignature } from "./commands/BanSearchCmd"; import { UtilityPluginType } from "./types"; import { refreshMembersIfNeeded } from "./refreshMembers"; import { getUserInfoEmbed } from "./functions/getUserInfoEmbed"; -import { allowTimeout } from "../../RegExpRunner"; +import { allowTimeout, RegExpRunner } from "../../RegExpRunner"; import { inputPatternToRegExp, InvalidRegexError } from "../../validatorUtils"; import { asyncFilter } from "../../utils/async"; import Timeout = NodeJS.Timeout; @@ -29,6 +29,29 @@ class SearchError extends Error {} type MemberSearchParams = ArgsFromSignatureOrArray; type BanSearchParams = ArgsFromSignatureOrArray; +type RegexRunner = InstanceType["exec"]; +function getOptimizedRegExpRunner(pluginData: GuildPluginData, isSafeRegex: boolean): RegexRunner { + if (isSafeRegex) { + return async (regex: RegExp, str: string) => { + if (!regex.global) { + const singleMatch = regex.exec(str); + return singleMatch ? [singleMatch] : null; + } + + const matches: RegExpExecArray[] = []; + let match: RegExpExecArray | null; + // tslint:disable-next-line:no-conditional-assignment + while ((match = regex.exec(str)) != null) { + matches.push(match); + } + + return matches.length ? matches : null; + }; + } + + return pluginData.state.regexRunner.exec.bind(pluginData.state.regexRunner); +} + export async function displaySearch( pluginData: GuildPluginData, args: MemberSearchParams, @@ -270,59 +293,51 @@ async function performMemberSearch( } if (args.query) { + let isSafeRegex = true; let queryRegex: RegExp; if (args.regex) { const flags = args["case-sensitive"] ? "" : "i"; queryRegex = inputPatternToRegExp(args.query.trimStart()); queryRegex = new RegExp(queryRegex.source, flags); + isSafeRegex = false; } else { queryRegex = new RegExp(escapeStringRegexp(args.query.trimStart()), args["case-sensitive"] ? "" : "i"); } + const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex); + if (args["status-search"]) { matchingMembers = await asyncFilter(matchingMembers, async member => { if (member.game) { - if ( - member.game.name && - (await pluginData.state.regexRunner.exec(queryRegex, member.game.name).catch(allowTimeout)) - ) { + if (member.game.name && (await execRegExp(queryRegex, member.game.name).catch(allowTimeout))) { return true; } - if ( - member.game.state && - (await pluginData.state.regexRunner.exec(queryRegex, member.game.state).catch(allowTimeout)) - ) { + if (member.game.state && (await execRegExp(queryRegex, member.game.state).catch(allowTimeout))) { return true; } - if ( - member.game.details && - (await pluginData.state.regexRunner.exec(queryRegex, member.game.details).catch(allowTimeout)) - ) { + if (member.game.details && (await execRegExp(queryRegex, member.game.details).catch(allowTimeout))) { return true; } if (member.game.assets) { if ( member.game.assets.small_text && - (await pluginData.state.regexRunner.exec(queryRegex, member.game.assets.small_text).catch(allowTimeout)) + (await execRegExp(queryRegex, member.game.assets.small_text).catch(allowTimeout)) ) { return true; } if ( member.game.assets.large_text && - (await pluginData.state.regexRunner.exec(queryRegex, member.game.assets.large_text).catch(allowTimeout)) + (await execRegExp(queryRegex, member.game.assets.large_text).catch(allowTimeout)) ) { return true; } } - if ( - member.game.emoji && - (await pluginData.state.regexRunner.exec(queryRegex, member.game.emoji.name).catch(allowTimeout)) - ) { + if (member.game.emoji && (await execRegExp(queryRegex, member.game.emoji.name).catch(allowTimeout))) { return true; } } @@ -330,12 +345,12 @@ async function performMemberSearch( }); } else { matchingMembers = await asyncFilter(matchingMembers, async member => { - if (member.nick && (await pluginData.state.regexRunner.exec(queryRegex, member.nick).catch(allowTimeout))) { + if (member.nick && (await execRegExp(queryRegex, member.nick).catch(allowTimeout))) { return true; } const fullUsername = `${member.user.username}#${member.user.discriminator}`; - if (await pluginData.state.regexRunner.exec(queryRegex, fullUsername).catch(allowTimeout)) return true; + if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true; return false; }); @@ -383,18 +398,21 @@ async function performBanSearch( let matchingBans = (await pluginData.guild.getBans()).map(x => x.user); if (args.query) { + let isSafeRegex = true; let queryRegex: RegExp; if (args.regex) { const flags = args["case-sensitive"] ? "" : "i"; queryRegex = inputPatternToRegExp(args.query.trimStart()); queryRegex = new RegExp(queryRegex.source, flags); + isSafeRegex = false; } else { queryRegex = new RegExp(escapeStringRegexp(args.query.trimStart()), args["case-sensitive"] ? "" : "i"); } + const execRegExp = getOptimizedRegExpRunner(pluginData, isSafeRegex); matchingBans = await asyncFilter(matchingBans, async user => { const fullUsername = `${user.username}#${user.discriminator}`; - if (await pluginData.state.regexRunner.exec(queryRegex, fullUsername).catch(allowTimeout)) return true; + if (await execRegExp(queryRegex, fullUsername).catch(allowTimeout)) return true; return false; }); }