mirror of
https://github.com/fosrl/pangolin.git
synced 2026-10-25 21:19:04 +01:00
fix(server): avoid exponential backtracking in isPathAllowed (#3862)
This commit is contained in:
+32
-50
@@ -1,5 +1,3 @@
|
|||||||
const MAX_RECURSION_DEPTH = 100;
|
|
||||||
|
|
||||||
const segmentRegexCache = new Map<string, RegExp>();
|
const segmentRegexCache = new Map<string, RegExp>();
|
||||||
|
|
||||||
function getSegmentRegex(patternPart: string): RegExp {
|
function getSegmentRegex(patternPart: string): RegExp {
|
||||||
@@ -56,55 +54,39 @@ export function isPathAllowed(pattern: string, path: string): boolean {
|
|||||||
const patternParts = decodeAndResolvePath(pattern);
|
const patternParts = decodeAndResolvePath(pattern);
|
||||||
const pathParts = decodeAndResolvePath(path);
|
const pathParts = decodeAndResolvePath(path);
|
||||||
|
|
||||||
function matchSegments(
|
// Iterative dynamic-programming matcher. The previous recursive
|
||||||
patternIndex: number,
|
// implementation forked on every bare `*`, which backtracked
|
||||||
pathIndex: number,
|
// exponentially and let an unauthenticated path rule pin the event loop.
|
||||||
depth: number = 0
|
// `dp[i][j]` is true when patternParts[i..] matches pathParts[j..].
|
||||||
): boolean {
|
const m = patternParts.length;
|
||||||
if (depth > MAX_RECURSION_DEPTH) {
|
const n = pathParts.length;
|
||||||
return false;
|
const dp: boolean[][] = Array.from({ length: m + 1 }, () =>
|
||||||
}
|
new Array<boolean>(n + 1).fill(false)
|
||||||
|
);
|
||||||
|
|
||||||
const currentPatternPart = patternParts[patternIndex];
|
dp[m][n] = true;
|
||||||
const currentPathPart = pathParts[pathIndex];
|
for (let j = 0; j < n; j++) {
|
||||||
|
dp[m][j] = false;
|
||||||
if (patternIndex >= patternParts.length) {
|
}
|
||||||
return pathIndex >= pathParts.length;
|
for (let i = m - 1; i >= 0; i--) {
|
||||||
}
|
dp[i][n] = patternParts.slice(i).every((p) => p === "*");
|
||||||
|
|
||||||
if (pathIndex >= pathParts.length) {
|
|
||||||
return patternParts.slice(patternIndex).every((p) => p === "*");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPatternPart === "*") {
|
|
||||||
if (matchSegments(patternIndex + 1, pathIndex, depth + 1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (matchSegments(patternIndex, pathIndex + 1, depth + 1)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPatternPart.includes("*")) {
|
|
||||||
const regex = getSegmentRegex(currentPatternPart);
|
|
||||||
|
|
||||||
if (regex.test(currentPathPart)) {
|
|
||||||
return matchSegments(
|
|
||||||
patternIndex + 1,
|
|
||||||
pathIndex + 1,
|
|
||||||
depth + 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentPatternPart !== currentPathPart) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return matchSegments(patternIndex + 1, pathIndex + 1, depth + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return matchSegments(0, 0, 0);
|
for (let i = m - 1; i >= 0; i--) {
|
||||||
|
const currentPatternPart = patternParts[i];
|
||||||
|
for (let j = n - 1; j >= 0; j--) {
|
||||||
|
if (currentPatternPart === "*") {
|
||||||
|
// match zero segments, or consume one path segment
|
||||||
|
dp[i][j] = dp[i + 1][j] || dp[i][j + 1];
|
||||||
|
} else if (currentPatternPart.includes("*")) {
|
||||||
|
const regex = getSegmentRegex(currentPatternPart);
|
||||||
|
dp[i][j] = regex.test(pathParts[j]) && dp[i + 1][j + 1];
|
||||||
|
} else {
|
||||||
|
dp[i][j] =
|
||||||
|
currentPatternPart === pathParts[j] && dp[i + 1][j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp[0][0];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user