UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

76 lines (75 loc) 2.48 kB
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.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 };