mirror of
https://github.com/jmarshall23/msword.git
synced 2026-08-11 23:50:53 +02:00
First pass of security fixes
This commit is contained in:
+5
-4
@@ -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"
|
||||
|
||||
@@ -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 */
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
style.auto_color = false;
|
||||
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(),
|
||||
OPC_WRITE_DEFAULT,
|
||||
output.Get()));
|
||||
if (FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &output)) ||
|
||||
FAILED(factory->WritePackageToStream(package.Get(),
|
||||
OPC_WRITE_DEFAULT,
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)) {
|
||||
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) {
|
||||
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;
|
||||
for (int attempt = 0; attempt < 32; ++attempt) {
|
||||
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;
|
||||
}
|
||||
g_win95_staging_directory = candidate;
|
||||
break;
|
||||
}
|
||||
if (GetLastError() != ERROR_ALREADY_EXISTS) return false;
|
||||
}
|
||||
DeleteFileA(temporary);
|
||||
char* existing_extension = std::strrchr(temporary, '.');
|
||||
if (existing_extension != nullptr) {
|
||||
lstrcpyA(existing_extension, desired_extension);
|
||||
}
|
||||
if (GetFileAttributesA(temporary) == INVALID_FILE_ATTRIBUTES &&
|
||||
std::strlen(temporary) < 120) {
|
||||
path = temporary;
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
heap_bytes_used.fetch_sub(size, std::memory_order_relaxed);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user