CFB Containers
CFB Containers
Compound File Binary (CFB) is the container format used by Outlook .msg files. This guide shows how to read, traverse, and write CFB containers using CfbReader, CfbWriter, and CfbDocument from Aspose.Email FOSS for .NET.
Reading CFB Containers
Open a CFB File
Create a CfbReader from a file path, a stream, or a byte array to open the CFB container and begin reading its directory entries:
using Aspose.Email.Foss.Cfb;
// From file
using var reader = CfbReader.FromFile("sample.msg");
// From stream
using var stream = File.OpenRead("sample.msg");
using var reader2 = CfbReader.FromStream(stream);
// From byte array
var data = File.ReadAllBytes("sample.msg");
using var reader3 = new CfbReader(data);Inspect Header and Structure
Read the header properties on CfbReader to inspect the sector sizes, entry count, and total file size:
Console.WriteLine($"Version: {reader.MajorVersion}");
Console.WriteLine($"Sector size: {reader.SectorSize}");
Console.WriteLine($"Mini-sector size: {reader.MiniSectorSize}");
Console.WriteLine($"Directory entries: {reader.DirectoryEntryCount}");
Console.WriteLine($"Streams materialized: {reader.MaterializedStreamCount}");
Console.WriteLine($"File size: {reader.FileSize}");Traverse the Directory Tree
Call IterTree() for a full recursive depth-annotated traversal, or use IterStorages() and IterStreams() to enumerate only storage or stream entries respectively:
// Full recursive traversal with depth
foreach (var (depth, entry) in reader.IterTree())
{
var indent = new string(' ', depth * 2);
Console.WriteLine($"{indent}{entry.Name} [{entry.ObjectType}]");
}
// Iterate only storages
foreach (var entry in reader.IterStorages())
Console.WriteLine($"Storage: {entry.Name}");
// Iterate only streams
foreach (var entry in reader.IterStreams())
Console.WriteLine($"Stream: {entry.Name} ({entry.StreamSize} bytes)");Navigate Children and Resolve Paths
Use IterChildren() to list direct children of a given storage entry, and ResolvePath() to locate a stream by its name sequence and read its bytes:
// Direct children of the root
foreach (var entry in reader.IterChildren(CfbConstants.RootStreamId))
Console.WriteLine(entry.Name);
// Resolve a nested path
var target = reader.ResolvePath(new[] { "__substg1.0_0037001F" });
if (target != null)
{
var streamData = reader.GetStreamData(target.StreamId);
Console.WriteLine($"Stream data: {streamData.Length} bytes");
}Find a Child by Name
Call FindChildByName() with a stream ID and name string to locate a specific child entry and read its raw bytes:
var child = reader.FindChildByName(CfbConstants.RootStreamId, "__properties_version1.0");
if (child != null)
{
var bytes = reader.GetStreamData(child.StreamId);
Console.WriteLine($"Property stream: {bytes.Length} bytes");
}Writing CFB Containers
Build a CFB Document
Create a CfbStorage root node, add CfbStream and nested CfbStorage children, then wrap it in a CfbDocument object:
using Aspose.Email.Foss.Cfb;
var root = new CfbStorage("Root Entry");
root.AddStream(new CfbStream("data.bin", new byte[] { 0x01, 0x02, 0x03 }));
var subStorage = new CfbStorage("SubFolder");
subStorage.AddStream(new CfbStream("nested.txt", System.Text.Encoding.UTF8.GetBytes("hello")));
root.AddStorage(subStorage);
var document = new CfbDocument(root);Serialize to Bytes or File
Pass the CfbDocument to CfbWriter.ToBytes() to get a byte array, or call CfbWriter.WriteFile() to write the serialized container directly to disk:
// To byte array
byte[] cfbBytes = CfbWriter.ToBytes(document);
// To file
CfbWriter.WriteFile(document, "output.cfb");Round-Trip Verification
Write the document with CfbWriter.WriteFile(), then open it again with CfbReader.FromFile() to verify the written entries are readable:
// Write and reload
CfbWriter.WriteFile(document, "test.cfb");
using var reloaded = CfbReader.FromFile("test.cfb");
foreach (var entry in reloaded.IterChildren(CfbConstants.RootStreamId))
Console.WriteLine(entry.Name);Load Existing CFB as CfbDocument
Use CfbDocument.FromFile() to load an existing CFB file and access its root storage and version metadata directly:
var doc = CfbDocument.FromFile("sample.msg");
Console.WriteLine($"Root: {doc.Root.Name}");
Console.WriteLine($"Version: {doc.MajorVersion}.{doc.MinorVersion}");See Also
- Reading MSG Files — High-level MSG access via MapiMessage
- MAPI Properties — Access MAPI property streams
- Features — Full feature reference