Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
# forensic
## Purpose
Implements `parseDmarcForensic`, the entry point for parsing DMARC forensic (RUF) reports. Forensic reports notify domain owners of individual authentication failures; they share the `<feedback>` / `<report_metadata>` / `<policy_published>` structure with aggregate reports but omit the `<record>` block and may include a raw MIME-formatted `auth_failure_report` element. This module provides the same auto-detect-decompress → parse → map → validate pipeline as the aggregate parser but targets the forensic schema.
## Features
- Auto-detects gzip input via magic-byte probe
- Accepts raw XML strings or `Uint8Array` binary blobs
- Maps snake_case XML fields to camelCase typed fields
- Surfaces `authFailureReport` string when present in the source XML
- Validates against `DmarcForensicReportSchema` (Zod)
- Emits OTel counter and histogram for both success and error paths
- Wraps all failures in typed `DmarcError` with stable codes
## Usage: Basic Example
```ts
import { parseDmarcForensic } from "@suaveplan/dmarc/parsers/forensic";
const report = parseDmarcForensic(`
<feedback>
<version>1.0</version>
<report_metadata>
<org_name>Example Corp</org_name>
<email>dmarc@example.com</email>
<report_id>ruf-001</report_id>
<date_range><begin>1609459200</begin><end>1609545600</end></date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim><aspf>r</aspf><p>none</p><pct>100</pct>
</policy_published>
</feedback>
`);
console.log(report.feedback.reportMetadata.orgName); // "Example Corp"
```
## Usage: Advanced Example
```ts
import { parseDmarcForensic } from "@suaveplan/dmarc/parsers/forensic";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
import { gzipSync, strToU8 } from "fflate";
const compressed = gzipSync(strToU8(xmlString));
const report = parseDmarcForensic(compressed);
// Access optional auth failure report body
if (report.authFailureReport) {
console.log("Failure report MIME body:", report.authFailureReport);
}
// Discriminated error handling
try {
parseDmarcForensic("<bad>xml</bad>");
} catch (err) {
if (err instanceof DmarcError && err.code === DMARC_CODES.INVALID_REPORT) {
// Schema validation failed — missing required elements
}
}
```
## API Reference
### `parseDmarcForensic(input, options?): DmarcForensicReport`
**Parameters:**
- `input: string | Uint8Array` — raw XML string or binary data
- `options.decompress?: boolean` — force gzip decompression
**Returns:** `DmarcForensicReport` — validated forensic report
**Throws:**
- `DmarcError(DMARC.PARSE_ERROR)` — XML parse failure
- `DmarcError(DMARC.INVALID_REPORT)` — schema validation failure or missing required elements
- `DmarcError(DMARC.UNSUPPORTED_ENCODING)` — gzip decompression failure
### `ParseDmarcForensicOptions`
`{ decompress?: boolean }`
## Implementation Notes
The forensic parser is structurally identical to the aggregate parser except for the output schema. Both share the same `str`/`num` coercion helpers and the same two-phase (map → validate) approach. In practice, RFC 7489 forensic reports are far less common than aggregate reports and are often transmitted as raw MIME messages rather than standalone XML files; the `authFailureReport` field is therefore optional and surfaced as a plain string for callers to process further if needed.