mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-22 15:22:12 +00:00
Compare commits
1 Commits
main
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
833422e739 |
3918
package-lock.json
generated
3918
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
106
package.json
106
package.json
@@ -35,7 +35,7 @@
|
||||
"@asteasolutions/zod-to-openapi": "8.5.0",
|
||||
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
|
||||
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.0.tgz",
|
||||
"@aws-sdk/client-s3": "3.1056.0",
|
||||
"@aws-sdk/client-s3": "3.1073.0",
|
||||
"@headlessui/react": "2.2.10",
|
||||
"@hookform/resolvers": "5.4.0",
|
||||
"@monaco-editor/react": "4.7.0",
|
||||
@@ -43,38 +43,38 @@
|
||||
"@novnc/novnc": "^1.7.0",
|
||||
"@oslojs/crypto": "1.0.1",
|
||||
"@oslojs/encoding": "1.1.0",
|
||||
"@radix-ui/react-avatar": "1.1.11",
|
||||
"@radix-ui/react-checkbox": "1.3.3",
|
||||
"@radix-ui/react-collapsible": "1.1.12",
|
||||
"@radix-ui/react-dialog": "1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.16",
|
||||
"@radix-ui/react-avatar": "1.2.0",
|
||||
"@radix-ui/react-checkbox": "1.3.5",
|
||||
"@radix-ui/react-collapsible": "1.1.14",
|
||||
"@radix-ui/react-dialog": "1.1.17",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.18",
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-label": "2.1.8",
|
||||
"@radix-ui/react-popover": "1.1.15",
|
||||
"@radix-ui/react-progress": "1.1.8",
|
||||
"@radix-ui/react-radio-group": "1.3.8",
|
||||
"@radix-ui/react-scroll-area": "1.2.10",
|
||||
"@radix-ui/react-select": "2.2.6",
|
||||
"@radix-ui/react-separator": "1.1.8",
|
||||
"@radix-ui/react-slot": "1.2.4",
|
||||
"@radix-ui/react-switch": "1.2.6",
|
||||
"@radix-ui/react-tabs": "1.1.13",
|
||||
"@radix-ui/react-toast": "1.2.15",
|
||||
"@radix-ui/react-tooltip": "1.2.8",
|
||||
"@radix-ui/react-label": "2.1.10",
|
||||
"@radix-ui/react-popover": "1.1.17",
|
||||
"@radix-ui/react-progress": "1.1.10",
|
||||
"@radix-ui/react-radio-group": "1.4.1",
|
||||
"@radix-ui/react-scroll-area": "1.2.12",
|
||||
"@radix-ui/react-select": "2.3.1",
|
||||
"@radix-ui/react-separator": "1.1.10",
|
||||
"@radix-ui/react-slot": "1.3.0",
|
||||
"@radix-ui/react-switch": "1.3.1",
|
||||
"@radix-ui/react-tabs": "1.1.15",
|
||||
"@radix-ui/react-toast": "1.2.17",
|
||||
"@radix-ui/react-tooltip": "1.2.10",
|
||||
"@react-email/body": "0.3.0",
|
||||
"@react-email/components": "1.0.12",
|
||||
"@react-email/render": "2.0.8",
|
||||
"@react-email/render": "2.0.9",
|
||||
"@react-email/tailwind": "2.0.7",
|
||||
"@simplewebauthn/browser": "13.3.0",
|
||||
"@simplewebauthn/server": "13.3.1",
|
||||
"@tailwindcss/forms": "0.5.11",
|
||||
"@tanstack/react-query": "5.100.14",
|
||||
"@tanstack/react-query": "5.101.0",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@xterm/addon-fit": "^0.11.0",
|
||||
"@xterm/addon-web-links": "^0.12.0",
|
||||
"@xterm/xterm": "^6.0.0",
|
||||
"arctic": "3.7.0",
|
||||
"axios": "1.16.1",
|
||||
"axios": "1.18.0",
|
||||
"better-sqlite3": "11.9.1",
|
||||
"canvas-confetti": "1.9.4",
|
||||
"class-variance-authority": "0.7.1",
|
||||
@@ -91,40 +91,40 @@
|
||||
"helmet": "8.2.0",
|
||||
"http-errors": "2.0.1",
|
||||
"input-otp": "1.4.2",
|
||||
"ioredis": "5.11.0",
|
||||
"ioredis": "5.11.1",
|
||||
"jmespath": "0.16.0",
|
||||
"js-yaml": "4.1.1",
|
||||
"js-yaml": "5.0.0",
|
||||
"jsonwebtoken": "9.0.3",
|
||||
"lucide-react": "1.17.0",
|
||||
"lucide-react": "1.21.0",
|
||||
"maxmind": "5.0.6",
|
||||
"moment": "2.30.1",
|
||||
"next": "16.2.6",
|
||||
"next": "16.2.9",
|
||||
"next-intl": "4.13.0",
|
||||
"next-themes": "0.4.6",
|
||||
"nextjs-toploader": "3.9.17",
|
||||
"node-cache": "5.1.2",
|
||||
"nodemailer": "8.0.9",
|
||||
"nodemailer": "9.0.1",
|
||||
"oslo": "1.2.1",
|
||||
"pg": "8.21.0",
|
||||
"posthog-node": "5.35.6",
|
||||
"pg": "8.22.0",
|
||||
"posthog-node": "5.38.2",
|
||||
"qrcode.react": "4.2.0",
|
||||
"react": "19.2.6",
|
||||
"react-day-picker": "9.14.0",
|
||||
"react-dom": "19.2.6",
|
||||
"react": "19.2.7",
|
||||
"react-day-picker": "10.0.1",
|
||||
"react-dom": "19.2.7",
|
||||
"react-easy-sort": "1.8.0",
|
||||
"react-hook-form": "7.76.1",
|
||||
"react-hook-form": "7.80.0",
|
||||
"react-icons": "5.6.0",
|
||||
"recharts": "3.8.1",
|
||||
"reodotdev": "1.1.0",
|
||||
"semver": "7.8.1",
|
||||
"semver": "7.8.5",
|
||||
"sshpk": "1.18.0",
|
||||
"stripe": "22.2.0",
|
||||
"stripe": "22.2.2",
|
||||
"swagger-ui-express": "5.0.1",
|
||||
"tailwind-merge": "3.6.0",
|
||||
"topojson-client": "3.1.0",
|
||||
"tw-animate-css": "1.4.0",
|
||||
"use-debounce": "10.1.1",
|
||||
"uuid": "14.0.0",
|
||||
"uuid": "14.0.1",
|
||||
"vaul": "1.1.2",
|
||||
"visionscarto-world-atlas": "1.0.0",
|
||||
"winston": "3.19.0",
|
||||
@@ -136,11 +136,11 @@
|
||||
"zod-validation-error": "5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@dotenvx/dotenvx": "1.69.1",
|
||||
"@dotenvx/dotenvx": "1.75.1",
|
||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||
"@react-email/ui": "^6.5.0",
|
||||
"@tailwindcss/postcss": "4.3.0",
|
||||
"@tanstack/react-query-devtools": "5.100.14",
|
||||
"@react-email/ui": "^6.6.3",
|
||||
"@tailwindcss/postcss": "4.3.1",
|
||||
"@tanstack/react-query-devtools": "5.101.0",
|
||||
"@types/better-sqlite3": "7.6.13",
|
||||
"@types/cookie-parser": "1.4.10",
|
||||
"@types/cors": "2.8.19",
|
||||
@@ -151,35 +151,35 @@
|
||||
"@types/jmespath": "0.15.2",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsonwebtoken": "9.0.10",
|
||||
"@types/node": "25.9.1",
|
||||
"@types/nodemailer": "8.0.0",
|
||||
"@types/node": "26.0.0",
|
||||
"@types/nodemailer": "8.0.1",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@types/pg": "8.20.0",
|
||||
"@types/react": "19.2.15",
|
||||
"@types/react": "19.2.17",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/semver": "7.7.1",
|
||||
"@types/sshpk": "1.17.4",
|
||||
"@types/sshpk": "1.17.5",
|
||||
"@types/swagger-ui-express": "4.1.8",
|
||||
"@types/topojson-client": "3.1.5",
|
||||
"@types/ws": "8.18.1",
|
||||
"@types/yargs": "17.0.35",
|
||||
"babel-plugin-react-compiler": "1.0.0",
|
||||
"drizzle-kit": "0.31.10",
|
||||
"esbuild": "0.28.0",
|
||||
"esbuild-node-externals": "1.22.0",
|
||||
"eslint": "10.4.0",
|
||||
"eslint-config-next": "16.2.6",
|
||||
"esbuild": "0.28.1",
|
||||
"esbuild-node-externals": "1.23.1",
|
||||
"eslint": "10.5.0",
|
||||
"eslint-config-next": "16.2.9",
|
||||
"postcss": "8.5.15",
|
||||
"prettier": "3.8.3",
|
||||
"react-email": "6.5.0",
|
||||
"tailwindcss": "4.3.0",
|
||||
"prettier": "3.8.4",
|
||||
"react-email": "6.6.3",
|
||||
"tailwindcss": "4.3.1",
|
||||
"tsc-alias": "1.8.17",
|
||||
"tsx": "4.22.3",
|
||||
"tsx": "4.22.4",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.60.0"
|
||||
"typescript-eslint": "8.61.1"
|
||||
},
|
||||
"overrides": {
|
||||
"esbuild": "0.28.0",
|
||||
"esbuild": "0.28.1",
|
||||
"dompurify": "3.4.0",
|
||||
"postcss": "8.5.15"
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
const MAX_RECURSION_DEPTH = 100;
|
||||
|
||||
const segmentRegexCache = new Map<string, RegExp>();
|
||||
|
||||
function getSegmentRegex(patternPart: string): RegExp {
|
||||
let regex = segmentRegexCache.get(patternPart);
|
||||
if (!regex) {
|
||||
const regexPattern = patternPart
|
||||
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
||||
.replace(/\*/g, ".*")
|
||||
.replace(/\?/g, ".");
|
||||
regex = new RegExp(`^${regexPattern}$`);
|
||||
segmentRegexCache.set(patternPart, regex);
|
||||
}
|
||||
return regex;
|
||||
}
|
||||
|
||||
export function isPathAllowed(pattern: string, path: string): boolean {
|
||||
const normalize = (p: string) => p.split("/").filter(Boolean);
|
||||
const patternParts = normalize(pattern);
|
||||
const pathParts = normalize(path);
|
||||
|
||||
function matchSegments(
|
||||
patternIndex: number,
|
||||
pathIndex: number,
|
||||
depth: number = 0
|
||||
): boolean {
|
||||
if (depth > MAX_RECURSION_DEPTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const currentPatternPart = patternParts[patternIndex];
|
||||
const currentPathPart = pathParts[pathIndex];
|
||||
|
||||
if (patternIndex >= patternParts.length) {
|
||||
return pathIndex >= pathParts.length;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import { assertEquals } from "@test/assert";
|
||||
import { REGIONS } from "@server/db/regions";
|
||||
import { isPathAllowed } from "@server/lib/pathMatch";
|
||||
|
||||
function isIpInRegion(
|
||||
ipCountryCode: string | undefined,
|
||||
@@ -34,6 +33,76 @@ function isIpInRegion(
|
||||
return false;
|
||||
}
|
||||
|
||||
function isPathAllowed(pattern: string, path: string): boolean {
|
||||
// Normalize and split paths into segments
|
||||
const normalize = (p: string) => p.split("/").filter(Boolean);
|
||||
const patternParts = normalize(pattern);
|
||||
const pathParts = normalize(path);
|
||||
|
||||
// Recursive function to try different wildcard matches
|
||||
function matchSegments(patternIndex: number, pathIndex: number): boolean {
|
||||
const indent = " ".repeat(pathIndex); // Indent based on recursion depth
|
||||
const currentPatternPart = patternParts[patternIndex];
|
||||
const currentPathPart = pathParts[pathIndex];
|
||||
|
||||
// If we've consumed all pattern parts, we should have consumed all path parts
|
||||
if (patternIndex >= patternParts.length) {
|
||||
const result = pathIndex >= pathParts.length;
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we've consumed all path parts but still have pattern parts
|
||||
if (pathIndex >= pathParts.length) {
|
||||
// The only way this can match is if all remaining pattern parts are wildcards
|
||||
const remainingPattern = patternParts.slice(patternIndex);
|
||||
const result = remainingPattern.every((p) => p === "*");
|
||||
return result;
|
||||
}
|
||||
|
||||
// For full segment wildcards, try consuming different numbers of path segments
|
||||
if (currentPatternPart === "*") {
|
||||
// Try consuming 0 segments (skip the wildcard)
|
||||
if (matchSegments(patternIndex + 1, pathIndex)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try consuming current segment and recursively try rest
|
||||
if (matchSegments(patternIndex, pathIndex + 1)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for in-segment wildcard (e.g., "prefix*" or "prefix*suffix")
|
||||
if (currentPatternPart.includes("*")) {
|
||||
// Convert the pattern segment to a regex pattern
|
||||
const regexPattern = currentPatternPart
|
||||
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard
|
||||
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
|
||||
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
|
||||
if (regex.test(currentPathPart)) {
|
||||
return matchSegments(patternIndex + 1, pathIndex + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// For regular segments, they must match exactly
|
||||
if (currentPatternPart !== currentPathPart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move to next segments in both pattern and path
|
||||
return matchSegments(patternIndex + 1, pathIndex + 1);
|
||||
}
|
||||
|
||||
const result = matchSegments(0, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
console.log("Running path matching tests...");
|
||||
|
||||
@@ -239,121 +308,6 @@ function runTests() {
|
||||
console.log("All path matching tests passed!");
|
||||
}
|
||||
|
||||
function runSpecialCharacterTests() {
|
||||
console.log("\nRunning special character tests...");
|
||||
|
||||
let threw = false;
|
||||
try {
|
||||
isPathAllowed("(api*", "anything");
|
||||
isPathAllowed("a(b*", "a(bc");
|
||||
isPathAllowed("c[d*", "c[de");
|
||||
isPathAllowed("x{2}*", "x{2}y");
|
||||
isPathAllowed("a|b*", "a|bc");
|
||||
isPathAllowed("back\\slash*", "back\\slashed");
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
console.error(
|
||||
"Patterns accepted by isValidUrlGlobPattern crashed the matcher:",
|
||||
e instanceof Error ? e.message : e
|
||||
);
|
||||
}
|
||||
assertEquals(
|
||||
threw,
|
||||
false,
|
||||
"Patterns with regex metacharacters must not throw"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("(api*", "(api-v1"),
|
||||
true,
|
||||
"Parenthesis should be treated as a literal character"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("(api*", "xapi-v1"),
|
||||
false,
|
||||
"Parenthesis should not match other characters"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("a(b)*", "a(b)c"),
|
||||
true,
|
||||
"Parentheses pair should be treated as literal characters"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("*.png", "image.png"),
|
||||
true,
|
||||
"Dot should match a literal dot"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("*.png", "imageXpng"),
|
||||
false,
|
||||
"Dot should not act as a regex wildcard"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("v1.0*", "v1.0.1"),
|
||||
true,
|
||||
"Version-like literal should match itself"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("v1.0*", "v1x0-beta"),
|
||||
false,
|
||||
"Version-like literal should not match arbitrary characters"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("a+b*", "a+bc"),
|
||||
true,
|
||||
"Plus should be treated as a literal character"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("a+b*", "aaabc"),
|
||||
false,
|
||||
"Plus should not act as a regex quantifier"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("$ref*", "$refs"),
|
||||
true,
|
||||
"Dollar sign should be treated as a literal character"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("price$*", "price$100"),
|
||||
true,
|
||||
"Dollar sign mid-pattern should be treated as a literal character"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("^start*", "^started"),
|
||||
true,
|
||||
"Caret should be treated as a literal character"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("a|b*", "a|bc"),
|
||||
true,
|
||||
"Pipe should be treated as a literal character"
|
||||
);
|
||||
assertEquals(
|
||||
isPathAllowed("a|b*", "a"),
|
||||
false,
|
||||
"Pipe should not act as regex alternation"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("file?*", "fileX"),
|
||||
true,
|
||||
"Question mark should still act as a single-character wildcard"
|
||||
);
|
||||
|
||||
assertEquals(
|
||||
isPathAllowed("api/*", "api/" + "x/".repeat(50)),
|
||||
true,
|
||||
"Deeply nested paths should still match"
|
||||
);
|
||||
|
||||
console.log("All special character tests passed!");
|
||||
}
|
||||
|
||||
function runRegionTests() {
|
||||
console.log("\nRunning isIpInRegion tests...");
|
||||
|
||||
@@ -413,7 +367,6 @@ function runRegionTests() {
|
||||
// Run all tests
|
||||
try {
|
||||
runTests();
|
||||
runSpecialCharacterTests();
|
||||
runRegionTests();
|
||||
console.log("\n✅ All tests passed!");
|
||||
} catch (error) {
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from "@server/db";
|
||||
import config from "@server/lib/config";
|
||||
import { isIpInCidr, stripPortFromHost } from "@server/lib/ip";
|
||||
import { isPathAllowed } from "@server/lib/pathMatch";
|
||||
import { response } from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -1091,7 +1090,143 @@ async function checkRules(
|
||||
return;
|
||||
}
|
||||
|
||||
export { isPathAllowed };
|
||||
export function isPathAllowed(pattern: string, path: string): boolean {
|
||||
logger.debug(`\nMatching path "${path}" against pattern "${pattern}"`);
|
||||
|
||||
// Normalize and split paths into segments
|
||||
const normalize = (p: string) => p.split("/").filter(Boolean);
|
||||
const patternParts = normalize(pattern);
|
||||
const pathParts = normalize(path);
|
||||
|
||||
logger.debug(`Normalized pattern parts: [${patternParts.join(", ")}]`);
|
||||
logger.debug(`Normalized path parts: [${pathParts.join(", ")}]`);
|
||||
|
||||
// Maximum recursion depth to prevent stack overflow and memory issues
|
||||
const MAX_RECURSION_DEPTH = 100;
|
||||
|
||||
// Recursive function to try different wildcard matches
|
||||
function matchSegments(
|
||||
patternIndex: number,
|
||||
pathIndex: number,
|
||||
depth: number = 0
|
||||
): boolean {
|
||||
// Check recursion depth limit
|
||||
if (depth > MAX_RECURSION_DEPTH) {
|
||||
logger.warn(
|
||||
`Path matching exceeded maximum recursion depth (${MAX_RECURSION_DEPTH}) for pattern "${pattern}" and path "${path}"`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const indent = " ".repeat(depth); // Indent based on recursion depth
|
||||
const currentPatternPart = patternParts[patternIndex];
|
||||
const currentPathPart = pathParts[pathIndex];
|
||||
|
||||
logger.debug(
|
||||
`${indent}Checking patternIndex=${patternIndex} (${currentPatternPart || "END"}) vs pathIndex=${pathIndex} (${currentPathPart || "END"}) [depth=${depth}]`
|
||||
);
|
||||
|
||||
// If we've consumed all pattern parts, we should have consumed all path parts
|
||||
if (patternIndex >= patternParts.length) {
|
||||
const result = pathIndex >= pathParts.length;
|
||||
logger.debug(
|
||||
`${indent}Reached end of pattern, remaining path: ${pathParts.slice(pathIndex).join("/")} -> ${result}`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
// If we've consumed all path parts but still have pattern parts
|
||||
if (pathIndex >= pathParts.length) {
|
||||
// The only way this can match is if all remaining pattern parts are wildcards
|
||||
const remainingPattern = patternParts.slice(patternIndex);
|
||||
const result = remainingPattern.every((p) => p === "*");
|
||||
logger.debug(
|
||||
`${indent}Reached end of path, remaining pattern: ${remainingPattern.join("/")} -> ${result}`
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
// For full segment wildcards, try consuming different numbers of path segments
|
||||
if (currentPatternPart === "*") {
|
||||
logger.debug(
|
||||
`${indent}Found wildcard at pattern index ${patternIndex}`
|
||||
);
|
||||
|
||||
// Try consuming 0 segments (skip the wildcard)
|
||||
logger.debug(
|
||||
`${indent}Trying to skip wildcard (consume 0 segments)`
|
||||
);
|
||||
if (matchSegments(patternIndex + 1, pathIndex, depth + 1)) {
|
||||
logger.debug(
|
||||
`${indent}Successfully matched by skipping wildcard`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try consuming current segment and recursively try rest
|
||||
logger.debug(
|
||||
`${indent}Trying to consume segment "${currentPathPart}" for wildcard`
|
||||
);
|
||||
if (matchSegments(patternIndex, pathIndex + 1, depth + 1)) {
|
||||
logger.debug(
|
||||
`${indent}Successfully matched by consuming segment for wildcard`
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.debug(`${indent}Failed to match wildcard`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for in-segment wildcard (e.g., "prefix*" or "prefix*suffix")
|
||||
if (currentPatternPart.includes("*")) {
|
||||
logger.debug(
|
||||
`${indent}Found in-segment wildcard in "${currentPatternPart}"`
|
||||
);
|
||||
|
||||
// Convert the pattern segment to a regex pattern
|
||||
const regexPattern = currentPatternPart
|
||||
.replace(/\*/g, ".*") // Replace * with .* for regex wildcard
|
||||
.replace(/\?/g, "."); // Replace ? with . for single character wildcard if needed
|
||||
|
||||
const regex = new RegExp(`^${regexPattern}$`);
|
||||
|
||||
if (regex.test(currentPathPart)) {
|
||||
logger.debug(
|
||||
`${indent}Segment with wildcard matches: "${currentPatternPart}" matches "${currentPathPart}"`
|
||||
);
|
||||
return matchSegments(
|
||||
patternIndex + 1,
|
||||
pathIndex + 1,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`${indent}Segment with wildcard mismatch: "${currentPatternPart}" doesn't match "${currentPathPart}"`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// For regular segments, they must match exactly
|
||||
if (currentPatternPart !== currentPathPart) {
|
||||
logger.debug(
|
||||
`${indent}Segment mismatch: "${currentPatternPart}" != "${currentPathPart}"`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
`${indent}Segments match: "${currentPatternPart}" = "${currentPathPart}"`
|
||||
);
|
||||
// Move to next segments in both pattern and path
|
||||
return matchSegments(patternIndex + 1, pathIndex + 1, depth + 1);
|
||||
}
|
||||
|
||||
const result = matchSegments(0, 0, 0);
|
||||
logger.debug(`Final result: ${result}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function isIpInGeoIP(
|
||||
ipCountryCode: string | undefined,
|
||||
|
||||
Reference in New Issue
Block a user