Recursive AI

Reports, MHTML and authentication results

All onemail docs

Typed views over the MIME entity tree (RecursiveAI.OneMail.Mime.Reports, .Mhtml, .Authentication). The tree stays the lossless store: a view reads it, never changes it, so an untouched message still writes back byte for byte. Every repair is a typed defect with the byte offset. The ambiguity entries (RP, AR, RS, MH) are in ambiguities.md.

multipart/report (RFC 6522)

MimeReport.Find(tree) returns the first multipart/report (searching nested message/rfc822 as well); TryCreate(entity) tests one entity. The view exposes the human-readable part, the machine-readable part, and the original (a message, or headers only for text/rfc822-headers and message/rfc822-headers). GetDeliveryStatus, GetDispositionNotification and GetFeedbackReport parse the machine part and add their defects to a caller's list.

ReportMedia typeSpecClass
Delivery statusmessage/delivery-status, message/global-delivery-statusRFC 3464, 6533DeliveryStatusReport (per-message block, per-recipient RecipientStatus)
Disposition notificationmessage/disposition-notificationRFC 8098 (3798)DispositionNotification, MdnDisposition
Feedbackmessage/feedback-reportRFC 5965, 6591, 6650FeedbackReport
  • Field syntax (ReportFieldParser): RFC 5322 header syntax, a block ends at an empty line. A line of only blanks is a fold, not an empty line. Values are not RFC 2047-decoded (the RFCs do not use encoded words here); bytes are read as UTF-8 if valid, else windows-1252. A lone CR stays in the value (BareCarriageReturnInHeader).
  • Typed values: DsnTypedValue (type; value), EnhancedStatusCode (RFC 3463; trailing comments tolerated), DsnAction, dates as MimeDate. A value that does not fit stays readable as text and raises ReportInvalidFieldValue.
  • Kind: the machine part's own media type decides; a report-type that disagrees or is missing raises ReportStructureInvalid.
  • Limits: a report body over 16 MiB or more than 100,000 fields raises ReportLimitExceeded; the rest is not parsed.
  • Writing: MimeReport.CreateDeliveryStatus / CreateDispositionNotification / CreateFeedbackReport build the multipart/report (boundary overridable for reproducible output); ToBytes writes fields folded at 78 columns, UTF-8 for non-ASCII (the caller picks the global media type with RequiresGlobal).

MHTML (RFC 2557)

MhtmlDocument.Find / TryCreate / Open / Load view a multipart/related (or a lone HTML part) as a root resource plus resources. Resolve(reference, htmlBase) returns the resource a reference in the HTML names; ResolveToAbsolute gives the URI. MhtmlBuilder writes a .mht. MhtmlUri is RFC 3986 §5.2 resolution, with thismessage:/ as the base of last resort.

Base order (RFC 2557 §5): the HTML's own base argument, the legacy Content-Base, an absolute Content-Location of the part and then of its enclosing entities, else thismessage:/. Lookup is exact first, then after Normalize (scheme and host case, percent-hex case, space as %20, default ports, dot segments). cid: matches Content-ID only (RFC 2557 §8.3: a Content-Location holding a cid: URL never satisfies it), with angle brackets stripped and percent-decoding; mid: needs both a content id and the message id. A lookup falls through to enclosing multipart/related entities. Duplicates: the first wins, and the ambiguity is reported.

Authentication-Results and Received-SPF

MimeEntity.GetAuthenticationResults() and GetReceivedSpf() parse every such field (RFC 8601; RFC 7208 §9.1) into AuthenticationResults (authserv-id, version, AuthenticationMethodResult with method, version, result, comment, reason and AuthenticationProperty ptype.name=value) and ReceivedSpf. This is parsing only: nothing is evaluated or trusted, and callers must still check the authserv-id against their own boundary. MimeEntity.GetAuthenticationResults takes the header text as the tree stores it (comments and folds kept in the raw value).

Method

tools/oracles/reports runs ours, MimeKit 4.18.2 (public API) and Python 3.13 email over testdata/reports (119 messages, generated by tools/reports/make_fixtures.py, with RFC 3464, 8601, 7208 and 2557 examples) and over the corpus messages that carry the headers (corpus.sh). Python has no Authentication-Results parser; Received-SPF has no oracle (RFC examples only). MHTML reference resolution is also run against Python urllib.parse.urljoin on 218 base/reference pairs. A difference must match a line of explained.tsv (naming an ambiguity entry or a documented oracle limit) or the run fails.