openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
76 lines (75 loc) • 2.48 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-mnp54Vah.js";
//#region src/gateway/security-path.ts
const MAX_PATH_DECODE_PASSES = 32;
function normalizePathSeparators(pathname) {
const collapsed = pathname.replace(/\/{2,}/g, "/");
if (collapsed.length <= 1) return collapsed;
return collapsed.replace(/\/+$/, "");
}
function resolveDotSegments(pathname) {
try {
return new URL(pathname, "http://localhost").pathname;
} catch {
return pathname;
}
}
function normalizePathForSecurity(pathname) {
return normalizePathSeparators(normalizeLowercaseStringOrEmpty(resolveDotSegments(pathname))) || "/";
}
function pushNormalizedCandidate(candidates, seen, value) {
const normalized = normalizePathForSecurity(value);
if (seen.has(normalized)) return;
seen.add(normalized);
candidates.push(normalized);
}
function buildCanonicalPathCandidates(pathname, maxDecodePasses = MAX_PATH_DECODE_PASSES) {
const candidates = [];
const seen = /* @__PURE__ */ new Set();
pushNormalizedCandidate(candidates, seen, pathname);
let decoded = pathname;
let malformedEncoding = false;
let decodePasses = 0;
for (let pass = 0; pass < maxDecodePasses; pass++) {
let nextDecoded;
try {
nextDecoded = decodeURIComponent(decoded);
} catch {
malformedEncoding = true;
break;
}
if (nextDecoded === decoded) break;
decodePasses += 1;
decoded = nextDecoded;
pushNormalizedCandidate(candidates, seen, decoded);
}
let decodePassLimitReached = false;
if (!malformedEncoding) try {
decodePassLimitReached = decodeURIComponent(decoded) !== decoded;
} catch {
malformedEncoding = true;
}
return {
candidates,
decodePasses,
decodePassLimitReached,
malformedEncoding
};
}
function canonicalizePathVariant(pathname) {
const { candidates } = buildCanonicalPathCandidates(pathname);
return candidates[candidates.length - 1] ?? "/";
}
function canonicalizePathForSecurity(pathname) {
const { candidates, decodePasses, decodePassLimitReached, malformedEncoding } = buildCanonicalPathCandidates(pathname);
return {
canonicalPath: candidates[candidates.length - 1] ?? "/",
candidates,
decodePasses,
decodePassLimitReached,
malformedEncoding,
rawNormalizedPath: normalizePathSeparators(normalizeLowercaseStringOrEmpty(pathname)) || "/"
};
}
const PROTECTED_PLUGIN_ROUTE_PREFIXES = ["/api/channels"];
//#endregion
export { canonicalizePathForSecurity as n, canonicalizePathVariant as r, PROTECTED_PLUGIN_ROUTE_PREFIXES as t };