UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

105 lines (104 loc) 4.36 kB
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js"; import { d as AUTH_RATE_LIMIT_SCOPE_WORKER_TRANSFER } from "./auth-rate-limit-C2k5aTEA.js"; import { n as withSerializedRateLimitAttempt } from "./rate-limit-attempt-serialization-CvMQJrpm.js"; import { m as watchClientDisconnect, s as sendJson } from "./http-common-BaZaosnr.js"; import { Transform } from "node:stream"; import { pipeline as pipeline$1 } from "node:stream/promises"; //#region src/gateway/worker-environments/artifact-transfer-http.ts const SHA256_PATTERN = /^[a-f0-9]{64}$/u; function sendOpaqueNotFound(res) { sendJson(res, 404, { error: "not_found" }); } async function handleArtifactTransferHttpRequest(params) { const parsed = URL.parse(params.req.url ?? "/", "http://localhost"); if (!parsed || params.classifyPath(parsed.pathname) === "outside") return false; params.res.setHeader("Cache-Control", "no-store"); const prefix = params.routePrefix; const artifactKey = parsed.pathname.startsWith(prefix) ? parsed.pathname.slice(prefix.length) : ""; if (params.req.method !== "GET" || !SHA256_PATTERN.test(artifactKey) || parsed.search || parsed.hash) { sendOpaqueNotFound(params.res); return true; } const authorization = normalizeOptionalString(params.req.headers.authorization); const bearer = authorization?.toLowerCase().startsWith("bearer ") ? normalizeOptionalString(authorization.slice(7)) : void 0; const admission = await withSerializedRateLimitAttempt({ ip: params.clientIp, scope: AUTH_RATE_LIMIT_SCOPE_WORKER_TRANSFER, run: async () => { const rateCheck = params.rateLimiter?.check(params.clientIp, AUTH_RATE_LIMIT_SCOPE_WORKER_TRANSFER); if (rateCheck && !rateCheck.allowed) return { kind: "rate-limited", retryAfterMs: rateCheck.retryAfterMs }; const outcome = bearer && params.callback ? await params.callback({ req: params.req, res: params.res, artifactKey, bearer }) : { kind: "unauthorized" }; if (outcome.kind === "unauthorized") params.rateLimiter?.recordFailure(params.clientIp, AUTH_RATE_LIMIT_SCOPE_WORKER_TRANSFER); else params.rateLimiter?.reset(params.clientIp, AUTH_RATE_LIMIT_SCOPE_WORKER_TRANSFER); return outcome; } }); if (admission.kind === "rate-limited") { if (admission.retryAfterMs > 0) params.res.setHeader("Retry-After", String(Math.ceil(admission.retryAfterMs / 1e3))); sendJson(params.res, 429, { error: "rate_limited" }); return true; } if (admission.kind === "unauthorized") { sendOpaqueNotFound(params.res); return true; } await admission.handle(); return true; } function createArtifactTransferHttpCallback(service) { return async ({ req, res, artifactKey, bearer }) => { const authorization = service.authorize({ token: bearer, artifactKey }); if (!authorization) return { kind: "unauthorized" }; return { kind: "authorized", handle: async () => { const clientAbort = new AbortController(); const stopWatchingDisconnect = watchClientDisconnect(req, res, clientAbort); const signal = AbortSignal.any([service.authorizationSignal(authorization), clientAbort.signal]); let fileHandle; try { const file = await service.openFile(authorization); fileHandle = file?.handle; if (!file || signal.aborted || !service.isAuthorizationCurrent(authorization)) { sendOpaqueNotFound(res); return; } const checkAuthority = new Transform({ transform(chunk, _encoding, next) { next(service.isAuthorizationCurrent(authorization) ? null : /* @__PURE__ */ new Error("Worker artifact transfer authority closed"), chunk); } }); res.writeHead(200, { "content-type": "application/octet-stream", "content-length": String(file.bytes), "x-openclaw-content-sha256": file.sha256 }); const stream = file.handle.createReadStream({ start: 0, end: file.bytes - 1, autoClose: false }); await pipeline$1(stream, checkAuthority, res, { signal }); } catch { if (!res.headersSent && !res.destroyed) sendOpaqueNotFound(res); else if (!res.destroyed) res.destroy(); } finally { stopWatchingDisconnect(); service.revoke(authorization); await fileHandle?.close(); } } }; }; } //#endregion export { handleArtifactTransferHttpRequest as n, createArtifactTransferHttpCallback as t };