Streams, cancellation and limits
Streams
- Reading.
FromFileand a seekableFromStreamread MSG sectors on demand instead of copying the file. A non-seekable stream is spooled to a temporary file, deleted when the message is disposed, and never held whole in memory. The parsed message still holds its properties and attachment bytes. - Writing.
Save(Stream),SaveToEml(Stream),MsgWriter.WriteandCfbWriter.Writewrite straight to the stream: an MSG a sector at a time, an EML with attachments base64-encoded block by block. Thebyte[]forms are thin wrappers. - EML parsing works from one block, since decoded parts stay in memory anyway.
LoadFromEmlreads the stream into one exactly sized array. - Attachments are copied with
CopyToandCopyToAsync, andOpenReadreturns a stream. - Preview.
WriteHtml(Stream)writes the document without building it as a string first.
using XRay.Email.Msg;
var message = MapiMessage.LoadFromEml("big.eml");
// Straight to disk, no byte[] for the whole file.
await using var target = File.Create("big.msg");
message.Save(target, token);
// AddAttachment also takes a stream.
await using var data = File.OpenRead("report.pdf");
message.AddAttachment("report.pdf", data);
Cancellation
Every load, save, convert, render and copy method takes a CancellationToken as its last
parameter and stops promptly with OperationCanceledException, which is never swallowed or
turned into a package exception. The token is checked at loop boundaries: CFB sector chains every
256 sectors, MIME parts and lines, writer sectors every 64, base64 blocks, HTML tokens every 4,096
and RTF control tokens every 4,096. The
family convention applies.
using XRay.Email.Msg;
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
var message = MapiMessage.Load(path, timeout.Token);
var html = message.ToHtml(null, timeout.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("gave up");
}
Errors and hostile input
Malformed MSG input raises CfbException or MsgException, or is read leniently and reported in
ValidationIssues. EML parsing does not throw. Recursion over file structure is bounded: MIME
nesting has a depth limit, the MSG storage tree has a depth limit and an ancestor check, and the
CFB tree walk keeps a visited set. A truncation and mutation test suite guards these paths.
What is not supported
- S/MIME: signed and encrypted messages (
smime.p7m). Such a message shows whatever body it carries, and the opaque part comes through as an attachment. - TNEF:
winmail.datis an attachment, not unpacked. - Mail servers: no IMAP, POP3 or SMTP.
- PST and OST mailboxes. Use X-Ray.Content to extract text from a PST.
- OLE storage attachments read as empty.