207 lines · 9.0 KB
import { env } from "cloudflare:workers";
import { all, first } from "../db";
import { repoDb } from "../scoped-db";
import { enforceRateLimit, requestIp } from "../limits";
import { loadRepo, repoHiddenFrom, repoPermission, repoTakenDown } from "../repos";
import type { RepoRow } from "~/lib/types";
import { publicUrl } from "../events/emit";
import { enqueueDirect, enqueueIntegrationDispatch } from "./queue";
import { verifyLinearSignature, verifySha256Signature, verifySlackSignature } from "./signatures";
import { loadIntegration, openCredentials, parseConfig, recordDelivery } from "./store";
import type { IntegrationRow } from "./store";
import { escapeSlack } from "./format";
import { handleMirrorHook, MIRROR_HOOK_PATH } from "../mirror/hooks";

export const INBOUND_HOOK_PREFIX = "/integrations/hooks/";

const MAX_INBOUND_BYTES = 1024 * 1024;
const MAX_LISTED_PULLS = 5;
const REPO_ARG = /^([A-Za-z0-9][A-Za-z0-9-]{0,38})\/([A-Za-z0-9._-]{1,100})$/;

/** Streams the body and stops reading as soon as the cap is passed, so a chunked request cannot buffer unbounded. */
async function readBody(request: Request): Promise<string | null> {
  if (Number(request.headers.get("content-length") ?? "0") > MAX_INBOUND_BYTES) return null;
  if (!request.body) return "";
  const reader = request.body.getReader();
  const chunks: Uint8Array[] = [];
  let size = 0;
  for (;;) {
    const { value, done } = await reader.read();
    if (done) break;
    size += value.byteLength;
    if (size > MAX_INBOUND_BYTES) {
      await reader.cancel().catch(() => undefined);
      return null;
    }
    chunks.push(value);
  }
  const merged = new Uint8Array(size);
  let offset = 0;
  for (const chunk of chunks) {