Skip to content

# PST/OST: parse the MS-PST header layout and verify its CRCs - #417

Merged
Hawkynt merged 2 commits into
mainfrom
feat/pst-ost
Sep 30, 2026
Merged

Hawkynt merged 2 commits into
mainfrom
feat/pst-ost

Conversation

@Hawkynt

@Hawkynt Hawkynt commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

What changed

  • Parse the MS-PST HEADER and ROOT structures for ANSI (wVer 14/15, 512-byte header) and Unicode (wVer 23+, 564-byte header): version, client version, ibFileEof, AMap/PMap fields, NBT/BBT BREFs, sentinel and crypt method.
  • Verify dwCRCPartial and (Unicode) dwCRCFull with the MS-PST section 5.3 CRC; accept the OST client signature "SO" as well as "SM".
  • List/OpenEntry never throw on a damaged header: FULL.pst plus a metadata.ini with parse_status=partial; Extract writes what it can and then throws, so an integrity test reports the damage.
  • The byte-copy "creation" that an earlier revision of this branch added is removed: copying an existing PST is not a PST writer, so the descriptor stays read-only (R).

Verification

  • Header parser pinned against the first 564 bytes of two real Outlook files from java-libpst's test resources (Apache-2.0, notice beside the tests): dist-list.pst (wVer 23) and example-2013.ost (wVer 36) — all fields and both CRCs.
  • Damage in each CRC range, truncation, bad version and bad sentinel list without throwing.
  • dotnet test Compression.Tests --filter "FullyQualifiedName~Pst|FullyQualifiedName~Readme|FullyQualifiedName~SupportMatrix" green locally.

@Hawkynt Hawkynt changed the title + Support PST/OST byte-preserving creation and correct header parsing # PST/OST: parse the MS-PST header layout and verify its CRCs Sep 30, 2026
@Hawkynt
Hawkynt force-pushed the feat/pst-ost branch 2 times, most recently from 7b52b69 to 119594d Compare September 30, 2026 16:11
Symptom: PST/OST were read-only and surfaced incorrect header metadata for ANSI and Unicode files.
Root cause: the parser classified ANSI version 15 as Unicode, used incorrect ROOT pointer offsets, and assumed a 512-byte header for every variant.
Fix: parse the specified ANSI/Unicode layouts and expose additional ROOT/header fields; add validated streaming re-emission of one existing PST/OST image so every opaque property and compression/encryption flag remains untouched. New mailboxes from loose files remain unsupported because that requires writing the NDB/LTP model.
Symptom: CI failed every Convert_*__to__Pst pair: the descriptor
advertised CanCreate and then refused every input except one existing
.pst/.ost file. Checked against real Outlook files, the corrected header
parser also rejected every OST, and List() threw on any damaged header.

Root cause:
- "Creation" was a byte copy of a PST the caller already had. That is not
  a PST writer, and advertising CanCreate for it misstates what the
  library can do.
- wMagicClient was required to be "SM"; an OST carries "SO".
- The header CRCs were read but never checked, and a failed header parse
  escaped from List().

Fix:
- Remove IArchiveCreatable, Create and CreateFromStreams; the descriptor
  is read-only again, and the README row says what it does.
- Accept "SM" and "SO"; verify dwCRCPartial (471 bytes) and, for Unicode,
  dwCRCFull (516 bytes) with the MS-PST section 5.3 CRC.
- List/OpenEntry fall back to FULL.pst plus a metadata.ini with
  parse_status=partial and the defect; Extract writes what it can and then
  throws, so an integrity test still reports the damage.
- Tests pin the parser against the headers of two real Outlook files
  (java-libpst's dist-list.pst, wVer 23, and example-2013.ost, wVer 36):
  version, client version, crypt method, ibFileEof, NBT/BBT offsets and
  both CRCs. Damage inside each CRC range, truncation, bad version and bad
  sentinel list without throwing.

Sourcing: rung 3, MS-PST HEADER, ROOT and CRC sections. The header bytes
of the java-libpst test files (Apache-2.0, notice beside the tests) are
used as reference vectors only.
@Hawkynt
Hawkynt merged commit cca4b7a into main Sep 30, 2026
5 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant