Files
craft-libs/crates/pst/README.md
Dylan M. TaylorandMartin Ball dbed0a83ea Add craft-pst: Outlook .pst reader from MailCraft (#930)
The [MS-PST] reader of MailCraft crates/pst at a44a450, moved as it is: the
node database, heap, property and table contexts, folders, messages,
recipients, attachments, named properties, recurrence blobs and list
streams, with its limits, salvage and tests.

Left in MailCraft for now: the compressed-RTF and RTF text reading and the
code-page name table, which are not part of the .pst format.

Co-authored-by: Martin Ball <98639962+benandez@users.noreply.github.com>
2026-11-11 18:43:21 -05:00

71 lines
3.3 KiB
Markdown

# craft-pst
A reader of Outlook personal folders files (`.pst`), written from Microsoft's published
specification, [MS-PST], and nothing else. No dependencies, no `unsafe`.
- `Pst::open(reader)` / `Pst::open_with(reader, Limits)` read over any `Read + Seek`. The file is
never held in memory: a page or a block is read when it is needed.
- ANSI and Unicode files, with data blocks unencoded, permuted or cyclic-encoded. A file with a
password answers only after `Pst::unlock`.
- The layers are the specification's: the node database (B-trees of nodes and blocks, data
trees, subnode trees), the heap on a node, its B-tree, property contexts and table contexts,
and the messaging layer: `store`, `walk_folders`, `items`, `associated_items`, `message`
(properties, recipients, attachments, attached messages), `attachment_reader`,
`read_property` for large values, and `named_properties`.
- `recur` reads the appointment recurrence pattern and time zone blobs of [MS-OXOCAL];
`list_stream` reads the entry stream of a distribution list.
- A damaged file is salvaged: what cannot be read is left out and named (`Pst::damage`,
`Message::left_out`).
```rust
use craft_pst::Pst;
let mut pst = Pst::open(std::fs::File::open(path)?)?;
for entry in pst.walk_folders()?.folders {
for item in pst.items(entry.folder.id)?.items {
let message = pst.message(&craft_pst::MessageRef::item(item.id))?;
}
}
```
## Hostile input
Nothing panics on any bytes. Every offset and length is checked against the file, every CRC,
signature and block id against what it guards; trees are bounded in depth and read each part
once. The caps of `Limits` are checked before anything is read or allocated; past a cap a part
is left out and named (`Error::Cap`).
| Limit | Default |
|---|---|
| `max_file_size` | 100 GiB |
| `max_items` | 2,000,000 |
| `max_folders` / `max_folder_depth` | 10,000 / 64 |
| `max_item_bytes` | 256 MiB |
| `max_attachment_bytes` | 150 MiB |
| `max_recipients` / `max_attachments` | 1,000 / 1,000 |
| `max_embedded_depth` / `max_embedded_messages` | 8 / 10,000 |
| `max_btree_pages` | 4,000,000 |
| `max_subnodes` | 200,000 |
| `max_inline_value` | 4 MiB (larger values are `PropValue::Large`, read on their own) |
| `max_listing_bytes` | 1 GiB per folder walk or item listing |
| `max_summary_value` | 64 KiB |
| `read_multiple` / `read_floor` | all reads together: 64 times the file size plus 256 MiB |
## What is not here
- Writing. The `test-writer` feature has a writer of small synthetic files, for tests only.
- Offline folders files with 4 KB pages (version 36) and files protected by Windows Information
Protection (version 37): `Error::UnsupportedVersion`.
- Text in a code page: `PropValue::String8` carries the bytes, and the caller decodes them in
the code page the message names.
- Compressed RTF bodies (`PidTagRtfCompressed`, [MS-OXRTFCP]): the property's bytes are
returned as they are.
- What an item means (mail, contact, appointment): that mapping is the caller's.
## Origin
MailCraft `crates/pst` at
[a44a450](https://github.com/UCS-Care-Group/mailcraft/commit/a44a45013d1c6396229c40ab60172d1882b368d5),
by Martin Ball and the MailCraft contributors (MIT OR Apache-2.0), with its tests. Left behind:
the RTF reading (`rtf.rs`) and the code-page name table, which are not part of the .pst format.