First pass of security fixes

This commit is contained in:
Justin Marshall
2026-08-10 22:31:19 -07:00
parent 2b1165d869
commit 6d95e7d78d
17 changed files with 726 additions and 158 deletions
+5 -4
View File
@@ -689,7 +689,7 @@ target_include_directories(opus_original_engine PRIVATE ${OPUS_ORIGINAL_INCLUDE_
if(MSVC)
# /J preserves the original compiler's unsigned-char default. Do not use
# the C++ compiler for K&R-era original C translation units.
target_compile_options(opus_original_engine PRIVATE /J /W3 /wd4005 /Gy)
target_compile_options(opus_original_engine PRIVATE /J /W3 /wd4005 /Gy /guard:cf)
endif()
set_target_properties(opus_original_engine PROPERTIES
FOLDER "Original Opus x64"
@@ -731,13 +731,14 @@ target_compile_definitions(opus_x64_runtime PRIVATE
NONATIVE
OPUS_X64
NOMINMAX
$<$<CONFIG:Debug>:OPUS_TEST_HOOKS>
)
target_include_directories(opus_x64_runtime PRIVATE ${OPUS_ORIGINAL_INCLUDE_DIRS})
target_link_libraries(opus_x64_runtime PUBLIC ole32 comdlg32 gdi32)
if(MSVC)
# Opus was built with unsigned plain characters; the byte-oriented string
# and international-character assembly routines depend on that behavior.
target_compile_options(opus_x64_runtime PRIVATE /J /W4 /permissive-)
target_compile_options(opus_x64_runtime PRIVATE /J /W4 /permissive- /guard:cf /sdl)
endif()
set_target_properties(opus_x64_runtime PROPERTIES
FOLDER "Original Opus x64/runtime"
@@ -866,8 +867,8 @@ target_compile_definitions(WORD1 PRIVATE
)
target_link_libraries(WORD1 PRIVATE opus_original_engine opus_x64_runtime user32 dbghelp)
if(MSVC)
target_compile_options(WORD1 PRIVATE /J /W3 /wd4005 /utf-8)
target_link_options(WORD1 PRIVATE /DYNAMICBASE /HIGHENTROPYVA /NXCOMPAT /MANIFEST:NO)
target_compile_options(WORD1 PRIVATE /J /W3 /wd4005 /utf-8 /guard:cf /sdl)
target_link_options(WORD1 PRIVATE /DYNAMICBASE /HIGHENTROPYVA /NXCOMPAT /guard:cf /CETCOMPAT /MANIFEST:NO)
endif()
set_target_properties(WORD1 PROPERTIES
FOLDER "Original Opus x64/product"
+8
View File
@@ -81,6 +81,10 @@ int wLow, wHigh;
int das;
BOOL f;
#ifdef OPUS_X64
/* DDE execute is unauthenticated cross-process macro execution. */
goto LExeNACK;
#endif
if (fTerminating || PdcldDcl(dcl)->fExecuting || fElActive)
goto LExeNACK;
@@ -117,6 +121,10 @@ LExeNACK:
int das = dasNACK;
int ibkmk;
#ifdef OPUS_X64
/* Do not let another process overwrite an open document through DDE. */
goto LNackPoke;
#endif
if (fTerminating)
/* not much point in receiving while terminating */
{
+5
View File
@@ -2479,6 +2479,11 @@ uns atm;
Assert(atm < atmMax);
#ifdef OPUS_X64
/* Never execute document/template macros merely because a file opened,
closed, or Word started. See the secure default in elmisc.c. */
return cmdOK;
#endif
/* Skip auto macro if shift key is down */
if (vfDisableAutoMacros || (GetAsyncKeyState(VK_SHIFT) & 0x8000))
+11
View File
@@ -80,7 +80,18 @@ extern BOOL vfAwfulNoise;
extern int vfSeeSel;
#ifdef OPUS_X64
/*
* Legacy Opus documents and templates can contain AutoOpen/AutoExec macros.
* The original application ran them without a trust boundary. That is not
* an acceptable default for documents received from modern file systems, so
* the x64 port keeps automatic macros disabled. Explicitly invoked macros
* continue to use the normal command path.
*/
BOOL vfDisableAutoMacros = fTrue;
#else
BOOL vfDisableAutoMacros = fFalse;
#endif
/* Simple Cursor Movement / Selection Commands */
+2
View File
@@ -620,6 +620,7 @@ BOOL fTutorial;
if (!fTutorial)
{
/* BLOCK: now run macros specified on command line */
#ifndef OPUS_X64
{
int isz;
CHAR * szArg;
@@ -639,6 +640,7 @@ BOOL fTutorial;
}
}
}
#endif /* !OPUS_X64 */
}
+7
View File
@@ -104,6 +104,7 @@ extern struct REB * vpreb;
static int vWin95ZoomPercent = 100;
static int vWin95BaseDxsInch = 0;
static int vWin95BaseDysInch = 0;
int vOpusPdfExportStage = 0;
int OpusExportCurrentDocumentPdf()
{
@@ -120,6 +121,7 @@ int OpusExportCurrentDocumentPdf()
extern int OpusPdfSnapshotExportDialog();
extern void OpusX64FontNameFromFtc();
vOpusPdfExportStage = 1;
if (selCur.doc == docNil)
return fFalse;
doc = DocMother(selCur.doc);
@@ -127,6 +129,7 @@ int OpusExportCurrentDocumentPdf()
if (cpMac < cp0 || cpMac > 0x3fffffffL)
return fFalse;
pdod = PdodMother(doc);
vOpusPdfExportStage = 2;
if (!OpusPdfSnapshotBegin(pdod->dop.xaPage, pdod->dop.yaPage,
pdod->dop.dxaLeft, pdod->dop.dxaRight,
pdod->dop.dyaTop < 0 ? -pdod->dop.dyaTop : pdod->dop.dyaTop,
@@ -141,6 +144,7 @@ int OpusExportCurrentDocumentPdf()
cpParaLim = CpMin(caPara.cpLim, cpMac);
if (cpParaLim <= cpPara)
cpParaLim = cpPara + 1;
vOpusPdfExportStage = 1000 + (int)cpPara;
if (!OpusPdfSnapshotAddParagraph(vpapFetch.jc,
vpapFetch.dxaLeft, vpapFetch.dxaRight,
vpapFetch.dxaLeft1, vpapFetch.dyaBefore,
@@ -185,6 +189,7 @@ int OpusExportCurrentDocumentPdf()
rgch[cchOutput] = '\0';
OpusX64FontNameFromFtc(vchpFetch.ftc,
szFont, sizeof(szFont));
vOpusPdfExportStage = 100000 + (int)cpRun;
if (!OpusPdfSnapshotAddRun(rgch, cchOutput, szFont,
vchpFetch.hps, vchpFetch.fBold,
vchpFetch.fItalic, vchpFetch.kul != kulNone,
@@ -197,7 +202,9 @@ int OpusExportCurrentDocumentPdf()
}
cpPara = cpParaLim;
}
vOpusPdfExportStage = 3;
result = OpusPdfSnapshotExportDialog(vhwndApp);
vOpusPdfExportStage = result ? 4 : -4;
return result;
}
+5
View File
@@ -1829,6 +1829,11 @@ LONG lParam;
pffn = (struct FFN *)PstFromSttb(vhsttbFont, ibst);
return (LRESULT)(unsigned char)ChsPffn(pffn);
}
case 105:
{
extern int vOpusPdfExportStage;
return (LRESULT)vOpusPdfExportStage;
}
}
return (LRESULT) -1;
#endif
+9 -2
View File
@@ -143,14 +143,21 @@ int CchCurSzPathNat(char* path, const int drive) {
if (path == nullptr) {
return 0;
}
/* The original ABI requires callers to provide exactly 67 bytes. Do not
copy a modern MAX_PATH-sized current directory into that legacy buffer. */
constexpr int kLegacyPathCapacity = 67;
char current[MAX_PATH]{};
if (_getdcwd(drive, current, static_cast<int>(sizeof(current))) ==
nullptr) {
path[0] = '\0';
return 0;
}
std::strcpy(path, current);
std::size_t length = std::strlen(path);
std::size_t length = std::strlen(current);
if (length + 2 > kLegacyPathCapacity) {
path[0] = '\0';
return 0;
}
std::memcpy(path, current, length + 1);
if (length == 2 && path[1] == ':') {
path[length++] = '\\';
path[length] = '\0';
+6
View File
@@ -137,6 +137,12 @@ constexpr UINT kPromptMessages[] = {
extern "C" {
LRESULT CALLBACK NatAppWndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
if (m == kWmDdeInitiate) {
// The legacy DDE server exposes document contents and macro commands
// to any process in the desktop session. The modern port does not
// accept inbound DDE conversations.
return 0;
}
return Dispatch(h, m, w, l, AppWndProc, kAppMessages);
}
LRESULT CALLBACK NatMwdWndProc(HWND h, UINT m, WPARAM w, LPARAM l) {
+337 -80
View File
@@ -7,14 +7,18 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <cctype>
#include <charconv>
#include <cwctype>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <iterator>
#include <limits>
#include <map>
#include <set>
#include <stdexcept>
#include <string>
#include <string_view>
#include <vector>
@@ -23,6 +27,50 @@ using Microsoft::WRL::ComPtr;
namespace {
constexpr std::size_t kMaxDocumentXmlBytes = 64u * 1024u * 1024u;
constexpr std::size_t kMaxStylesXmlBytes = 8u * 1024u * 1024u;
constexpr std::size_t kMaxRtfBytes = 64u * 1024u * 1024u;
constexpr std::size_t kMaxTextBytes = 32u * 1024u * 1024u;
constexpr std::size_t kMaxGeneratedBytes = 256u * 1024u * 1024u;
constexpr std::size_t kMaxStyles = 4096;
constexpr std::size_t kMaxParagraphs = 200000;
constexpr std::size_t kMaxRuns = 1000000;
constexpr std::size_t kMaxTableRows = 4096;
constexpr std::size_t kMaxTableColumns = 256;
constexpr std::size_t kMaxTableCells = 262144;
void require_parse_limit(const bool condition) {
if (!condition) throw std::length_error("document exceeds import limits");
}
bool parse_bounded_int(std::string_view text, const int minimum,
const int maximum, int& result) {
if (text.empty()) return false;
int parsed = 0;
const auto conversion = std::from_chars(
text.data(), text.data() + text.size(), parsed, 10);
if (conversion.ec != std::errc{} ||
conversion.ptr != text.data() + text.size() ||
parsed < minimum || parsed > maximum) {
return false;
}
result = parsed;
return true;
}
bool parse_rgb(std::string_view text, unsigned& result) {
if (text.size() != 6) return false;
unsigned parsed = 0;
const auto conversion = std::from_chars(
text.data(), text.data() + text.size(), parsed, 16);
if (conversion.ec != std::errc{} ||
conversion.ptr != text.data() + text.size() || parsed > 0xffffff) {
return false;
}
result = parsed;
return true;
}
struct ComApartment {
HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
~ComApartment() {
@@ -180,11 +228,37 @@ bool has_extension(const std::string& path, const char* extension) {
_stricmp(path.c_str() + path.size() - length, extension) == 0;
}
bool read_stream(IStream* stream, std::string& data) {
bool safe_file_path_syntax(std::wstring_view path) {
if (path.empty() || path.size() >= 32760 ||
path.starts_with(LR"(\\.\)") ||
path.starts_with(LR"(\\?\GLOBALROOT\)")) return false;
for (std::size_t index = 0; index < path.size(); ++index) {
if (path[index] == L':' && index != 1) return false;
}
return true;
}
bool regular_file_within_limit(const std::wstring& path,
const std::size_t maximum_size) {
if (!safe_file_path_syntax(path)) return false;
WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (!GetFileAttributesExW(path.c_str(), GetFileExInfoStandard,
&attributes) ||
(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
return false;
}
const ULONGLONG size =
(static_cast<ULONGLONG>(attributes.nFileSizeHigh) << 32) |
attributes.nFileSizeLow;
return size <= maximum_size;
}
bool read_stream(IStream* stream, std::string& data,
const std::size_t maximum_size) {
STATSTG status{};
if (stream == nullptr || FAILED(stream->Stat(&status, STATFLAG_NONAME)) ||
status.cbSize.QuadPart < 0 ||
status.cbSize.QuadPart > static_cast<LONGLONG>(256 * 1024 * 1024)) {
status.cbSize.QuadPart > static_cast<ULONGLONG>(maximum_size) ||
maximum_size > MAXDWORD) {
return false;
}
data.resize(static_cast<std::size_t>(status.cbSize.QuadPart));
@@ -197,7 +271,8 @@ bool read_stream(IStream* stream, std::string& data) {
}
bool read_opc_part(const std::wstring& path, const wchar_t* part_name,
std::string& data) {
std::string& data, const std::size_t maximum_size) {
if (!regular_file_within_limit(path, kMaxGeneratedBytes)) return false;
ComApartment apartment;
if (!apartment.usable()) return false;
ComPtr<IOpcFactory> factory;
@@ -221,7 +296,7 @@ bool read_opc_part(const std::wstring& path, const wchar_t* part_name,
SUCCEEDED(factory->CreatePartUri(part_name, &uri)) &&
SUCCEEDED(parts->GetPart(uri.Get(), &part)) &&
SUCCEEDED(part->GetContentStream(&content)) &&
read_stream(content.Get(), data);
read_stream(content.Get(), data, maximum_size);
}
std::string tag_attribute(std::string_view tag, std::string_view name) {
@@ -371,7 +446,7 @@ void apply_run_properties(RunStyle& style, std::string_view props) {
apply_switch("caps", style.all_caps);
apply_switch("vanish", style.hidden);
if (const std::string size = first_property_value(props, "sz"); !size.empty()) {
style.half_points = (std::max)(2, std::atoi(size.c_str()));
parse_bounded_int(size, 2, 254, style.half_points);
}
if (const std::string font = first_property_value(props, "rFonts", "ascii");
!font.empty()) {
@@ -379,9 +454,12 @@ void apply_run_properties(RunStyle& style, std::string_view props) {
}
if (const std::string color = first_property_value(props, "color");
!color.empty() && color != "auto" && color.size() == 6) {
const unsigned rgb = std::strtoul(color.c_str(), nullptr, 16);
style.color = RGB((rgb >> 16) & 0xff, (rgb >> 8) & 0xff, rgb & 0xff);
unsigned rgb = 0;
if (parse_rgb(color, rgb)) {
style.color = RGB((rgb >> 16) & 0xff,
(rgb >> 8) & 0xff, rgb & 0xff);
style.auto_color = false;
}
} else if (first_property_value(props, "color") == "auto") {
style.auto_color = true;
style.color = RGB(0, 0, 0);
@@ -402,19 +480,22 @@ void apply_paragraph_properties(Paragraph& paragraph, std::string_view props) {
const std::string right = tag_attribute(indent, "right");
const std::string first = tag_attribute(indent, "firstLine");
const std::string hanging = tag_attribute(indent, "hanging");
if (!left.empty()) paragraph.left_indent = std::atoi(left.c_str());
if (!right.empty()) paragraph.right_indent = std::atoi(right.c_str());
if (!first.empty()) paragraph.first_line_indent = std::atoi(first.c_str());
if (!hanging.empty()) paragraph.first_line_indent = -std::atoi(hanging.c_str());
parse_bounded_int(left, -31680, 31680, paragraph.left_indent);
parse_bounded_int(right, -31680, 31680, paragraph.right_indent);
parse_bounded_int(first, -31680, 31680,
paragraph.first_line_indent);
int hanging_indent = 0;
if (parse_bounded_int(hanging, 0, 31680, hanging_indent))
paragraph.first_line_indent = -hanging_indent;
}
if (const std::string_view spacing = element_block(props, "spacing");
!spacing.empty()) {
const std::string before = tag_attribute(spacing, "before");
const std::string after = tag_attribute(spacing, "after");
const std::string line = tag_attribute(spacing, "line");
if (!before.empty()) paragraph.space_before = std::atoi(before.c_str());
if (!after.empty()) paragraph.space_after = std::atoi(after.c_str());
if (!line.empty()) paragraph.line_spacing = std::atoi(line.c_str());
parse_bounded_int(before, 0, 31680, paragraph.space_before);
parse_bounded_int(after, 0, 31680, paragraph.space_after);
parse_bounded_int(line, 0, 31680, paragraph.line_spacing);
}
const auto apply_switch = [&](const char* name, bool& target) {
const int state = property_state(props, name);
@@ -473,6 +554,7 @@ StyleCatalog parse_style_catalog(std::string_view xml) {
const std::string id = tag_attribute(opening, "styleId");
const std::string_view block = xml.substr(position, close + 10 - position);
if (!id.empty()) {
require_parse_limit(catalog.definitions.size() < kMaxStyles);
StyleDefinition definition;
definition.based_on = first_property_value(block, "basedOn");
if (const std::string_view props = element_block(block, "rPr");
@@ -522,6 +604,7 @@ std::wstring parse_run_text(std::string_view run) {
const std::size_t close = run.find("</w:t>", tag_end + 1);
if (close == std::string_view::npos) break;
text += xml_unescape(run.substr(tag_end + 1, close - tag_end - 1));
require_parse_limit(text.size() <= kMaxTextBytes);
position = close + 6;
} else {
if (name == "tab") text.push_back(L'\t');
@@ -538,7 +621,8 @@ std::wstring parse_run_text(std::string_view run) {
}
std::vector<Paragraph> parse_paragraphs_flat(
std::string_view xml, const StyleCatalog& catalog = {}) {
std::string_view xml, const StyleCatalog& catalog,
std::size_t& total_paragraphs, std::size_t& total_runs) {
std::vector<Paragraph> paragraphs;
std::size_t position = 0;
while ((position = xml.find("<w:p", position)) != std::string_view::npos) {
@@ -577,11 +661,18 @@ std::vector<Paragraph> parse_paragraphs_flat(
const std::string_view run = block.substr(
run_position, run_close + 6 - run_position);
std::wstring text = parse_run_text(run);
if (!text.empty()) paragraph.runs.push_back(
{parse_run_style(run, paragraph_run, catalog), std::move(text)});
if (!text.empty()) {
require_parse_limit(total_runs < kMaxRuns);
paragraph.runs.push_back(
{parse_run_style(run, paragraph_run, catalog),
std::move(text)});
++total_runs;
}
run_position = run_close + 6;
}
require_parse_limit(total_paragraphs < kMaxParagraphs);
paragraphs.push_back(std::move(paragraph));
++total_paragraphs;
position = close + 6;
}
if (paragraphs.empty()) paragraphs.push_back({});
@@ -615,6 +706,8 @@ std::vector<Paragraph> parse_document_xml(
std::vector<TableLayout>* table_layouts = nullptr) {
std::vector<Paragraph> paragraphs;
if (table_layouts != nullptr) table_layouts->clear();
std::size_t total_paragraphs = 0;
std::size_t total_runs = 0;
std::size_t cursor = 0;
while (cursor < xml.size()) {
const std::size_t table_start = find_word_tag(xml, "tbl", cursor);
@@ -622,10 +715,12 @@ std::vector<Paragraph> parse_document_xml(
const std::string_view remainder = xml.substr(cursor);
if (find_word_tag(remainder, "p", 0) != std::string_view::npos) {
std::vector<Paragraph> tail =
parse_paragraphs_flat(remainder, catalog);
parse_paragraphs_flat(remainder, catalog,
total_paragraphs, total_runs);
paragraphs.insert(paragraphs.end(),
std::make_move_iterator(tail.begin()),
std::make_move_iterator(tail.end()));
require_parse_limit(paragraphs.size() <= kMaxParagraphs);
}
break;
}
@@ -633,10 +728,12 @@ std::vector<Paragraph> parse_document_xml(
xml.substr(cursor, table_start - cursor);
if (find_word_tag(prefix, "p", 0) != std::string_view::npos) {
std::vector<Paragraph> before =
parse_paragraphs_flat(prefix, catalog);
parse_paragraphs_flat(prefix, catalog,
total_paragraphs, total_runs);
paragraphs.insert(paragraphs.end(),
std::make_move_iterator(before.begin()),
std::make_move_iterator(before.end()));
require_parse_limit(paragraphs.size() <= kMaxParagraphs);
}
const std::size_t table_close = xml.find("</w:tbl>", table_start);
if (table_close == std::string_view::npos) break;
@@ -646,12 +743,15 @@ std::vector<Paragraph> parse_document_xml(
TableLayout layout;
layout.first_paragraph = paragraphs.size();
std::size_t row_cursor = 0;
std::size_t table_cell_count = 0;
while (true) {
const std::size_t row_start = find_word_tag(table, "tr", row_cursor);
if (row_start == std::string_view::npos) break;
const std::size_t row_close = table.find("</w:tr>", row_start);
if (row_close == std::string_view::npos) break;
const std::size_t row_end = row_close + std::strlen("</w:tr>");
require_parse_limit(static_cast<std::size_t>(layout.rows) <
kMaxTableRows);
const std::string_view row = table.substr(row_start,
row_end - row_start);
std::vector<std::vector<Paragraph>> cells;
@@ -662,8 +762,12 @@ std::vector<Paragraph> parse_document_xml(
const std::size_t cell_close = row.find("</w:tc>", cell_start);
if (cell_close == std::string_view::npos) break;
const std::size_t cell_end = cell_close + std::strlen("</w:tc>");
require_parse_limit(cells.size() < kMaxTableColumns &&
table_cell_count < kMaxTableCells);
cells.push_back(parse_paragraphs_flat(
row.substr(cell_start, cell_end - cell_start), catalog));
row.substr(cell_start, cell_end - cell_start), catalog,
total_paragraphs, total_runs));
++table_cell_count;
cell_cursor = cell_end;
}
std::size_t line_count = 0;
@@ -678,6 +782,8 @@ std::vector<Paragraph> parse_document_xml(
paragraphs.push_back(std::move(cell[line]));
else
paragraphs.push_back({});
require_parse_limit(paragraphs.size() <=
kMaxParagraphs);
}
++layout.rows;
}
@@ -691,10 +797,12 @@ std::vector<Paragraph> parse_document_xml(
if (table_layouts != nullptr) table_layouts->push_back(layout);
} else {
paragraphs.resize(layout.first_paragraph);
std::vector<Paragraph> flat = parse_paragraphs_flat(table, catalog);
std::vector<Paragraph> flat = parse_paragraphs_flat(
table, catalog, total_paragraphs, total_runs);
paragraphs.insert(paragraphs.end(),
std::make_move_iterator(flat.begin()),
std::make_move_iterator(flat.end()));
require_parse_limit(paragraphs.size() <= kMaxParagraphs);
}
cursor = table_end;
}
@@ -707,20 +815,22 @@ DocumentSettings parse_document_settings(std::string_view xml) {
const std::string_view section = element_block(xml, "sectPr");
const std::string_view size = element_block(section, "pgSz");
const std::string_view margins = element_block(section, "pgMar");
const auto integer_attribute = [](std::string_view tag,
const char* name) {
const auto integer_attribute = [](std::string_view tag, const char* name,
const int minimum, const int maximum) {
const std::string value = tag_attribute(tag, name);
return value.empty() ? 0 : std::atoi(value.c_str());
int result = 0;
parse_bounded_int(value, minimum, maximum, result);
return result;
};
settings.page_width = integer_attribute(size, "w");
settings.page_height = integer_attribute(size, "h");
settings.margin_left = integer_attribute(margins, "left");
settings.margin_right = integer_attribute(margins, "right");
settings.margin_top = integer_attribute(margins, "top");
settings.margin_bottom = integer_attribute(margins, "bottom");
settings.page_width = integer_attribute(size, "w", 720, 63360);
settings.page_height = integer_attribute(size, "h", 720, 63360);
settings.margin_left = integer_attribute(margins, "left", 0, 31680);
settings.margin_right = integer_attribute(margins, "right", 0, 31680);
settings.margin_top = integer_attribute(margins, "top", 0, 31680);
settings.margin_bottom = integer_attribute(margins, "bottom", 0, 31680);
settings.valid = settings.page_width > 0 && settings.page_height > 0 &&
settings.margin_left >= 0 && settings.margin_right >= 0 &&
settings.margin_top >= 0 && settings.margin_bottom >= 0;
settings.margin_left + settings.margin_right < settings.page_width &&
settings.margin_top + settings.margin_bottom < settings.page_height;
return settings;
}
@@ -756,6 +866,8 @@ PendingDocxImport pending_docx_import;
struct PendingPdfExport {
std::vector<Paragraph> paragraphs;
DocumentSettings settings;
std::size_t run_count = 0;
std::size_t text_bytes = 0;
};
PendingPdfExport pending_pdf_export;
@@ -780,6 +892,7 @@ std::string paragraphs_to_text(const std::vector<Paragraph>& paragraphs,
std::string text;
if (pending != nullptr) *pending = {};
for (std::size_t index = 0; index < paragraphs.size(); ++index) {
require_parse_limit(text.size() <= kMaxTextBytes);
PendingParagraphFormat paragraph_format;
paragraph_format.cp_first = static_cast<long>(text.size());
paragraph_format.paragraph = paragraphs[index];
@@ -788,12 +901,17 @@ std::string paragraphs_to_text(const std::vector<Paragraph>& paragraphs,
PendingRunFormat run_format;
run_format.cp_first = static_cast<long>(text.size());
run_format.style = run.style;
text += ansi_run_text(run.text);
const std::string encoded = ansi_run_text(run.text);
require_parse_limit(encoded.size() <= kMaxTextBytes - text.size());
text += encoded;
run_format.cp_lim = static_cast<long>(text.size());
if (pending != nullptr && run_format.cp_lim > run_format.cp_first)
pending->runs.push_back(std::move(run_format));
}
if (index + 1 < paragraphs.size()) text += "\r\n";
if (index + 1 < paragraphs.size()) {
require_parse_limit(text.size() <= kMaxTextBytes - 2);
text += "\r\n";
}
paragraph_format.cp_lim = static_cast<long>(text.size());
if (pending != nullptr)
pending->paragraphs.push_back(std::move(paragraph_format));
@@ -824,9 +942,13 @@ bool load_docx_paragraphs(const char* path,
std::vector<TableLayout>* tables = nullptr) {
std::string document;
std::string styles;
if (!read_opc_part(wide_path(path), L"/word/document.xml", document))
const std::wstring document_path = wide_path(path);
if (document_path.empty() ||
!read_opc_part(document_path, L"/word/document.xml", document,
kMaxDocumentXmlBytes))
return false;
read_opc_part(wide_path(path), L"/word/styles.xml", styles);
read_opc_part(document_path, L"/word/styles.xml", styles,
kMaxStylesXmlBytes);
paragraphs = parse_document_xml(document, parse_style_catalog(styles),
tables);
if (settings != nullptr) *settings = parse_document_settings(document);
@@ -971,27 +1093,86 @@ std::string paragraphs_to_rtf(const std::vector<Paragraph>& paragraphs) {
return rtf;
}
bool write_bytes(const std::wstring& path, std::string_view bytes) {
HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) return false;
DWORD written = 0;
const bool ok = bytes.size() <= MAXDWORD &&
WriteFile(file, bytes.data(), static_cast<DWORD>(bytes.size()),
&written, nullptr) && written == bytes.size();
CloseHandle(file);
if (!ok) DeleteFileW(path.c_str());
return ok;
bool reserve_sibling_temporary_file(const std::wstring& path,
std::wstring& temporary,
HANDLE& file) {
if (!safe_file_path_syntax(path)) return false;
static std::atomic<unsigned long> sequence{0};
for (int attempt = 0; attempt < 64; ++attempt) {
temporary = path + L".word1tmp-" +
std::to_wstring(GetCurrentProcessId()) + L"-" +
std::to_wstring(++sequence);
file = CreateFileW(temporary.c_str(), GENERIC_WRITE, 0, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file != INVALID_HANDLE_VALUE) return true;
if (GetLastError() != ERROR_FILE_EXISTS &&
GetLastError() != ERROR_ALREADY_EXISTS) return false;
}
return false;
}
bool read_bytes(const std::wstring& path, std::string& bytes) {
bool commit_sibling_temporary_file(const std::wstring& temporary,
const std::wstring& path) {
const DWORD attributes = GetFileAttributesW(path.c_str());
bool replaced = false;
if (attributes != INVALID_FILE_ATTRIBUTES &&
(attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
replaced = ReplaceFileW(path.c_str(), temporary.c_str(), nullptr,
REPLACEFILE_WRITE_THROUGH, nullptr, nullptr) !=
FALSE;
if (!replaced) {
replaced = MoveFileExW(
temporary.c_str(), path.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != FALSE;
}
} else if (attributes == INVALID_FILE_ATTRIBUTES &&
GetLastError() == ERROR_FILE_NOT_FOUND) {
replaced = MoveFileExW(temporary.c_str(), path.c_str(),
MOVEFILE_WRITE_THROUGH) != FALSE;
}
if (!replaced) DeleteFileW(temporary.c_str());
return replaced;
}
bool write_bytes(const std::wstring& path, std::string_view bytes) {
if (bytes.size() > MAXDWORD) return false;
std::wstring temporary;
HANDLE file = INVALID_HANDLE_VALUE;
if (!reserve_sibling_temporary_file(path, temporary, file)) return false;
std::size_t position = 0;
bool ok = true;
while (position < bytes.size()) {
DWORD written = 0;
const DWORD requested = static_cast<DWORD>((std::min)(
bytes.size() - position,
static_cast<std::size_t>((std::numeric_limits<DWORD>::max)())));
if (!WriteFile(file, bytes.data() + position, requested, &written,
nullptr) || written == 0) {
ok = false;
break;
}
position += written;
}
if (ok) ok = FlushFileBuffers(file) != FALSE;
CloseHandle(file);
if (!ok) {
DeleteFileW(temporary.c_str());
return false;
}
return commit_sibling_temporary_file(temporary, path);
}
bool read_bytes(const std::wstring& path, std::string& bytes,
const std::size_t maximum_size = kMaxRtfBytes) {
if (!regular_file_within_limit(path, maximum_size)) return false;
HANDLE file = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
nullptr);
if (file == INVALID_HANDLE_VALUE) return false;
LARGE_INTEGER size{};
bool ok = GetFileSizeEx(file, &size) && size.QuadPart >= 0 &&
size.QuadPart <= 256 * 1024 * 1024;
size.QuadPart <= static_cast<LONGLONG>(maximum_size) &&
maximum_size <= MAXDWORD;
if (ok) {
bytes.resize(static_cast<std::size_t>(size.QuadPart));
DWORD read = 0;
@@ -1005,7 +1186,17 @@ bool read_bytes(const std::wstring& path, std::string& bytes) {
struct StreamCookie { const char* data; LONG length; LONG position; };
DWORD CALLBACK rich_edit_stream_in(DWORD_PTR cookie, LPBYTE buffer,
LONG requested, LONG* copied) {
if (cookie == 0 || buffer == nullptr || copied == nullptr ||
requested <= 0) {
if (copied != nullptr) *copied = 0;
return 1;
}
auto& source = *reinterpret_cast<StreamCookie*>(cookie);
if (source.data == nullptr || source.position < 0 ||
source.position > source.length) {
*copied = 0;
return 1;
}
*copied = (std::min)(requested, source.length - source.position);
if (*copied > 0) {
std::memcpy(buffer, source.data + source.position, *copied);
@@ -1017,7 +1208,10 @@ DWORD CALLBACK rich_edit_stream_in(DWORD_PTR cookie, LPBYTE buffer,
class RichEditDocument {
public:
bool load(std::string_view rtf) {
module_ = LoadLibraryW(L"Msftedit.dll");
if (rtf.size() > kMaxRtfBytes ||
rtf.size() > static_cast<std::size_t>(LONG_MAX)) return false;
module_ = LoadLibraryExW(L"Msftedit.dll", nullptr,
LOAD_LIBRARY_SEARCH_SYSTEM32);
if (module_ == nullptr) return false;
window_ = CreateWindowExW(0, MSFTEDIT_CLASS, L"", WS_POPUP | ES_MULTILINE,
0, 0, 100, 100, nullptr, nullptr,
@@ -1238,13 +1432,13 @@ bool write_docx(const std::wstring& path, const std::vector<Paragraph>& paragrap
L"/word/settings.xml",
L"http://schemas.openxmlformats.org/officeDocument/2006/relationships/settings"))
return false;
DeleteFileW(path.c_str());
return SUCCEEDED(factory->CreateStreamOnFile(path.c_str(), OPC_STREAM_IO_WRITE,
nullptr, FILE_ATTRIBUTE_NORMAL,
&output)) &&
SUCCEEDED(factory->WritePackageToStream(package.Get(),
if (FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &output)) ||
FAILED(factory->WritePackageToStream(package.Get(),
OPC_WRITE_DEFAULT,
output.Get()));
output.Get()))) return false;
std::string package_bytes;
return read_stream(output.Get(), package_bytes, kMaxGeneratedBytes) &&
write_bytes(path, package_bytes);
}
bool rtf_to_docx(const std::wstring& rtf_path, const std::wstring& docx_path) {
@@ -1620,11 +1814,18 @@ int export_paragraphs_to_pdf_dialog(
dialog.lpstrDefExt = L"pdf";
dialog.Flags = OFN_EXPLORER | OFN_ENABLESIZING | OFN_LONGNAMES |
OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;
const DWORD test_path_length = GetEnvironmentVariableW(
DWORD test_path_length = 0;
#ifdef OPUS_TEST_HOOKS
test_path_length = GetEnvironmentVariableW(
L"WORD1_TEST_PDF_PATH", path, static_cast<DWORD>(std::size(path)));
#endif
const bool accepted =
test_path_length > 0 && test_path_length < std::size(path) ?
#ifdef OPUS_TEST_HOOKS
(SetEnvironmentVariableW(L"WORD1_TEST_PDF_PATH", nullptr), true) :
#else
true :
#endif
GetSaveFileNameW(&dialog) != FALSE;
if (!accepted) {
return CommDlgExtendedError() == 0 ? -1 : false;
@@ -1652,14 +1853,16 @@ extern "C" int OpusModernPathIsDocx(const char* path) {
}
extern "C" int OpusModernDocxToRtfFile(const char* docx_path,
const char* rtf_path) {
const char* rtf_path) try {
std::vector<Paragraph> paragraphs;
return load_docx_paragraphs(docx_path, paragraphs) &&
write_bytes(wide_path(rtf_path), paragraphs_to_rtf(paragraphs));
} catch (...) {
return false;
}
extern "C" int OpusModernDocxToTextFile(const char* docx_path,
const char* text_path) {
const char* text_path) try {
std::vector<Paragraph> paragraphs;
std::vector<TableLayout> tables;
DocumentSettings settings;
@@ -1675,6 +1878,9 @@ extern "C" int OpusModernDocxToTextFile(const char* docx_path,
return false;
}
return true;
} catch (...) {
pending_docx_import = {};
return false;
}
extern "C" int OpusModernPendingDocxRunCount() {
@@ -1732,7 +1938,8 @@ extern "C" int OpusModernGetPendingDocxPage(
extern "C" int OpusModernGetPendingDocxRun(
const int index, long* cp_first, long* cp_lim, int* bold, int* italic,
int* underline, int* strike, int* small_caps, int* all_caps, int* hidden,
int* half_points, int* color_index, char* font, const int font_capacity) {
int* half_points, int* color_index, char* font,
const int font_capacity) try {
if (index < 0 || static_cast<std::size_t>(index) >=
pending_docx_import.runs.size()) return false;
const PendingRunFormat& record = pending_docx_import.runs[index];
@@ -1752,6 +1959,9 @@ extern "C" int OpusModernGetPendingDocxRun(
lstrcpynA(font, ansi_font.c_str(), font_capacity);
}
return true;
} catch (...) {
if (font != nullptr && font_capacity > 0) font[0] = '\0';
return false;
}
extern "C" int OpusModernGetPendingDocxParagraph(
@@ -1795,16 +2005,23 @@ extern "C" int OpusPdfSnapshotBegin(
const int margin_right, const int margin_top, const int margin_bottom) {
pending_pdf_export = {};
DocumentSettings& settings = pending_pdf_export.settings;
const bool dimensions_valid =
page_width >= 720 && page_width <= 63360 &&
page_height >= 720 && page_height <= 63360 &&
margin_left >= 0 && margin_left <= 31680 &&
margin_right >= 0 && margin_right <= 31680 &&
margin_top >= 0 && margin_top <= 31680 &&
margin_bottom >= 0 && margin_bottom <= 31680;
settings.page_width = page_width;
settings.page_height = page_height;
settings.margin_left = (std::max)(0, margin_left);
settings.margin_right = (std::max)(0, margin_right);
settings.margin_top = (std::max)(0, margin_top);
settings.margin_bottom = (std::max)(0, margin_bottom);
settings.valid = page_width > 0 && page_height > 0 &&
settings.margin_left = margin_left;
settings.margin_right = margin_right;
settings.margin_top = margin_top;
settings.margin_bottom = margin_bottom;
settings.valid = dimensions_valid &&
settings.margin_left + settings.margin_right < page_width &&
settings.margin_top + settings.margin_bottom < page_height;
return true;
return settings.valid;
}
extern "C" int OpusPdfSnapshotAddParagraph(
@@ -1812,7 +2029,18 @@ extern "C" int OpusPdfSnapshotAddParagraph(
const int first_line_indent, const int space_before,
const int space_after, const int line_spacing,
const int keep_together, const int keep_with_next,
const int page_break_before, const int bottom_border) {
const int page_break_before, const int bottom_border) try {
if (pending_pdf_export.paragraphs.size() >= kMaxParagraphs) return false;
/* These values come from the original PAP: indentation and line spacing
are signed 16-bit fields, while before/after spacing are unsigned.
Validate their real storage domains instead of rejecting legitimate
exact-line-spacing and legacy sentinel values. */
if (left_indent < INT16_MIN || left_indent > INT16_MAX ||
right_indent < INT16_MIN || right_indent > INT16_MAX ||
first_line_indent < INT16_MIN || first_line_indent > INT16_MAX ||
space_before < 0 || space_before > UINT16_MAX ||
space_after < 0 || space_after > UINT16_MAX ||
line_spacing < INT16_MIN || line_spacing > INT16_MAX) return false;
Paragraph paragraph;
paragraph.alignment = alignment == 1 ? PFA_CENTER :
alignment == 2 ? PFA_RIGHT :
@@ -1829,15 +2057,21 @@ extern "C" int OpusPdfSnapshotAddParagraph(
paragraph.bottom_border = bottom_border != 0;
pending_pdf_export.paragraphs.push_back(std::move(paragraph));
return true;
} catch (...) {
return false;
}
extern "C" int OpusPdfSnapshotAddRun(
const char* text, const int length, const char* font,
const int half_points, const int bold, const int italic,
const int underline, const int strike, const int small_caps,
const int all_caps, const int hidden, const int color_index) {
const int all_caps, const int hidden, const int color_index) try {
if (text == nullptr || length < 0 ||
pending_pdf_export.paragraphs.empty()) return false;
static_cast<std::size_t>(length) > kMaxTextBytes ||
pending_pdf_export.paragraphs.empty() ||
pending_pdf_export.run_count >= kMaxRuns ||
static_cast<std::size_t>(length) >
kMaxTextBytes - pending_pdf_export.text_bytes) return false;
RunStyle style;
style.bold = bold != 0;
style.italic = italic != 0;
@@ -1846,8 +2080,13 @@ extern "C" int OpusPdfSnapshotAddRun(
style.small_caps = small_caps != 0;
style.all_caps = all_caps != 0;
style.hidden = hidden != 0;
style.half_points = half_points >= 8 ? half_points : 20;
if (font != nullptr && *font != '\0') style.font = ansi_to_wide(font);
style.half_points = half_points >= 8 && half_points <= 254 ?
half_points : 20;
if (font != nullptr && *font != '\0') {
const std::size_t font_length = strnlen_s(font, 256);
if (font_length == 256) return false;
style.font = ansi_to_wide(std::string_view(font, font_length));
}
apply_legacy_color(color_index, style);
std::wstring run_text = ansi_to_wide(
std::string_view(text, static_cast<std::size_t>(length)));
@@ -1861,38 +2100,56 @@ extern "C" int OpusPdfSnapshotAddRun(
}
pending_pdf_export.paragraphs.back().runs.push_back(
{style, std::move(run_text)});
++pending_pdf_export.run_count;
pending_pdf_export.text_bytes += static_cast<std::size_t>(length);
return true;
} catch (...) {
return false;
}
extern "C" int OpusPdfSnapshotExportDialog(HWND owner) {
extern "C" int OpusPdfSnapshotExportDialog(HWND owner) try {
if (pending_pdf_export.paragraphs.empty()) return false;
const int result = export_paragraphs_to_pdf_dialog(
owner, pending_pdf_export.paragraphs, pending_pdf_export.settings);
pending_pdf_export = {};
return result;
} catch (...) {
pending_pdf_export = {};
return false;
}
extern "C" int OpusModernRtfFileToDocx(const char* rtf_path,
const char* docx_path) {
const char* docx_path) try {
return rtf_to_docx(wide_path(rtf_path), wide_path(docx_path));
} catch (...) {
return false;
}
extern "C" int OpusModernRtfFileToPdf(const char* rtf_path,
const char* pdf_path) {
const char* pdf_path) try {
std::string rtf;
return read_bytes(wide_path(rtf_path), rtf) &&
rtf_to_pdf(rtf, wide_path(pdf_path));
} catch (...) {
return false;
}
extern "C" int OpusExportRtfToPdfDialog(HWND owner, const char* rtf) {
return rtf == nullptr ? false :
export_rtf_to_pdf_dialog(owner, rtf);
extern "C" int OpusExportRtfToPdfDialog(HWND owner, const char* rtf) try {
if (rtf == nullptr) return false;
const std::size_t length = strnlen_s(rtf, kMaxRtfBytes + 1);
return length <= kMaxRtfBytes &&
export_rtf_to_pdf_dialog(owner, std::string_view(rtf, length));
} catch (...) {
return false;
}
extern "C" int OpusExportTextToPdfDialog(HWND owner, const char* text,
const int length) {
if (text == nullptr || length < 0) return false;
const int length) try {
if (text == nullptr || length < 0 ||
static_cast<std::size_t>(length) > kMaxTextBytes) return false;
return export_rtf_to_pdf_dialog(
owner, ansi_text_to_rtf(std::string_view(
text, static_cast<std::size_t>(length))));
} catch (...) {
return false;
}
+59 -2
View File
@@ -1,15 +1,35 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <climits>
#include <cstring>
#include <fstream>
#include <iostream>
#include <string>
extern "C" int OpusModernDocxToRtfFile(const char*, const char*);
extern "C" int OpusModernDocxToTextFile(const char*, const char*);
extern "C" int OpusModernRtfFileToDocx(const char*, const char*);
extern "C" int OpusModernRtfFileToPdf(const char*, const char*);
extern "C" int OpusPdfSnapshotBegin(int, int, int, int, int, int);
extern "C" int OpusPdfSnapshotAddParagraph(int, int, int, int, int, int,
int, int, int, int, int);
extern "C" int OpusPdfSnapshotAddRun(const char*, int, const char*, int,
int, int, int, int, int, int, int, int);
std::string read_file(const std::string& path) {
std::ifstream input(path, std::ios::binary);
return {std::istreambuf_iterator<char>(input), {}};
}
int main(const int argument_count, char** arguments) {
if (argument_count == 4 && std::strcmp(arguments[1], "--text") == 0) {
const bool converted =
OpusModernDocxToTextFile(arguments[2], arguments[3]) != 0;
std::cout << "DOCX text import "
<< (converted ? "passed" : "failed") << '\n';
return converted ? 0 : 4;
}
if (argument_count == 3) {
const bool converted =
OpusModernDocxToRtfFile(arguments[1], arguments[2]) != 0;
@@ -32,6 +52,8 @@ int main(const int argument_count, char** arguments) {
const std::string rtf = base + ".rtf";
const std::string docx = base + ".docx";
const std::string roundtrip = base + ".roundtrip.rtf";
const std::string oversized = base + ".oversized.rtf";
const std::string preserved = base + ".preserved.pdf";
char requested_pdf[32768]{};
const DWORD requested_pdf_length = GetEnvironmentVariableA(
"WORD1_TEST_KEEP_PDF", requested_pdf,
@@ -51,6 +73,35 @@ int main(const int argument_count, char** arguments) {
const bool read = written &&
OpusModernDocxToRtfFile(docx.c_str(), roundtrip.c_str()) != 0;
const bool pdf_written = OpusModernRtfFileToPdf(rtf.c_str(), pdf.c_str()) != 0;
{
std::ofstream output(preserved, std::ios::binary);
output << "ORIGINAL";
}
HANDLE oversized_file = CreateFileA(
oversized.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS,
FILE_ATTRIBUTE_TEMPORARY, nullptr);
bool oversized_created = oversized_file != INVALID_HANDLE_VALUE;
if (oversized_created) {
LARGE_INTEGER hostile_size{};
hostile_size.QuadPart = 64ll * 1024ll * 1024ll + 1;
oversized_created = SetFilePointerEx(oversized_file, hostile_size,
nullptr, FILE_BEGIN) &&
SetEndOfFile(oversized_file);
CloseHandle(oversized_file);
}
const bool oversized_rejected = oversized_created &&
OpusModernRtfFileToPdf(oversized.c_str(), preserved.c_str()) == 0 &&
read_file(preserved) == "ORIGINAL";
const bool invalid_snapshot_rejected =
OpusPdfSnapshotBegin(INT_MAX, INT_MAX, INT_MAX, INT_MAX,
INT_MAX, INT_MAX) == 0 &&
OpusPdfSnapshotBegin(12240, 15840, 1440, 1440, 1440, 1440) != 0 &&
OpusPdfSnapshotAddParagraph(0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0) != 0 &&
OpusPdfSnapshotAddParagraph(0, 0, 0, 0, 0, 0, -240,
0, 0, 0, 0) != 0 &&
OpusPdfSnapshotAddRun("x", INT_MAX, "Arial", 20,
0, 0, 0, 0, 0, 0, 0, 0) == 0;
std::string result;
if (read) {
std::ifstream input(roundtrip, std::ios::binary);
@@ -64,6 +115,8 @@ int main(const int argument_count, char** arguments) {
DeleteFileA(rtf.c_str());
DeleteFileA(docx.c_str());
DeleteFileA(roundtrip.c_str());
DeleteFileA(oversized.c_str());
DeleteFileA(preserved.c_str());
if (!keep_pdf) DeleteFileA(pdf.c_str());
if (!written || !read || result.find("Bold") == std::string::npos ||
result.find("Second paragraph") == std::string::npos ||
@@ -75,10 +128,14 @@ int main(const int argument_count, char** arguments) {
pdf_data.find("Second paragraph") == std::string::npos ||
pdf_data.find("/Helvetica-Bold") == std::string::npos ||
pdf_data.find("xref") == std::string::npos ||
pdf_data.find("%%EOF") == std::string::npos) {
pdf_data.find("%%EOF") == std::string::npos ||
!oversized_rejected || !invalid_snapshot_rejected) {
std::cerr << "DOCX round trip failed: write=" << written
<< " read=" << read << " pdf=" << pdf_written
<< " bytes=" << result.size() << '\n';
<< " bytes=" << result.size()
<< " oversizedRejected=" << oversized_rejected
<< " invalidSnapshotRejected="
<< invalid_snapshot_rejected << '\n';
return 2;
}
std::cout << "DOCX round trip passed (" << result.size() << " RTF bytes)\n";
@@ -374,28 +374,6 @@ LONG WINAPI ObserveVectoredException(EXCEPTION_POINTERS* exception) {
} // namespace
extern "C" void OpusX64TraceRibbon(const char* stage, int message, int tmc,
int first_value, int second_value,
long cp_first, long cp_limit,
int insertion) {
char trace_path[MAX_PATH] = {};
BuildDiagnosticPath("WORD1-ribbon.txt", trace_path, sizeof(trace_path));
HANDLE file = CreateFileA(trace_path, FILE_APPEND_DATA,
FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) {
return;
}
char line[384] = {};
std::snprintf(line, sizeof(line),
"%llu %s msg=%d tmc=%d a=%d b=%d sel=%ld,%ld ins=%d\r\n",
static_cast<unsigned long long>(GetTickCount64()),
stage != nullptr ? stage : "", message, tmc, first_value,
second_value, cp_first, cp_limit, insertion);
WriteCrashText(file, line);
CloseHandle(file);
}
int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous,
PWSTR command_line, int show_command) {
if ((command_line != nullptr &&
@@ -404,6 +382,15 @@ int WINAPI wWinMain(HINSTANCE instance, HINSTANCE previous,
return 0;
}
/* Exclude the current directory and PATH from DLL resolution. The app
directory remains available for intentionally deployed components and
system DLLs are resolved only from System32. */
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_APPLICATION_DIR |
LOAD_LIBRARY_SEARCH_SYSTEM32 |
LOAD_LIBRARY_SEARCH_USER_DIRS);
SetSearchPathMode(BASE_SEARCH_PATH_ENABLE_SAFE_SEARCHMODE |
BASE_SEARCH_PATH_PERMANENT);
SetUnhandledExceptionFilter(WriteCrashStack);
AddVectoredExceptionHandler(1, ObserveVectoredException);
_RTC_SetErrorFuncW(WriteRtcFailure);
+144 -28
View File
@@ -1,11 +1,13 @@
#include "opus_x64_compat.h"
#include "opus_x64_heap.h"
#include <commdlg.h>
#include <objbase.h>
extern "C" {
#include "dac.h"
}
#include <algorithm>
#include <atomic>
#include <cctype>
#include <cstdio>
#include <cstdint>
@@ -21,12 +23,18 @@ extern std::uintptr_t wRefDlgCur;
extern HWND vhWndMsgBoxParent;
void GetCabSz(void**, char*, std::uint16_t, std::uint16_t);
int FSetCabSz(void**, const char*, std::uint16_t);
void OpusX64TraceRibbon(const char*, int, int, int, int, long, long, int);
int OpusModernPathIsDocx(const char*);
int OpusModernDocxToTextFile(const char*, const char*);
int OpusModernRtfFileToDocx(const char*, const char*);
}
extern "C" void OpusX64TraceRibbon(const char*, int, int, int, int,
long, long, int) {
/* Ribbon tracing used during the bring-up of the x64 port. Keep the ABI
for original call sites without writing diagnostic files in product or
test processes. */
}
/*
* Flat, stateful implementation of the public SDM 2.21 dialog API used by
* Opus. The archive contains the SDM headers and 16-bit .obj files, but not
@@ -131,6 +139,7 @@ struct Win95SaveAlias {
Win95SaveAlias g_win95_save_alias;
std::unordered_map<std::string, std::string> g_win95_saved_aliases;
std::string g_win95_staging_directory;
struct Win95AliasCleanup {
~Win95AliasCleanup() {
@@ -141,6 +150,9 @@ struct Win95AliasCleanup {
for (const auto& saved : g_win95_saved_aliases) {
DeleteFileA(saved.first.c_str());
}
if (!g_win95_staging_directory.empty()) {
RemoveDirectoryA(g_win95_staging_directory.c_str());
}
}
};
@@ -154,43 +166,131 @@ std::string win95_alias_key(std::string path) {
return path;
}
bool safe_dialog_file_path(const std::string& path, const bool must_exist) {
if (path.empty() || path.size() >= 32760 ||
path.starts_with(R"(\\.\)") ||
path.starts_with(R"(\\?\GLOBALROOT\)")) return false;
for (std::size_t index = 0; index < path.size(); ++index) {
if (path[index] == ':' && index != 1) return false;
}
const DWORD attributes = GetFileAttributesA(path.c_str());
if (attributes != INVALID_FILE_ATTRIBUTES)
return (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
return !must_exist && (GetLastError() == ERROR_FILE_NOT_FOUND ||
GetLastError() == ERROR_PATH_NOT_FOUND);
}
bool import_file_within_limit(const std::string& path) {
if (!safe_dialog_file_path(path, true)) return false;
WIN32_FILE_ATTRIBUTE_DATA attributes{};
if (!GetFileAttributesExA(path.c_str(), GetFileExInfoStandard,
&attributes)) return false;
constexpr ULONGLONG kMaximumImportBytes = 256ull * 1024ull * 1024ull;
const ULONGLONG size =
(static_cast<ULONGLONG>(attributes.nFileSizeHigh) << 32) |
attributes.nFileSizeLow;
return size <= kMaximumImportBytes;
}
bool make_win95_staging_path(std::string& path,
const char* desired_extension = ".DOC") {
char module_path[32768]{};
const DWORD module_length = GetModuleFileNameA(
nullptr, module_path, static_cast<DWORD>(std::size(module_path)));
if (module_length == 0 || module_length >= std::size(module_path)) {
if (_stricmp(desired_extension, ".DOC") != 0 &&
_stricmp(desired_extension, ".TXT") != 0) return false;
if (g_win95_staging_directory.empty()) {
char temporary_root[32768]{};
const DWORD root_length = GetTempPathA(
static_cast<DWORD>(std::size(temporary_root)), temporary_root);
if (root_length == 0 || root_length >= std::size(temporary_root))
return false;
}
char* separator = std::strrchr(module_path, '\\');
if (separator == nullptr) {
return false;
}
*separator = '\0';
std::string directory = std::string(module_path) + "\\W95TEMP";
if (!CreateDirectoryA(directory.c_str(), nullptr) &&
GetLastError() != ERROR_ALREADY_EXISTS) {
return false;
}
for (int attempt = 0; attempt < 32; ++attempt) {
char temporary[MAX_PATH]{};
if (GetTempFileNameA(directory.c_str(), "W95", 0, temporary) == 0) {
GUID identifier{};
if (FAILED(CoCreateGuid(&identifier))) return false;
char leaf[9]{};
_snprintf_s(leaf, std::size(leaf), _TRUNCATE, "W%07lX",
identifier.Data1 & 0x0ffffffful);
const std::string candidate = std::string(temporary_root) + leaf;
/* The original normalizer requires DOS 8.3-safe path components
even though the native file APIs support long names. */
if (candidate.size() >= 52) return false;
if (CreateDirectoryA(candidate.c_str(), nullptr)) {
const DWORD attributes = GetFileAttributesA(candidate.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES ||
(attributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
RemoveDirectoryA(candidate.c_str());
return false;
}
DeleteFileA(temporary);
char* existing_extension = std::strrchr(temporary, '.');
if (existing_extension != nullptr) {
lstrcpyA(existing_extension, desired_extension);
g_win95_staging_directory = candidate;
break;
}
if (GetFileAttributesA(temporary) == INVALID_FILE_ATTRIBUTES &&
std::strlen(temporary) < 120) {
path = temporary;
if (GetLastError() != ERROR_ALREADY_EXISTS) return false;
}
if (g_win95_staging_directory.empty()) return false;
}
static std::atomic<unsigned long> sequence{0};
for (int attempt = 0; attempt < 32; ++attempt) {
char leaf[18]{};
_snprintf_s(leaf, std::size(leaf), _TRUNCATE, "\\D%07lX%s",
++sequence & 0x0ffffffful, desired_extension);
const std::string candidate = g_win95_staging_directory + leaf;
if (candidate.size() >= 120) return false;
const DWORD attributes = GetFileAttributesA(candidate.c_str());
const DWORD attribute_error = GetLastError();
if (attributes == INVALID_FILE_ATTRIBUTES &&
(attribute_error == ERROR_FILE_NOT_FOUND ||
attribute_error == ERROR_PATH_NOT_FOUND)) {
path = candidate;
return true;
}
}
return false;
}
bool atomic_copy_file(const std::string& source, const std::string& target) {
if (source.empty() || target.empty()) return false;
static std::atomic<unsigned long> sequence{0};
std::string temporary;
bool copied = false;
for (int attempt = 0; attempt < 64; ++attempt) {
temporary = target + ".word1tmp-" +
std::to_string(GetCurrentProcessId()) + "-" +
std::to_string(++sequence);
if (CopyFileA(source.c_str(), temporary.c_str(), TRUE)) {
copied = true;
break;
}
if (GetLastError() != ERROR_FILE_EXISTS &&
GetLastError() != ERROR_ALREADY_EXISTS) return false;
}
if (!copied) return false;
HANDLE file = CreateFileA(temporary.c_str(), GENERIC_WRITE,
FILE_SHARE_READ, nullptr, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, nullptr);
const bool flushed = file != INVALID_HANDLE_VALUE &&
FlushFileBuffers(file) != FALSE;
if (file != INVALID_HANDLE_VALUE) CloseHandle(file);
if (!flushed) {
DeleteFileA(temporary.c_str());
return false;
}
const DWORD attributes = GetFileAttributesA(target.c_str());
bool committed = false;
if (attributes != INVALID_FILE_ATTRIBUTES &&
(attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
committed = ReplaceFileA(target.c_str(), temporary.c_str(), nullptr,
REPLACEFILE_WRITE_THROUGH, nullptr, nullptr) != FALSE;
if (!committed) {
committed = MoveFileExA(temporary.c_str(), target.c_str(),
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != FALSE;
}
} else if (attributes == INVALID_FILE_ATTRIBUTES &&
GetLastError() == ERROR_FILE_NOT_FOUND) {
committed = MoveFileExA(temporary.c_str(), target.c_str(),
MOVEFILE_WRITE_THROUGH) != FALSE;
}
if (!committed) DeleteFileA(temporary.c_str());
return committed;
}
std::string counted_path(const unsigned char* st_file) {
if (st_file == nullptr || st_file[0] == 0 || st_file[0] >= 120) {
return {};
@@ -2187,9 +2287,13 @@ Tmc run_word95_common_file_dialog(DialogState& dialog) {
}
char test_path[32768]{};
#ifdef OPUS_TEST_HOOKS
const DWORD test_path_length = GetEnvironmentVariableA(
"WORD1_TEST_FILE_DIALOG_PATH", test_path,
static_cast<DWORD>(std::size(test_path)));
#else
const DWORD test_path_length = 0;
#endif
BOOL accepted = FALSE;
if (test_path_length > 0 && test_path_length < std::size(test_path)) {
lstrcpynA(file_buffer.data(), test_path,
@@ -2197,7 +2301,9 @@ Tmc run_word95_common_file_dialog(DialogState& dialog) {
if (saving && OpusModernPathIsDocx(test_path)) {
file_dialog.nFilterIndex = 2;
}
#ifdef OPUS_TEST_HOOKS
SetEnvironmentVariableA("WORD1_TEST_FILE_DIALOG_PATH", nullptr);
#endif
accepted = TRUE;
} else {
accepted = opening ? GetOpenFileNameA(&file_dialog) :
@@ -2232,6 +2338,16 @@ Tmc run_word95_common_file_dialog(DialogState& dialog) {
".docx");
}
}
if ((opening && !import_file_within_limit(selected_path)) ||
(saving && !safe_dialog_file_path(selected_path, false))) {
MessageBoxA(owner,
opening ?
"This document is not a regular file or is too large to open safely." :
"This is not a safe document file name.",
opening ? "Open" : "Save As",
MB_OK | MB_ICONEXCLAMATION);
continue;
}
std::string legacy_path;
const bool docx = OpusModernPathIsDocx(selected_path.c_str()) != 0;
if (!make_win95_staging_path(legacy_path,
@@ -2428,8 +2544,8 @@ int OpusFinishWin95SaveAlias(const unsigned char* st_file,
OpusModernRtfFileToDocx(
g_win95_save_alias.legacy_path.c_str(),
g_win95_save_alias.selected_path.c_str()) != 0 :
CopyFileA(g_win95_save_alias.legacy_path.c_str(),
g_win95_save_alias.selected_path.c_str(), FALSE) != 0;
atomic_copy_file(g_win95_save_alias.legacy_path,
g_win95_save_alias.selected_path);
}
if (!success) {
DeleteFileA(g_win95_save_alias.legacy_path.c_str());
@@ -2450,7 +2566,7 @@ int OpusFinishWin95SaveAlias(const unsigned char* st_file,
}
return OpusModernPathIsDocx(saved->second.c_str()) ?
OpusModernRtfFileToDocx(path.c_str(), saved->second.c_str()) != 0 :
CopyFileA(path.c_str(), saved->second.c_str(), FALSE) != 0;
atomic_copy_file(path, saved->second);
}
int OpusWin95SaveAliasRequiresRtf(const unsigned char* st_file) {
+4 -2
View File
@@ -227,7 +227,8 @@ int scale(HWND window, int value) {
void set_window_classic(HWND window) {
using SetWindowThemeProc = HRESULT(WINAPI*)(HWND, LPCWSTR, LPCWSTR);
static HMODULE theme_module = LoadLibraryW(L"uxtheme.dll");
static HMODULE theme_module = LoadLibraryExW(
L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
static const auto set_theme = theme_module != nullptr ?
reinterpret_cast<SetWindowThemeProc>(
GetProcAddress(theme_module, "SetWindowTheme")) : nullptr;
@@ -454,7 +455,8 @@ void configure_word95_menus(HWND window) {
void apply_caption_colors(HWND window) {
using DwmSetWindowAttributeProc = HRESULT(WINAPI*)(HWND, DWORD,
LPCVOID, DWORD);
HMODULE module = LoadLibraryW(L"dwmapi.dll");
HMODULE module = LoadLibraryExW(
L"dwmapi.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
if (module == nullptr) {
return;
}
+9 -3
View File
@@ -959,8 +959,10 @@ int wmain(const int argument_count, wchar_t** arguments) {
}
}
bool rich_pdf_valid = !docx_pdf_export_mode;
if (docx_pdf_export_mode &&
PostMessageW(main_window, kWmCommand, kExportPdf, 0)) {
DWORD_PTR export_result = 0;
if (docx_pdf_export_mode && SendMessageTimeoutW(
main_window, kWmCommand, kExportPdf, 0,
SMTO_ABORTIFHUNG | SMTO_BLOCK, 30000, &export_result)) {
const ULONGLONG pdf_deadline = GetTickCount64() + 15000;
do {
std::ifstream input(pdf_path, std::ios::binary);
@@ -1001,7 +1003,11 @@ int wmain(const int argument_count, wchar_t** arguments) {
<< "} bodyFont={index:" << body_font_index
<< ",charset:" << body_font_charset << "}\n";
if (docx_pdf_export_mode)
std::cerr << "richPdf=" << rich_pdf_valid << '\n';
std::cerr << "richPdf=" << rich_pdf_valid
<< " exportStage="
<< (pane != nullptr ? SendMessageW(
pane, kWmOpusX64QuerySelection, 105, 0) : -1)
<< '\n';
if (!keep_pdf && !pdf_path.empty()) DeleteFileA(pdf_path.c_str());
return 84;
}
+98 -8
View File
@@ -29,14 +29,18 @@ static_assert(offsetof(NativeHandle, data) == 0);
constexpr int kPrcTokenNil = 0x3fff;
constexpr int kPrcTokenMac = kPrcTokenNil - 1;
constexpr int kCompactHandleMac = 0x3fff;
constexpr std::size_t kNativeHandleSlots = 131071;
constexpr std::size_t kSegmentSlots = 65536;
constexpr std::size_t kGuardBytes = 64;
constexpr std::size_t kMaxAllocationBytes = 256u * 1024u * 1024u;
constexpr unsigned char kGuardValue = 0xa5;
SRWLOCK prc_registry_lock = SRWLOCK_INIT;
SRWLOCK compact_handle_lock = SRWLOCK_INIT;
SRWLOCK native_handle_lock = SRWLOCK_INIT;
SRWLOCK segment_registry_lock = SRWLOCK_INIT;
void** prc_handles[kPrcTokenNil]{};
void** compact_handles[kCompactHandleMac + 1]{};
void** native_handles[kNativeHandleSlots]{};
std::array<NativeSegment, kSegmentSlots> native_segments{};
std::atomic_size_t heap_bytes_used{0};
@@ -47,6 +51,7 @@ std::atomic_size_t heap_bytes_used{0};
[[nodiscard]] constexpr bool guarded_size(
const std::size_t logical_size, std::size_t* result) noexcept {
if (logical_size > kMaxAllocationBytes) return false;
const std::size_t payload = payload_size(logical_size);
if (payload > (std::numeric_limits<std::size_t>::max)() - kGuardBytes) {
return false;
@@ -55,6 +60,75 @@ std::atomic_size_t heap_bytes_used{0};
return true;
}
[[nodiscard]] bool valid_native_handle(void** opaque_handle) noexcept {
if (opaque_handle == nullptr) return false;
void** const tombstone = reinterpret_cast<void**>(static_cast<uintptr_t>(1));
const std::size_t start =
(reinterpret_cast<uintptr_t>(opaque_handle) >> 4) % kNativeHandleSlots;
AcquireSRWLockShared(&native_handle_lock);
bool valid = false;
for (std::size_t probe = 0; probe < kNativeHandleSlots; ++probe) {
void** const candidate =
native_handles[(start + probe) % kNativeHandleSlots];
if (candidate == opaque_handle) {
valid = true;
break;
}
if (candidate == nullptr) break;
if (candidate == tombstone) continue;
}
ReleaseSRWLockShared(&native_handle_lock);
return valid;
}
[[nodiscard]] bool register_native_handle(void** opaque_handle) noexcept {
void** const tombstone = reinterpret_cast<void**>(static_cast<uintptr_t>(1));
const std::size_t start =
(reinterpret_cast<uintptr_t>(opaque_handle) >> 4) % kNativeHandleSlots;
AcquireSRWLockExclusive(&native_handle_lock);
std::size_t first_tombstone = kNativeHandleSlots;
for (std::size_t probe = 0; probe < kNativeHandleSlots; ++probe) {
const std::size_t index = (start + probe) % kNativeHandleSlots;
if (native_handles[index] == opaque_handle) {
ReleaseSRWLockExclusive(&native_handle_lock);
return true;
}
if (native_handles[index] == tombstone &&
first_tombstone == kNativeHandleSlots) first_tombstone = index;
if (native_handles[index] == nullptr) {
native_handles[first_tombstone == kNativeHandleSlots ?
index : first_tombstone] = opaque_handle;
ReleaseSRWLockExclusive(&native_handle_lock);
return true;
}
}
if (first_tombstone != kNativeHandleSlots) {
native_handles[first_tombstone] = opaque_handle;
ReleaseSRWLockExclusive(&native_handle_lock);
return true;
}
ReleaseSRWLockExclusive(&native_handle_lock);
return false;
}
[[nodiscard]] bool unregister_native_handle(void** opaque_handle) noexcept {
void** const tombstone = reinterpret_cast<void**>(static_cast<uintptr_t>(1));
const std::size_t start =
(reinterpret_cast<uintptr_t>(opaque_handle) >> 4) % kNativeHandleSlots;
AcquireSRWLockExclusive(&native_handle_lock);
for (std::size_t probe = 0; probe < kNativeHandleSlots; ++probe) {
const std::size_t index = (start + probe) % kNativeHandleSlots;
if (native_handles[index] == opaque_handle) {
native_handles[index] = tombstone;
ReleaseSRWLockExclusive(&native_handle_lock);
return true;
}
if (native_handles[index] == nullptr) break;
}
ReleaseSRWLockExclusive(&native_handle_lock);
return false;
}
void set_guard(void* const allocation,
const std::size_t logical_size) noexcept {
std::memset(static_cast<unsigned char*>(allocation) +
@@ -126,9 +200,11 @@ extern "C" HP OpusHpOfSbIbImpl(const SB segment, const uintptr_t offset) {
return reinterpret_cast<HP>(static_cast<uintptr_t>(segment) + offset);
}
AcquireSRWLockShared(&segment_registry_lock);
void* base = native_segments[static_cast<std::size_t>(segment)].data;
const auto& slot = native_segments[static_cast<std::size_t>(segment)];
void* base = slot.data;
const std::size_t size = slot.size;
ReleaseSRWLockShared(&segment_registry_lock);
return base == nullptr
return base == nullptr || offset > size
? nullptr
: reinterpret_cast<HP>(reinterpret_cast<uintptr_t>(base) +
offset);
@@ -323,7 +399,7 @@ extern "C" int FAssureHcb(void*** handle_address,
const int byte_count_needed, int* byte_count,
int* byte_capacity) {
if (handle_address == nullptr || byte_count_needed < 0 ||
byte_capacity == nullptr) {
byte_capacity == nullptr || *byte_capacity < 0) {
return 0;
}
if (byte_count != nullptr) {
@@ -332,7 +408,10 @@ extern "C" int FAssureHcb(void*** handle_address,
if (*handle_address != nullptr && byte_count_needed <= *byte_capacity) {
return 1;
}
const int doubled = *byte_capacity > 0 ? *byte_capacity * 2 : 16;
const int doubled = *byte_capacity >
(std::numeric_limits<int>::max)() / 2 ?
(std::numeric_limits<int>::max)() :
(*byte_capacity > 0 ? *byte_capacity * 2 : 16);
const int new_capacity = (std::max)(byte_count_needed, doubled);
if (*handle_address == nullptr) {
*handle_address = OpusHAllocateCb(static_cast<std::size_t>(new_capacity));
@@ -397,6 +476,11 @@ extern "C" void** OpusHAllocateCb(const std::size_t byte_count) {
}
set_guard(handle->data, byte_count);
handle->size = byte_count;
if (!register_native_handle(reinterpret_cast<void**>(handle))) {
HeapFree(process_heap(), 0, handle->data);
HeapFree(process_heap(), 0, handle);
return nullptr;
}
heap_bytes_used.fetch_add(byte_count, std::memory_order_relaxed);
return reinterpret_cast<void**>(handle);
}
@@ -405,6 +489,9 @@ extern "C" void OpusFreeH(void** opaque_handle) {
if (opaque_handle == nullptr) {
return;
}
if (!unregister_native_handle(opaque_handle)) {
report_guard_failure();
}
unregister_prc_handle(opaque_handle);
unregister_compact_handle(opaque_handle);
auto* handle = reinterpret_cast<NativeHandle*>(opaque_handle);
@@ -433,6 +520,9 @@ extern "C" int OpusFChngSizeHCb(void** opaque_handle,
if (opaque_handle == nullptr) {
return 0;
}
if (!valid_native_handle(opaque_handle)) {
report_guard_failure();
}
auto* handle = reinterpret_cast<NativeHandle*>(opaque_handle);
if (!allow_shrink && byte_count <= handle->size) {
return 1;
@@ -470,7 +560,7 @@ extern "C" int OpusFChngSizePhqLcb(void*** handle_address,
}
extern "C" size_t OpusCbOfH(void** opaque_handle) {
return opaque_handle == nullptr
return !valid_native_handle(opaque_handle)
? 0
: reinterpret_cast<NativeHandle*>(opaque_handle)->size;
}
@@ -480,12 +570,13 @@ extern "C" size_t OpusHeapBytesUsed() {
}
extern "C" void* OpusDerefH(void** opaque_handle) {
return opaque_handle == nullptr
return !valid_native_handle(opaque_handle)
? nullptr
: reinterpret_cast<NativeHandle*>(opaque_handle)->data;
}
extern "C" void* OpusHpAlloc(const std::size_t byte_count) {
if (byte_count > kMaxAllocationBytes) return nullptr;
void* allocation =
HeapAlloc(process_heap(), 0, byte_count == 0 ? 1 : byte_count);
if (allocation != nullptr) {
@@ -500,9 +591,8 @@ extern "C" void* OpusHpAlloc(const std::size_t byte_count) {
extern "C" void OpusFreeHp(void* pointer) {
if (pointer != nullptr) {
const SIZE_T size = HeapSize(process_heap(), 0, pointer);
if (size != static_cast<SIZE_T>(-1)) {
if (size == static_cast<SIZE_T>(-1)) report_guard_failure();
heap_bytes_used.fetch_sub(size, std::memory_order_relaxed);
}
HeapFree(process_heap(), 0, pointer);
}
}
+2 -1
View File
@@ -325,7 +325,8 @@ int main() {
EndSdm();
return 23;
}
TestDltHeader modal_template{{8, 24, 206, 104}, 3, 0x8400,
/* Character is native-modal but is not routed to a common file dialog. */
TestDltHeader modal_template{{8, 24, 206, 104}, 16, 0x8400,
reinterpret_cast<void*>(ModalRuntimeProbe),
11, 4};
auto* modal_template_pointer = &modal_template;