From 3652fdd89953a0349deeddcdb56d912a5407fba9 Mon Sep 17 00:00:00 2001 From: bwittgen Date: Fri, 12 Jun 2026 11:33:14 -0400 Subject: [PATCH 1/3] Fix XML message export and gate legacy EML option behind a setting * Save Message To File: rename "Text file" dropdown label to "XML file (saves all properties of message to an XML file)" so the option matches the format it actually produces. * Fix invalid XML output from every XML exporter (property pane, dumpStore, profile, contents-table dump, MrMAPI). The files are written as UTF-16 LE (MyOpenFile uses "w, ccs=UNICODE", which emits a UTF-16 LE BOM), but g_szXMLHeader declared encoding="iso-8859-1", causing every conformant XML parser to reject the file. Updated the shared header constant to declare encoding="UTF-16". * Remove the dead "EML file (using PR_INTERNET_CONTENT)" export option and its SaveToEML implementation. The PR_INTERNET_CONTENT path has not produced usable EML on any modern store for years. * Add an "Enable legacy features" registry/Options toggle (default off). When off, the remaining IConverterSession-based EML export option is hidden from the Save Message To File dropdown. The export code path is preserved so administrators can re-enable it without a rebuild. * Refactor the exporter dropdown -> exportType mapping from a positional static_cast to a switch on GetDropDownValue so the enum no longer has to track dropdown ordering. This is what makes the gated-row behavior above safe. --- UI/file/exporter.cpp | 64 +++++++++++++++++++++++++-------------- UI/file/exporter.h | 5 ++- core/mapi/mapiFile.cpp | 35 --------------------- core/mapi/mapiFile.h | 1 - core/res/MFCMapi.rc2 | 4 +-- core/res/Resource.h | 2 +- core/utility/output.cpp | 5 ++- core/utility/registry.cpp | 2 ++ core/utility/registry.h | 1 + 9 files changed, 53 insertions(+), 66 deletions(-) diff --git a/UI/file/exporter.cpp b/UI/file/exporter.cpp index 65e20ecff..3e0be1abc 100644 --- a/UI/file/exporter.cpp +++ b/UI/file/exporter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -23,15 +24,16 @@ namespace file dialog::editor::CEditor MyData( pParentWnd, IDS_SAVEMESSAGETOFILE, IDS_SAVEMESSAGETOFILEPROMPT, CEDITOR_BUTTON_OK | CEDITOR_BUTTON_CANCEL); - UINT uidDropDown[] = { - IDS_DDTEXTFILE, - IDS_DDMSGFILEANSI, - IDS_DDMSGFILEUNICODE, - IDS_DDEMLFILE, - IDS_DDEMLFILEUSINGICONVERTERSESSION, - IDS_DDTNEFFILE}; + std::vector uidDropDown{IDS_DDTEXTFILE, IDS_DDMSGFILEANSI, IDS_DDMSGFILEUNICODE}; + if (registry::enableLegacyFeatures) + { + uidDropDown.push_back(IDS_DDEMLFILEUSINGICONVERTERSESSION); + } + uidDropDown.push_back(IDS_DDTNEFFILE); + MyData.AddPane( - viewpane::DropDownPane::Create(0, IDS_FORMATTOSAVEMESSAGE, _countof(uidDropDown), uidDropDown, true)); + viewpane::DropDownPane::Create( + 0, IDS_FORMATTOSAVEMESSAGE, static_cast(uidDropDown.size()), uidDropDown.data(), true)); if (bMultiSelect) { MyData.AddPane(viewpane::CheckPane::Create(1, IDS_EXPORTPROMPTLOCATION, false, false)); @@ -39,7 +41,25 @@ namespace file if (!MyData.DisplayDialog()) return false; - exportType = static_cast(MyData.GetDropDown(0)); + switch (MyData.GetDropDownValue(0)) + { + case IDS_DDTEXTFILE: + exportType = exportType::text; + break; + case IDS_DDMSGFILEANSI: + exportType = exportType::msgAnsi; + break; + case IDS_DDMSGFILEUNICODE: + exportType = exportType::msgUnicode; + break; + case IDS_DDEMLFILEUSINGICONVERTERSESSION: + exportType = exportType::emlIConverter; + break; + case IDS_DDTNEFFILE: + exportType = exportType::tnef; + break; + } + bPrompt = !bMultiSelect || MyData.GetCheck(1); switch (exportType) @@ -55,7 +75,6 @@ namespace file szDotExt = L".msg"; szFilter = strings::loadstring(IDS_MSGFILES); break; - case exportType::eml: case exportType::emlIConverter: szExt = L"eml"; szDotExt = L".eml"; @@ -110,9 +129,6 @@ namespace file case exportType::msgUnicode: return EC_H(file::SaveToMSG(lpMessage, filename, true, pParentWnd->GetSafeHwnd(), true)); break; - case exportType::eml: - return EC_H(file::SaveToEML(lpMessage, filename)); - break; case exportType::emlIConverter: { auto ulConvertFlags = CCSF_SMTP; @@ -121,18 +137,20 @@ namespace file ULONG ulWrapLines = USE_DEFAULT_WRAPPING; auto bDoAdrBook = false; - hRes = EC_H(ui::mapiui::GetConversionToEMLOptions( - pParentWnd, &ulConvertFlags, &et, &mst, &ulWrapLines, &bDoAdrBook)); + hRes = EC_H( + ui::mapiui::GetConversionToEMLOptions( + pParentWnd, &ulConvertFlags, &et, &mst, &ulWrapLines, &bDoAdrBook)); if (hRes == S_OK) { - return EC_H(mapi::mapimime::ExportIMessageToEML( - lpMessage, - filename.c_str(), - ulConvertFlags, - et, - mst, - ulWrapLines, - bDoAdrBook ? lpAddrBook : nullptr)); + return EC_H( + mapi::mapimime::ExportIMessageToEML( + lpMessage, + filename.c_str(), + ulConvertFlags, + et, + mst, + ulWrapLines, + bDoAdrBook ? lpAddrBook : nullptr)); } } diff --git a/UI/file/exporter.h b/UI/file/exporter.h index f02d16b42..2bc0c155e 100644 --- a/UI/file/exporter.h +++ b/UI/file/exporter.h @@ -7,9 +7,8 @@ namespace file text = 0, msgAnsi = 1, msgUnicode = 2, - eml = 3, - emlIConverter = 4, - tnef = 5 + emlIConverter = 3, + tnef = 4 }; class exporter diff --git a/core/mapi/mapiFile.cpp b/core/mapi/mapiFile.cpp index 4a20a0a14..a14b827f6 100644 --- a/core/mapi/mapiFile.cpp +++ b/core/mapi/mapiFile.cpp @@ -326,41 +326,6 @@ namespace file return hRes; } - _Check_return_ HRESULT SaveToEML(_In_ LPMESSAGE lpMessage, _In_ const std::wstring& szFileName) - { - LPSTREAM pStrmSrc = nullptr; - - if (!lpMessage || szFileName.empty()) return MAPI_E_INVALID_PARAMETER; - output::DebugPrint(output::dbgLevel::Generic, L"SaveToEML: Saving message to \"%ws\"\n", szFileName.c_str()); - - // Open the property of the attachment - // containing the file data - auto hRes = EC_MAPI(lpMessage->OpenProperty( - PR_INTERNET_CONTENT, // TODO: There's a modern property for this... - const_cast(&IID_IStream), - 0, - NULL, // MAPI_MODIFY is not needed - reinterpret_cast(&pStrmSrc))); - if (FAILED(hRes)) - { - if (hRes == MAPI_E_NOT_FOUND) - { - output::DebugPrint(output::dbgLevel::Generic, L"No internet content found\n"); - } - } - else - { - if (pStrmSrc) - { - hRes = WC_H(WriteStreamToFile(pStrmSrc, szFileName)); - - pStrmSrc->Release(); - } - } - - return hRes; - } - _Check_return_ HRESULT STDAPICALLTYPE MyStgCreateStorageEx( _In_ const std::wstring& pName, DWORD grfMode, diff --git a/core/mapi/mapiFile.h b/core/mapi/mapiFile.h index 2ab593617..14b019cef 100644 --- a/core/mapi/mapiFile.h +++ b/core/mapi/mapiFile.h @@ -15,7 +15,6 @@ namespace file bool bAssoc, bool bUnicode, HWND hWnd); - _Check_return_ HRESULT SaveToEML(_In_ LPMESSAGE lpMessage, _In_ const std::wstring& szFileName); _Check_return_ HRESULT CreateNewMSG( _In_ const std::wstring& szFileName, bool bUnicode, diff --git a/core/res/MFCMapi.rc2 b/core/res/MFCMapi.rc2 index 6858b92b3..185cbe53d 100644 --- a/core/res/MFCMapi.rc2 +++ b/core/res/MFCMapi.rc2 @@ -995,6 +995,7 @@ IDS_REGKEY_FORCEOUTLOOKMAPI "Force the use of Outlook's MAPI files" IDS_REGKEY_FORCESYSTEMMAPI "Force the use of the system's MAPI files" IDS_REGKEY_PREFER_OLMAPI32 "Prefer olmapi32.dll over other MAPI files" IDS_REGKEY_UIDIAG "Show UI diagnostics" +IDS_REGKEY_ENABLE_LEGACY_FEATURES "Enable legacy features" // RestrictEditor.cpp IDS_RELOP "relop" @@ -1200,10 +1201,9 @@ IDS_DDDISPLAYPROPSONLY "Only display properties from MSG file" IDS_DDENTERFORMCLASS "Manually enter form class" IDS_DDFOLDERFORMLIBRARY "Folder Form library" IDS_DDORGFORMLIBRARY "Organization Forms and Application Forms libraries" -IDS_DDTEXTFILE "Text file (saves all properties of message to a text file)" +IDS_DDTEXTFILE "XML file (saves all properties of message to an XML file)" IDS_DDMSGFILEANSI "MSG file (ANSI)" IDS_DDMSGFILEUNICODE "MSG file (UNICODE)" -IDS_DDEMLFILE "EML file (using PR_INTERNET_CONTENT)" IDS_DDEMLFILEUSINGICONVERTERSESSION "EML file (using IConverterSession)" IDS_DDTNEFFILE "TNEF file (same format as winmail.dat)" IDS_DDCOPYPROPS "IMAPIProp::CopyProps" diff --git a/core/res/Resource.h b/core/res/Resource.h index d6007cc2c..6a2c79487 100644 --- a/core/res/Resource.h +++ b/core/res/Resource.h @@ -807,7 +807,6 @@ #define IDS_DDORGFORMLIBRARY 35246 #define IDS_DDTEXTFILE 35247 #define IDS_DDMSGFILEANSI 35248 -#define IDS_DDEMLFILE 35249 #define IDS_DDTNEFFILE 35250 #define IDS_DDCOPYPROPS 35251 #define IDS_DDGETSETPROPS 35252 @@ -1235,3 +1234,4 @@ #define IDS_CAPABILITIES_FOLDER 35872 #define IDS_CAPABILITIES_RESTRICTION 35873 #define IDS_REGKEY_PREFER_OLMAPI32 35874 +#define IDS_REGKEY_ENABLE_LEGACY_FEATURES 35875 diff --git a/core/utility/output.cpp b/core/utility/output.cpp index 28563e97c..93c184705 100644 --- a/core/utility/output.cpp +++ b/core/utility/output.cpp @@ -15,7 +15,10 @@ namespace output { - std::wstring g_szXMLHeader = L"\n"; + // Files are opened with the CRT "ccs=UNICODE" mode, which writes a UTF-16 LE BOM + // and encodes each character as two little-endian bytes. The declared encoding + // must match, otherwise XML parsers reject the file with an encoding mismatch. + std::wstring g_szXMLHeader = L"\n"; std::function outputToDbgView; FILE* g_fDebugFile = nullptr; diff --git a/core/utility/registry.cpp b/core/utility/registry.cpp index 09f268c1f..0aefa4dc2 100644 --- a/core/utility/registry.cpp +++ b/core/utility/registry.cpp @@ -67,6 +67,7 @@ namespace registry boolRegKey preferOlmapi32{L"PreferOlmapi32", true, false, IDS_REGKEY_PREFER_OLMAPI32}; boolRegKey uiDiag{L"UIDiag", false, false, IDS_REGKEY_UIDIAG}; boolRegKey displayAboutDialog{L"DisplayAboutDialog", true, false, NULL}; + boolRegKey enableLegacyFeatures{L"EnableLegacyFeatures", false, false, IDS_REGKEY_ENABLE_LEGACY_FEATURES}; wstringRegKey propertyColumnOrder{L"PropertyColumnOrder", L"", false, NULL}; dwordRegKey namedPropBatchSize{L"NamedPropBatchSize", regOptionType::stringDec, 400, false, NULL}; @@ -101,6 +102,7 @@ namespace registry &preferOlmapi32, &uiDiag, &displayAboutDialog, + &enableLegacyFeatures, &propertyColumnOrder, &namedPropBatchSize}; diff --git a/core/utility/registry.h b/core/utility/registry.h index 338ffe57c..14fcd11fd 100644 --- a/core/utility/registry.h +++ b/core/utility/registry.h @@ -198,6 +198,7 @@ namespace registry extern boolRegKey preferOlmapi32; extern boolRegKey uiDiag; extern boolRegKey displayAboutDialog; + extern boolRegKey enableLegacyFeatures; extern wstringRegKey propertyColumnOrder; extern dwordRegKey namedPropBatchSize; } // namespace registry \ No newline at end of file From 0328926141500028f52be0d8eb9bf4123bb3a425 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:21:34 +0000 Subject: [PATCH 2/3] Initial plan From 8550fa650b128fd78e907583027cd64076dd82c9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 20:25:27 +0000 Subject: [PATCH 3/3] Format exporter for clang-format check --- UI/file/exporter.cpp | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/UI/file/exporter.cpp b/UI/file/exporter.cpp index 3e0be1abc..288360b1b 100644 --- a/UI/file/exporter.cpp +++ b/UI/file/exporter.cpp @@ -31,9 +31,8 @@ namespace file } uidDropDown.push_back(IDS_DDTNEFFILE); - MyData.AddPane( - viewpane::DropDownPane::Create( - 0, IDS_FORMATTOSAVEMESSAGE, static_cast(uidDropDown.size()), uidDropDown.data(), true)); + MyData.AddPane(viewpane::DropDownPane::Create( + 0, IDS_FORMATTOSAVEMESSAGE, static_cast(uidDropDown.size()), uidDropDown.data(), true)); if (bMultiSelect) { MyData.AddPane(viewpane::CheckPane::Create(1, IDS_EXPORTPROMPTLOCATION, false, false)); @@ -137,20 +136,18 @@ namespace file ULONG ulWrapLines = USE_DEFAULT_WRAPPING; auto bDoAdrBook = false; - hRes = EC_H( - ui::mapiui::GetConversionToEMLOptions( - pParentWnd, &ulConvertFlags, &et, &mst, &ulWrapLines, &bDoAdrBook)); + hRes = EC_H(ui::mapiui::GetConversionToEMLOptions( + pParentWnd, &ulConvertFlags, &et, &mst, &ulWrapLines, &bDoAdrBook)); if (hRes == S_OK) { - return EC_H( - mapi::mapimime::ExportIMessageToEML( - lpMessage, - filename.c_str(), - ulConvertFlags, - et, - mst, - ulWrapLines, - bDoAdrBook ? lpAddrBook : nullptr)); + return EC_H(mapi::mapimime::ExportIMessageToEML( + lpMessage, + filename.c_str(), + ulConvertFlags, + et, + mst, + ulWrapLines, + bDoAdrBook ? lpAddrBook : nullptr)); } }