Spaces:
Build error
Build error
File size: 2,571 Bytes
0bfe2e3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
import { isMatch, firstMatch } from 'super-regex';
import { Cache } from './cache';
import { getSimpleTextHash } from './crypto';
import { createLogger } from './logger';
import { Env } from './env';
const DEFAULT_TIMEOUT = 1000; // 1 second timeout
const regexCache = Cache.getInstance<string, RegExp>('regexCache', 1_000);
const resultCache = Cache.getInstance<string, boolean>(
'regexResultCache',
1_000_000
);
const logger = createLogger('regex');
/**
* Safely tests a regex pattern against a string with ReDoS protection
* @param pattern The regex pattern to test
* @param str The string to test against
* @param timeoutMs Optional timeout in milliseconds (default: 1000ms)
* @returns boolean indicating if the pattern matches the string
*/
export async function safeRegexTest(
pattern: string | RegExp,
str: string,
timeoutMs: number = DEFAULT_TIMEOUT
): Promise<boolean> {
const compiledPattern =
typeof pattern === 'string' ? await compileRegex(pattern) : pattern;
try {
return await resultCache.wrap(
(p: RegExp, s: string) => isMatch(p, s, { timeout: timeoutMs }),
getSimpleTextHash(`${compiledPattern.source}|${str}`),
100,
compiledPattern,
str
);
} catch (error) {
logger.error(`Regex test timed out after ${timeoutMs}ms:`, error);
return false;
}
}
// parses regex and flags, also checks for existence of a custom flag - n - for negate
export function parseRegex(pattern: string): {
regex: string;
flags: string;
} {
const regexFormatMatch = /^\/(.+)\/([gimuyn]*)$/.exec(pattern);
return regexFormatMatch
? { regex: regexFormatMatch[1], flags: regexFormatMatch[2] }
: { regex: pattern, flags: '' };
}
export async function compileRegex(
pattern: string,
bypassCache: boolean = false
): Promise<RegExp> {
let { regex, flags } = parseRegex(pattern);
// the n flag is not to be used when compiling the regex
if (flags.includes('n')) {
flags = flags.replace('n', '');
}
if (bypassCache) {
return new RegExp(regex, flags);
}
return await regexCache.wrap(
(p: string, f: string) => new RegExp(p, f || undefined),
getSimpleTextHash(`${regex}|${flags}`),
60,
regex,
flags
);
}
export async function formRegexFromKeywords(
keywords: string[]
): Promise<RegExp> {
const pattern = `/(?<![^ [(_\\-.])(${keywords
.map((filter) => filter.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'))
.map((filter) => filter.replace(/\s/g, '[ .\\-_]?'))
.join('|')})(?=[ \\)\\]_.-]|$)/i`;
return await compileRegex(pattern);
}
|