97 lines · 4.0 KB
import PostalMime from "postal-mime";
import { abuseForwardAddress, guessAbuseCategory, recordAbuseReport, siteIdForUrl } from "./abuse";
import type { ReporterKind } from "./abuse";

const MAX_RAW_BYTES = 2 * 1024 * 1024;
const MAX_TARGETS = 10;
const URL_PATTERN = /\bhttps?:\/\/[^\s<>"'()]+/gi;
const TRAILING_PUNCTUATION = /[.,;:!?\]]+$/;

/** URLs mentioned in the mail, hosted sites first so a long signature can't push the reported page out. */
export async function extractReportTargets(text: string): Promise<{ hosted: string[]; other: string[] }> {
  const seen = new Set<string>();
  const hosted: string[] = [];
  const other: string[] = [];
  for (const match of text.matchAll(URL_PATTERN)) {
    const url = match[0].replace(TRAILING_PUNCTUATION, "");
    if (seen.has(url) || seen.size >= MAX_TARGETS * 4) continue;
    seen.add(url);
    if (hosted.length < MAX_TARGETS && (await siteIdForUrl(url))) hosted.push(url);
    else if (other.length < MAX_TARGETS) other.push(url);
  }
  return { hosted, other };
}

/**
 * Whether the receiving MTA (Cloudflare Email Routing, which prepends its own Authentication-Results) saw a DMARC
 * pass aligned with the From domain. Only the topmost header counts: anything below it came from the sender and
 * can be forged.
 */
export function dmarcPassed(headers: { key: string; value: string }[], fromDomain: string): boolean {
  const results = headers.find((header) => header.key.toLowerCase() === "authentication-results")?.value;
  if (!results || !fromDomain) return false;
  const dmarc = results
    .split(";")
    .map((part) => part.trim().toLowerCase())
    .find((part) => part.startsWith("dmarc="));
  if (!dmarc || !/^dmarc=pass\b/.test(dmarc)) return false;
  const from = /header\.from=([^\s;]+)/.exec(dmarc)?.[1];
  return from === fromDomain.toLowerCase();
}