Add PST → Thunderbird mail export - #80
Open
Aksel-Visby wants to merge 36 commits into
Open
Conversation
…streamed attachment payload
The forward writer stores In-Reply-To and References as discrete MAPI properties (0x1042 / 0x1039) rather than only inside the transport headers blob. The reader now surfaces both on PstMailMessage, needed to reconstruct threading on the engine-PST path where transport headers are absent.
EnumerateFolder read mf.MessageCount (loop bound) and folder.MessageCount outside the try that guards folder access, so a corrupt contents table there propagated fatal instead of the stated warn+skip-subtree policy. Both reads are now guarded, falling back to zero (skip this folder's messages, continue the child walk) on failure. Also stop the recipient-type warning closure from capturing the nullable raw int32; it now threads the resolved value through explicitly.
…ders and mboxrd escaping
…e-CID multipart/related
…erate MIME structure
EnumerateMessages yields only message-bearing folders, so the mbox-tree writer's full-structure requirement (empty leaves, structural parents) cannot be derived from the message stream. Add a companion walk that returns every mail folder path including empty ones, recursing through non-mail containers and warning on a corrupt subtree.
… export Folder-tree-oriented report (output root, mbox files, message/folder counts, structured per-message skips kept separate from warnings, elapsed) with ToJson/ToSummary, distinct from the forward ConversionReport.
Extract ReportMessageReadFailure: with an onSkipped sink it records a structured skip (folder, index, reason) ONLY - no duplicate warning; without one it emits the legacy warning. EnumerateMessages gains the optional onSkipped param. Additive/non-breaking.
…ation Two non-mutating passes over the PST (structure, then a streamed message write) into a sibling staging dir, published to the final output via a directory move as the last step; a non-empty/file destination is rejected up front and a mid-export failure cleans staging. Skips and warnings are kept distinct; per-message progress fires only after a successful append (MboxTreeWriter gains an optional onMessageWritten callback).
…, trailing sep, staging cleanup, invalid PST)
…messages in progress
Converts a nested profile (Parent/Inbox, Sent, Empty) to PST, exports it back to an mbox tree, and re-parses with MboxParser asserting nested relative paths, per-folder counts, zero skips, and per-message fields.
…ollback semantics
…ough the full PST round trip
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a reverse conversion path: turn an Outlook PST into a Thunderbird-importable
mbox folder tree, without Outlook or any Microsoft library. This complements the
existing forward mbox → PST converter, making ContinuMail a two-way bridge for mail.
New CLI subcommand:
It reads the PST, reconstructs each message as MIME, and writes an mbox tree
(
Name.sbd/nesting mirroring the folder hierarchy) with Thunderbird'sX-Mozilla-Status/-Status2/-Keysheaders so Thunderbird regenerates itsown index on import.
How it works
fully materialized); recovers subject, sender (name + address), recipients with
To/Cc/Bcc kind, dates, Message-ID/In-Reply-To/References, plain and HTML bodies
(with the stored code page), read/replied/forwarded state, categories, and
attachments (including inline Content-ID and Content-Location).
X-Mozilla-Status/-Status2bitsand
X-Mozilla-Keystags (the inverse of how the forward parser reads them). The"flagged" state maps to the Marked bit.
regenerating MIME-structural headers from the fresh body/attachment tree; legacy
code pages decode via the CodePages provider with a UTF-8 fallback.
.sbdlayout with mboxrdFromescaping and theraw
X-Mozilla-*header lines.publishes on success (an existing non-empty destination is rejected; a failure
cleans up without leaving a half-written tree), and produces an export report
(messages exported, folders, structured skips, warnings, output files).
started/progress/warning/skipped/done/error), each tagged withcommand: "export"; the report is written to sibling<output>.export-report.json/.txtfiles.Vendored code
vendor/PSTFileFormat(LGPLv3) gains three additivePropertyIDenum members(
PidTagTransportMessageHeaders,PidTagInternetReferences,PidTagInReplyToId) —read-only additions, no behaviour change.
Testing
Full solution green. Round-trip coverage proves that a forward-written PST exports
back to an mbox tree preserving folder structure, per-folder counts, message bodies,
attachment bytes, and read/starred/tagged state. A manual Thunderbird-import check
remains a release-time validation step.
Known limitations / follow-ups
X-Mozilla-Keysvalues, but the visible tag name/colour depends on the importing profile's tag
configuration, so a tag key may not render as a coloured label in a fresh profile.
one (only reachable with arbitrary Outlook-authored PSTs).