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 steps (section 5): {"c":[start,end]} copies lines or field instances of this version, {"d":[...]} gives ASCII literals as JSON text, and {"b":[...]} gives literals whose raw octets are not ASCII (a Latin-1 or ISO-2022-JP line, say) as base64. Copy ranges must ascend: each starts after the one before it ends. The "b" step and the ascending rule for body Recipes are an agreed extension to spec-06 that is being proposed to the working group; this module emits "b" for every non-ASCII literal and rejects a Recipe that breaks either rule.
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
-
calculateonly: an arrayref of hash algorithm names forh=, fromsha256andsha512. Default['sha256']. - UseEpilogue, EpilogueThreshold
-
calculatewith 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, overlapping another or out of order, or a "b" literal that is not base64 or decodes to a CR or LF) 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.