115 lines · 4.4 KB
import { parseSshSignature, sshFingerprint } from "./sshsig";
import { parsePgpSignature } from "./openpgp";
import type { PgpKey } from "./openpgp";

const decoder = new TextDecoder();
const encoder = new TextEncoder();
const SIGNATURE_HEADERS = new Set(["gpgsig", "gpgsig-sha256"]);
const EMAIL = /<([^>]*)>/;

export type SignedCommit = { payload: Uint8Array; signature: string | null; committerEmail: string | null };

/** Splits a raw commit into the signed payload (commit minus its gpgsig header) and the signature text. */
export function splitSignedCommit(raw: Uint8Array): SignedCommit {
  const text = decoder.decode(raw);
  const headerEnd = text.indexOf("\n\n");
  const headers = headerEnd === -1 ? text : text.slice(0, headerEnd);
  const rest = headerEnd === -1 ? "" : text.slice(headerEnd);
  const kept: string[] = [];
  const signature: string[] = [];
  let inSignature = false;
  let committerEmail: string | null = null;
  for (const line of headers.split("\n")) {
    if (line.startsWith(" ") && inSignature) {
      signature.push(line.slice(1));
      continue;
    }
    const name = line.slice(0, line.indexOf(" "));
    inSignature = SIGNATURE_HEADERS.has(name) && signature.length === 0;
    if (inSignature) {
      signature.push(line.slice(name.length + 1));
      continue;
    }
    if (name === "committer") committerEmail = EMAIL.exec(line)?.[1] ?? null;
    kept.push(line);
  }
  return {
    payload: encoder.encode(`${kept.join("\n")}${rest}`),
    signature: signature.length > 0 ? `${signature.join("\n")}\n` : null,
    committerEmail,
  };