NAME

Mail::DKIM2::MessageInstance - Compute, verify and undo Message-Instance headers

SYNOPSIS

use Mail::DKIM2::MessageInstance;

# First hop: record the message as it is.
my $mi = Mail::DKIM2::MessageInstance->calculate($msg);
print "Message-Instance: " . $mi->as_string . "\n";

# A later hop that changed the message: record the new state and a
# Recipe for getting back to the state it received.
my $mi = Mail::DKIM2::MessageInstance->calculate($modified, $received);

# Does the top instance describe this message?
my $m = Mail::DKIM2::MessageInstance->verify($msg);
my ($m, $why) = Mail::DKIM2::MessageInstance->verify($msg);

# Does the whole chain undo cleanly, each instance matching?
my ($ok, $why) = Mail::DKIM2::MessageInstance->chain_verifies($msg);

# Apply the top Recipe: the message as the previous hop sent it.
my $previous = Mail::DKIM2::MessageInstance->undo($msg);

DESCRIPTION

A Message-Instance header (spec-06 sections 4 to 7) records the message at one point in its journey: a hash of its header fields and a hash of its body, and, from the second instance on, a Recipe for turning this instance back into the previous one. The wire format is

m=N; h=<alg>:<header-hash>:<body-hash>[,<alg>:...]; r=<base64 JSON>;

where m= numbers the instance from 1, h= carries one hash set per algorithm the signer chose (section 7.3: sha256, sha512, or both; this module emits sha256 unless told otherwise and verifies every set it implements), and r= is the Recipe: "b" for the body and "h" for header fields, each a list of copy ranges and literal lines (section 5).

Messages are accepted as Email::MIME objects or as strings, which are parsed. verify, undo and chain_verifies look at the highest numbered Message-Instance the message carries.

This module implements draft-ietf-dkim-dkim2-spec-06; see "STATUS" in Mail::DKIM2 for what that means for the wire format and the API, and "CONVENTIONS" in Mail::DKIM2 for the option, input and error conventions every module here follows.

OPTIONS

The class methods below take these options after their positional arguments:

IgnorePrefixes

An arrayref of header-field-name prefixes to leave out of the header hash and header Recipes; see "Operator-local header fields" in Mail::DKIM2.

Algs

calculate only: an arrayref of hash algorithm names for h=, from sha256 and sha512. Default ['sha256'].

UseEpilogue, EpilogueThreshold

calculate with a previous message only; see below.

CLASS METHODS

calculate($msg, [$previous], %options)

Returns a new instance describing $msg. With no $previous, $msg must carry no Message-Instance and the result is m=1. With $previous, both messages must carry the same existing instances; the result is the next m=, with hashes of $msg and Recipes that rebuild $previous from it. Dies if the message cannot be processed: it already has 32 instances, its instances do not form a chain, or $previous is not an earlier form of the same message.

The body Recipe is a line diff by default. With UseEpilogue => 1, the previous body is instead appended after the final MIME boundary (the message is wrapped in a multipart/mixed container if it is not already multipart) and the Recipe copies it from there; with EpilogueThreshold => N, that happens only when the diff would carry more than N literal lines. Both epilogue forms modify $msg in place, and the hashes cover the modified message. Recipe computation uses Algorithm::Diff, loaded on first use.

verify($msg, %options)

Checks the top instance against the message. Returns its m= on success. On failure, including an instance that does not parse, returns 0 in scalar context and (0, $reason) in list context. Never dies.

undo($msg)

Applies the top instance's Recipes and removes that instance, returning the Email::MIME of the previous form of the message; undef if there is no instance. Dies if the Recipe is malformed (a copy range outside the message, or overlapping another) or the instances do not form a chain.

chain_verifies($msg, %options)

Runs verify and undo down the whole chain to m=1 or to an instance that declares the previous state unrecoverable. Returns (1, undef), or (0, $reason) at the first instance that does not match or does not undo. Never dies. A forwarder runs this before signing so it does not put its name to a chain its recipients will reject.

parse($header_value)

Parses a Message-Instance header value into an object. Dies with a PERMERROR string on a missing m=, a hash set that is not alg:hash:hash, or an algorithm named twice.

hash_algs()

A hashref of the hash algorithms this module implements, name to function.

parse_hash_sets($h_value)

Splits an h= value into an arrayref of [alg, header_hash, body_hash], lowercasing the names and stripping folding whitespace.

INSTANCE METHODS

as_string()

The header value in wire format, unfolded. Fold it with "fold_header" in Mail::DKIM2::Common before inserting it.

header_hash()

The base64 sha256 header hash, or undef if the instance carries no sha256 set.

unrecoverable()

True if the body Recipe is null: the body changed and the previous state cannot be recreated (section 4.2), so the chain cannot be undone past this instance.

set_null_body_recipe()

Marks the body Recipe null.

get_tag($name), set_tag($name, $value)

The parsed fields: m, hashes (a hashref of algorithm to [header_hash, body_hash]), rb and rh (the body and header Recipes in internal form), and h1/b1, the sha256 pair.

FUNCTIONS

h_digest($email_mime, [$alg], [\@prefixes])

The base64 header hash of a message: every field not excluded by "should_skip" in Mail::DKIM2::Common, canonicalized, sorted by name, repeated fields in bottom-up order.

b_digest($email_mime, [$alg])

The base64 body hash, over the body with trailing empty lines removed and one CRLF added.

AUTHOR

Bron Gondwana <brong@fastmailteam.com>

COPYRIGHT AND LICENSE

Copyright (c) 2025-2026 Fastmail Pty Ltd. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself.