@stll/folio-core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/apply.d.ts +2 -1
- package/dist/ai-edits/apply.js +693 -63
- package/dist/ai-edits/block-identity.d.ts +4 -0
- package/dist/ai-edits/block-identity.js +9 -0
- package/dist/ai-edits/blockRange.d.ts +28 -1
- package/dist/ai-edits/blockRange.js +68 -2
- package/dist/ai-edits/clean-text.js +2 -1
- package/dist/ai-edits/headless.d.ts +4 -4
- package/dist/ai-edits/headless.js +9 -9
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/read.d.ts +7 -7
- package/dist/ai-edits/read.js +110 -3
- package/dist/ai-edits/snapshot.js +21 -3
- package/dist/ai-edits/table-cell-mutations.d.ts +48 -0
- package/dist/ai-edits/table-cell-mutations.js +238 -0
- package/dist/ai-edits/table-mutation-plan.d.ts +38 -0
- package/dist/ai-edits/table-mutation-plan.js +90 -0
- package/dist/ai-edits/table-row-column-mutations.d.ts +108 -0
- package/dist/ai-edits/table-row-column-mutations.js +300 -0
- package/dist/ai-edits/table-targets.d.ts +32 -0
- package/dist/ai-edits/table-targets.js +70 -0
- package/dist/ai-edits/types.d.ts +62 -1
- package/dist/ai-edits/word-diff.js +21 -0
- package/dist/compat/eigenpal.d.ts +3 -3
- package/dist/compat/eigenpal.js +3 -3
- package/dist/content-controls/mutateContentControls.js +1 -1
- package/dist/controller/activeEditorStory.d.ts +36 -0
- package/dist/controller/activeEditorStory.js +30 -0
- package/dist/controller/contentControlWidgetController.d.ts +55 -0
- package/dist/controller/contentControlWidgetController.js +141 -0
- package/dist/controller/fontReadiness.js +2 -0
- package/dist/controller/headerFooterEditorManager.js +2 -2
- package/dist/controller/hiddenEditorApi.js +2 -2
- package/dist/controller/hiddenEditorManager.d.ts +12 -1
- package/dist/controller/hiddenEditorManager.js +44 -12
- package/dist/controller/layoutPipeline.d.ts +2 -0
- package/dist/controller/layoutPipeline.js +17 -2
- package/dist/controller/noteEditorManager.d.ts +36 -0
- package/dist/controller/noteEditorManager.js +210 -0
- package/dist/document-operations.d.ts +43 -11
- package/dist/document-operations.js +186 -18
- package/dist/docx/blockContentParser.js +21 -7
- package/dist/docx/commentParser.js +6 -1
- package/dist/docx/commentRangeIntegrity.d.ts +6 -0
- package/dist/docx/commentRangeIntegrity.js +186 -0
- package/dist/docx/conformance.js +62 -1
- package/dist/docx/documentGrid.d.ts +8 -0
- package/dist/docx/documentGrid.js +8 -0
- package/dist/docx/drawingUtils.js +1 -1
- package/dist/docx/encryption/agileDecryption.js +12 -2
- package/dist/docx/encryption/compoundFile.js +6 -1
- package/dist/docx/encryption/encryptionInfo.d.ts +9 -1
- package/dist/docx/encryption/encryptionInfo.js +18 -2
- package/dist/docx/encryption/openEncryptedDocx.js +1 -1
- package/dist/docx/ensureParaIds.js +8 -7
- package/dist/docx/fieldParser.js +1 -1
- package/dist/docx/groupDrawingParser.js +10 -5
- package/dist/docx/hyperlinkParser.js +1 -1
- package/dist/docx/imageParser.js +10 -6
- package/dist/docx/metadataPrivacy.js +17 -3
- package/dist/docx/notePropertiesParser.js +1 -1
- package/dist/docx/numberingParser.js +1 -1
- package/dist/docx/paragraphParser.js +70 -61
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +2 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +67 -13
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/parser.js +3 -3
- package/dist/docx/rezip.js +20 -9
- package/dist/docx/runParser.js +17 -6
- package/dist/docx/sdtProperties.js +1 -1
- package/dist/docx/sectionParser.js +18 -5
- package/dist/docx/selectiveSave.js +5 -1
- package/dist/docx/selectiveXmlPatch.d.ts +22 -1
- package/dist/docx/selectiveXmlPatch.js +61 -1
- package/dist/docx/serializer/blockSdtSerializer.js +3 -2
- package/dist/docx/serializer/commentSerializer.js +5 -6
- package/dist/docx/serializer/documentSerializer.js +2 -2
- package/dist/docx/serializer/headerFooterSerializer.js +2 -2
- package/dist/docx/serializer/noteSerializer.js +2 -2
- package/dist/docx/serializer/paragraphSerializer.js +23 -15
- package/dist/docx/serializer/runSerializer.js +25 -21
- package/dist/docx/serializer/sectionPropertiesSerializer.js +2 -1
- package/dist/docx/serializer/stylesSerializer.js +1 -1
- package/dist/docx/serializer/tableSerializer.d.ts +5 -4
- package/dist/docx/serializer/tableSerializer.js +26 -20
- package/dist/docx/serializer/xmlUtils.d.ts +13 -1
- package/dist/docx/serializer/xmlUtils.js +27 -1
- package/dist/docx/server/applyDocxXmlPatchProposal.d.ts +87 -0
- package/dist/docx/server/applyDocxXmlPatchProposal.js +204 -0
- package/dist/docx/server/boundedArchive.d.ts +14 -3
- package/dist/docx/server/boundedArchive.js +56 -11
- package/dist/docx/server/evaluateDocxXmlPatchProposal.d.ts +85 -0
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +361 -0
- package/dist/docx/server/extractDocxText.js +55 -8
- package/dist/docx/server/inspectDocxPackage.d.ts +70 -0
- package/dist/docx/server/inspectDocxPackage.js +217 -0
- package/dist/docx/server/validateDocxConformance.d.ts +41 -0
- package/dist/docx/server/validateDocxConformance.js +336 -0
- package/dist/docx/settingsParser.js +26 -3
- package/dist/docx/shapeParser.js +1 -1
- package/dist/docx/styleParser.js +4 -2
- package/dist/docx/tableParser.d.ts +8 -1
- package/dist/docx/tableParser.js +31 -10
- package/dist/docx/textBoxParser.d.ts +3 -3
- package/dist/docx/textBoxParser.js +16 -12
- package/dist/docx/unzip.js +1 -0
- package/dist/docx/vmlImageParser.js +11 -3
- package/dist/docx/xmlParser.js +11 -1
- package/dist/docx/xmlSafety.d.ts +5 -0
- package/dist/docx/xmlSafety.js +9 -0
- package/dist/fields/resolveFieldValues.js +1 -3
- package/dist/fonts/embeddedFonts.d.ts +33 -5
- package/dist/fonts/embeddedFonts.js +37 -8
- package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
- package/dist/i18n/messages/catalogs.gen.js +17 -0
- package/dist/i18n/messages/messages.gen.d.ts +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +47 -40
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +3 -0
- package/dist/layout-bridge/convert/templatePreviewFlow.js +3 -3
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +151 -20
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +6 -0
- package/dist/layout-bridge/dom/noteStoryDom.js +17 -0
- package/dist/layout-bridge/engine/clickToPosition.js +2 -2
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +58 -30
- package/dist/layout-engine/justificationCompatibility.d.ts +6 -0
- package/dist/layout-engine/justificationCompatibility.js +8 -0
- package/dist/layout-engine/keep-together.js +11 -1
- package/dist/layout-engine/measure/cache.js +23 -5
- package/dist/layout-engine/measure/effectiveLineBreakPolicy.js +8 -2
- package/dist/layout-engine/measure/lineBreakProvider.d.ts +2 -2
- package/dist/layout-engine/measure/lineBreakProvider.js +56 -37
- package/dist/layout-engine/measure/measureBlocks.js +15 -4
- package/dist/layout-engine/measure/measureContainer.js +1 -1
- package/dist/layout-engine/measure/measureHelpers.d.ts +11 -2
- package/dist/layout-engine/measure/measureHelpers.js +15 -4
- package/dist/layout-engine/measure/measureParagraph.js +85 -25
- package/dist/layout-engine/measure/measureProvider.js +4 -3
- package/dist/layout-engine/measure/tableCellFlow.js +5 -2
- package/dist/layout-engine/measure/textBoxParagraphLayout.d.ts +9 -7
- package/dist/layout-engine/measure/textBoxParagraphLayout.js +22 -6
- package/dist/layout-engine/measure/textMeasurementPolicy.d.ts +3 -1
- package/dist/layout-engine/measure/textMeasurementPolicy.js +7 -1
- package/dist/layout-engine/paginator.js +5 -1
- package/dist/layout-engine/paragraphSpacing.d.ts +28 -4
- package/dist/layout-engine/paragraphSpacing.js +107 -13
- package/dist/layout-engine/renderedBreakReconciliation.js +1 -1
- package/dist/layout-engine/types.d.ts +48 -15
- package/dist/layout-painter/registry/modules/textBox.js +5 -1
- package/dist/layout-painter/renderImage.d.ts +12 -1
- package/dist/layout-painter/renderImage.js +21 -4
- package/dist/layout-painter/renderPage.d.ts +5 -2
- package/dist/layout-painter/renderPage.js +59 -14
- package/dist/layout-painter/renderParagraph.js +55 -15
- package/dist/layout-painter/renderTable.d.ts +5 -1
- package/dist/layout-painter/renderTable.js +7 -3
- package/dist/layout-painter/renderTextBox.d.ts +2 -1
- package/dist/layout-painter/renderTextBox.js +25 -14
- package/dist/layout-painter/renderWatermark.d.ts +1 -1
- package/dist/layout-painter/renderWatermark.js +2 -1
- package/dist/managers/DocumentLoaderManager.js +1 -1
- package/dist/markdown/fromMarkdown.js +1 -1
- package/dist/model.js +1 -1
- package/dist/prosemirror/attrs/index.d.ts +4 -2
- package/dist/prosemirror/attrs/index.js +175 -18
- package/dist/prosemirror/commands/comments.d.ts +79 -4
- package/dist/prosemirror/commands/comments.js +622 -13
- package/dist/prosemirror/commands/contentControls.js +1 -1
- package/dist/prosemirror/commands/hyperlink.js +15 -1
- package/dist/prosemirror/commands/image.js +7 -1
- package/dist/prosemirror/commands/index.d.ts +2 -2
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commands/propertyChangeScope.d.ts +2 -2
- package/dist/prosemirror/commands/propertyChangeScope.js +3 -2
- package/dist/prosemirror/commands/tableCellMergeResolution.d.ts +9 -0
- package/dist/prosemirror/commands/tableCellMergeResolution.js +181 -0
- package/dist/prosemirror/commentIdAllocator.js +13 -3
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +404 -62
- package/dist/prosemirror/conversion/index.js +1 -1
- package/dist/prosemirror/conversion/sdtAttrs.d.ts +9 -0
- package/dist/prosemirror/conversion/sdtAttrs.js +49 -0
- package/dist/prosemirror/conversion/toProseDoc.d.ts +2 -1
- package/dist/prosemirror/conversion/toProseDoc.js +364 -103
- package/dist/prosemirror/extensions/ExtensionManager.js +1 -1
- package/dist/prosemirror/extensions/StarterKit.js +6 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +6 -2
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +1 -0
- package/dist/prosemirror/extensions/features/ImagePasteExtension.js +10 -2
- package/dist/prosemirror/extensions/features/ListExtension.js +1 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +10 -1
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +93 -2
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.d.ts +2 -1
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +6 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +15 -2
- package/dist/prosemirror/extensions/marks/CharacterSpacingExtension.js +1 -1
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +30 -4
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.d.ts +9 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +70 -11
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +1 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -0
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +22 -0
- package/dist/prosemirror/extensions/nodes/TableExtension.d.ts +1 -1
- package/dist/prosemirror/extensions/nodes/TableExtension.js +14 -6
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +13 -0
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +41 -0
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.d.ts +5 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +4 -1
- package/dist/prosemirror/index.d.ts +1 -1
- package/dist/prosemirror/index.js +1 -1
- package/dist/prosemirror/plugins/contentControlWidgets.d.ts +36 -10
- package/dist/prosemirror/plugins/contentControlWidgets.js +128 -94
- package/dist/prosemirror/plugins/revisionIds.d.ts +23 -0
- package/dist/prosemirror/plugins/revisionIds.js +57 -0
- package/dist/prosemirror/plugins/selectionTracker.js +1 -0
- package/dist/prosemirror/plugins/suggestionMode.js +5 -4
- package/dist/prosemirror/runFormattingMarkNames.d.ts +14 -0
- package/dist/prosemirror/runFormattingMarkNames.js +40 -0
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +30 -3
- package/dist/prosemirror/schema/marks.js +15 -0
- package/dist/prosemirror/schema/nodes.d.ts +111 -6
- package/dist/prosemirror/selectionState.js +1 -0
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +1 -0
- package/dist/prosemirror/tableCellMergeRevision.d.ts +9 -0
- package/dist/prosemirror/tableCellMergeRevision.js +22 -0
- package/dist/prosemirror/textBoxAnchorAttrs.d.ts +9 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +33 -0
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +1 -1
- package/dist/prosemirror/utils/extractTrackedChanges.js +62 -14
- package/dist/prosemirror/validation.js +9 -1
- package/dist/render-dom/RemoteSelectionOverlay.js +2 -2
- package/dist/server.d.ts +7 -3
- package/dist/server.js +7 -3
- package/dist/utils/clipboard.d.ts +10 -1
- package/dist/utils/clipboard.js +33 -6
- package/dist/utils/colorResolver.d.ts +8 -1
- package/dist/utils/colorResolver.js +35 -5
- package/dist/utils/fontFamilyMerge.js +9 -11
- package/dist/utils/fontLoader.js +3 -2
- package/dist/utils/fontResolver.d.ts +2 -1
- package/dist/utils/fontResolver.js +31 -7
- package/dist/utils/fontWeights.d.ts +1 -1
- package/dist/utils/fontWeights.js +1 -1
- package/dist/utils/hexId.d.ts +11 -1
- package/dist/utils/hexId.js +11 -1
- package/dist/utils/paragraphFormattingMerge.js +1 -0
- package/dist/utils/sanitizeImageSrc.d.ts +21 -0
- package/dist/utils/sanitizeImageSrc.js +30 -0
- package/dist/utils/stripXmlDeclarations.d.ts +11 -0
- package/dist/utils/stripXmlDeclarations.js +35 -0
- package/dist/version-comparison.d.ts +22 -2
- package/dist/version-comparison.js +41 -12
- package/dist/watermark/index.d.ts +11 -1
- package/dist/watermark/index.js +22 -1
- package/package.json +3 -2
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
//#region src/docx/commentRangeIntegrity.ts
|
|
2
|
+
const withoutOrphanCommentRanges = (doc) => {
|
|
3
|
+
const validCommentIds = new Set((doc.package.document.comments ?? []).map((comment) => comment.id));
|
|
4
|
+
const document = withoutOrphanDocumentBodyMarkers(doc.package.document, validCommentIds);
|
|
5
|
+
const headers = withoutOrphanHeaderFooterMapMarkers(doc.package.headers, validCommentIds);
|
|
6
|
+
const footers = withoutOrphanHeaderFooterMapMarkers(doc.package.footers, validCommentIds);
|
|
7
|
+
const footnotes = withoutOrphanNoteMarkers(doc.package.footnotes, validCommentIds);
|
|
8
|
+
const endnotes = withoutOrphanNoteMarkers(doc.package.endnotes, validCommentIds);
|
|
9
|
+
if (!document && !headers && !footers && !footnotes && !endnotes) return doc;
|
|
10
|
+
return {
|
|
11
|
+
...doc,
|
|
12
|
+
package: {
|
|
13
|
+
...doc.package,
|
|
14
|
+
...document ? { document } : {},
|
|
15
|
+
...headers ? { headers } : {},
|
|
16
|
+
...footers ? { footers } : {},
|
|
17
|
+
...footnotes ? { footnotes } : {},
|
|
18
|
+
...endnotes ? { endnotes } : {}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
const withoutOrphanDocumentBodyMarkers = (document, validCommentIds) => {
|
|
23
|
+
const content = withoutOrphanBlockMarkers(document.content, validCommentIds);
|
|
24
|
+
const comments = withoutOrphanCommentBodyMarkers(document.comments, validCommentIds);
|
|
25
|
+
if (!content && !comments) return null;
|
|
26
|
+
return {
|
|
27
|
+
...document,
|
|
28
|
+
...content ? { content } : {},
|
|
29
|
+
...comments ? { comments } : {}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const withoutOrphanCommentBodyMarkers = (comments, validCommentIds) => {
|
|
33
|
+
if (!comments) return null;
|
|
34
|
+
let changed = false;
|
|
35
|
+
const next = [];
|
|
36
|
+
for (const comment of comments) {
|
|
37
|
+
const content = withoutOrphanParagraphListMarkers(comment.content, validCommentIds);
|
|
38
|
+
if (!content) {
|
|
39
|
+
next.push(comment);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
changed = true;
|
|
43
|
+
next.push({
|
|
44
|
+
...comment,
|
|
45
|
+
content
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return changed ? next : null;
|
|
49
|
+
};
|
|
50
|
+
const withoutOrphanParagraphListMarkers = (paragraphs, validCommentIds) => {
|
|
51
|
+
let changed = false;
|
|
52
|
+
const next = [];
|
|
53
|
+
for (const paragraph of paragraphs) {
|
|
54
|
+
const nextParagraph = withoutOrphanParagraphMarkers(paragraph, validCommentIds);
|
|
55
|
+
if (nextParagraph !== paragraph) changed = true;
|
|
56
|
+
next.push(nextParagraph);
|
|
57
|
+
}
|
|
58
|
+
return changed ? next : null;
|
|
59
|
+
};
|
|
60
|
+
const withoutOrphanHeaderFooterMapMarkers = (map, validCommentIds) => {
|
|
61
|
+
if (!map) return null;
|
|
62
|
+
let changed = false;
|
|
63
|
+
const next = /* @__PURE__ */ new Map();
|
|
64
|
+
for (const [rId, headerFooter] of map) {
|
|
65
|
+
const content = withoutOrphanBlockMarkers(headerFooter.content, validCommentIds);
|
|
66
|
+
if (!content) {
|
|
67
|
+
next.set(rId, headerFooter);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
changed = true;
|
|
71
|
+
next.set(rId, {
|
|
72
|
+
...headerFooter,
|
|
73
|
+
content
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return changed ? next : null;
|
|
77
|
+
};
|
|
78
|
+
const withoutOrphanNoteMarkers = (notes, validCommentIds) => {
|
|
79
|
+
if (!notes) return null;
|
|
80
|
+
let changed = false;
|
|
81
|
+
const next = [];
|
|
82
|
+
for (const note of notes) {
|
|
83
|
+
const content = withoutOrphanBlockMarkers(note.content, validCommentIds);
|
|
84
|
+
if (!content) {
|
|
85
|
+
next.push(note);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
changed = true;
|
|
89
|
+
next.push({
|
|
90
|
+
...note,
|
|
91
|
+
content
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
return changed ? next : null;
|
|
95
|
+
};
|
|
96
|
+
const withoutOrphanBlockMarkers = (blocks, validCommentIds) => {
|
|
97
|
+
let changed = false;
|
|
98
|
+
const next = [];
|
|
99
|
+
for (const block of blocks) {
|
|
100
|
+
const nextBlock = withoutOrphanBlockMarker(block, validCommentIds);
|
|
101
|
+
if (nextBlock !== block) changed = true;
|
|
102
|
+
next.push(nextBlock);
|
|
103
|
+
}
|
|
104
|
+
return changed ? next : null;
|
|
105
|
+
};
|
|
106
|
+
const withoutOrphanBlockMarker = (block, validCommentIds) => {
|
|
107
|
+
if (block.type === "paragraph") return withoutOrphanParagraphMarkers(block, validCommentIds);
|
|
108
|
+
if (block.type === "table") return withoutOrphanTableMarkers(block, validCommentIds);
|
|
109
|
+
if (!("content" in block) || !block.content) return block;
|
|
110
|
+
const content = withoutOrphanBlockMarkers(block.content, validCommentIds);
|
|
111
|
+
if (!content) return block;
|
|
112
|
+
return {
|
|
113
|
+
...block,
|
|
114
|
+
content
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
const withoutOrphanTableMarkers = (table, validCommentIds) => {
|
|
118
|
+
let changed = false;
|
|
119
|
+
const rows = [];
|
|
120
|
+
for (const row of table.rows) {
|
|
121
|
+
const nextRow = withoutOrphanTableRowMarkers(row, validCommentIds);
|
|
122
|
+
if (nextRow !== row) changed = true;
|
|
123
|
+
rows.push(nextRow);
|
|
124
|
+
}
|
|
125
|
+
if (!changed) return table;
|
|
126
|
+
return {
|
|
127
|
+
...table,
|
|
128
|
+
rows
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
const withoutOrphanTableRowMarkers = (row, validCommentIds) => {
|
|
132
|
+
let changed = false;
|
|
133
|
+
const cells = [];
|
|
134
|
+
for (const cell of row.cells) {
|
|
135
|
+
const nextCell = withoutOrphanTableCellMarkers(cell, validCommentIds);
|
|
136
|
+
if (nextCell !== cell) changed = true;
|
|
137
|
+
cells.push(nextCell);
|
|
138
|
+
}
|
|
139
|
+
if (!changed) return row;
|
|
140
|
+
return {
|
|
141
|
+
...row,
|
|
142
|
+
cells
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
const withoutOrphanTableCellMarkers = (cell, validCommentIds) => {
|
|
146
|
+
const content = withoutOrphanTableCellBlockMarkers(cell.content, validCommentIds);
|
|
147
|
+
if (!content) return cell;
|
|
148
|
+
return {
|
|
149
|
+
...cell,
|
|
150
|
+
content
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
const withoutOrphanTableCellBlockMarkers = (blocks, validCommentIds) => {
|
|
154
|
+
let changed = false;
|
|
155
|
+
const next = [];
|
|
156
|
+
for (const block of blocks) {
|
|
157
|
+
const nextBlock = withoutOrphanTableCellBlockMarker(block, validCommentIds);
|
|
158
|
+
if (nextBlock !== block) changed = true;
|
|
159
|
+
next.push(nextBlock);
|
|
160
|
+
}
|
|
161
|
+
return changed ? next : null;
|
|
162
|
+
};
|
|
163
|
+
const withoutOrphanTableCellBlockMarker = (block, validCommentIds) => {
|
|
164
|
+
if (block.type === "paragraph") return withoutOrphanParagraphMarkers(block, validCommentIds);
|
|
165
|
+
if (block.type === "table") return withoutOrphanTableMarkers(block, validCommentIds);
|
|
166
|
+
return block;
|
|
167
|
+
};
|
|
168
|
+
const withoutOrphanParagraphMarkers = (paragraph, validCommentIds) => {
|
|
169
|
+
let changed = false;
|
|
170
|
+
const content = [];
|
|
171
|
+
for (const item of paragraph.content) {
|
|
172
|
+
if (isCommentMarker(item) && !validCommentIds.has(item.id)) {
|
|
173
|
+
changed = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
content.push(item);
|
|
177
|
+
}
|
|
178
|
+
if (!changed) return paragraph;
|
|
179
|
+
return {
|
|
180
|
+
...paragraph,
|
|
181
|
+
content
|
|
182
|
+
};
|
|
183
|
+
};
|
|
184
|
+
const isCommentMarker = (content) => content.type === "commentRangeStart" || content.type === "commentRangeEnd" || content.type === "commentReference";
|
|
185
|
+
//#endregion
|
|
186
|
+
export { withoutOrphanCommentRanges };
|
package/dist/docx/conformance.js
CHANGED
|
@@ -3,9 +3,70 @@ import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
|
3
3
|
//#region src/docx/conformance.ts
|
|
4
4
|
const STRICT_MAIN_NAMESPACE = "http://purl.oclc.org/ooxml/wordprocessingml/main";
|
|
5
5
|
const TRANSITIONAL_MAIN_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
|
|
6
|
+
const ROOT_START_TAG_SCAN_LIMIT = 64 * 1024;
|
|
7
|
+
const XML_WHITESPACE = /\s/u;
|
|
8
|
+
/**
|
|
9
|
+
* Find the root start tag within a bounded prefix of `document.xml`,
|
|
10
|
+
* without backtracking.
|
|
11
|
+
*
|
|
12
|
+
* Equivalent in captured-tag semantics to the previous
|
|
13
|
+
* `/^(?:\uFEFF|\s|<\?[\s\S]*?\?>|<!--[\s\S]*?-->)*(?<tag><[^\s/>]+(?:[^>"']|"[^"]*"|'[^']*')*>)/u`
|
|
14
|
+
* pattern, but as a linear hand-rolled scanner: the original alternation of
|
|
15
|
+
* greedy/lazy `[\s\S]*?` spans over an attacker-controlled prefix was
|
|
16
|
+
* susceptible to catastrophic backtracking on crafted input. This walks
|
|
17
|
+
* `scanTarget` once, tracking in-quote state, and returns `undefined` in
|
|
18
|
+
* every case the regex would have failed to match (unterminated PI/comment,
|
|
19
|
+
* no tag found before the scan limit, malformed tag open).
|
|
20
|
+
*/
|
|
21
|
+
function scanRootStartTag(scanTarget) {
|
|
22
|
+
const length = scanTarget.length;
|
|
23
|
+
let index = 0;
|
|
24
|
+
for (;;) {
|
|
25
|
+
if (index >= length) return;
|
|
26
|
+
const character = scanTarget[index];
|
|
27
|
+
if (character === "" || XML_WHITESPACE.test(character ?? "")) {
|
|
28
|
+
index += 1;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (scanTarget.startsWith("<?", index)) {
|
|
32
|
+
const closeIndex = scanTarget.indexOf("?>", index + 2);
|
|
33
|
+
if (closeIndex === -1) return;
|
|
34
|
+
index = closeIndex + 2;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (scanTarget.startsWith("<!--", index)) {
|
|
38
|
+
const closeIndex = scanTarget.indexOf("-->", index + 4);
|
|
39
|
+
if (closeIndex === -1) return;
|
|
40
|
+
index = closeIndex + 3;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
if (scanTarget[index] !== "<") return;
|
|
46
|
+
const nameStart = index + 1;
|
|
47
|
+
const firstNameChar = scanTarget[nameStart];
|
|
48
|
+
if (firstNameChar === void 0 || firstNameChar === "/" || firstNameChar === ">" || XML_WHITESPACE.test(firstNameChar)) return;
|
|
49
|
+
let quote;
|
|
50
|
+
for (let cursor = nameStart; cursor < length; cursor++) {
|
|
51
|
+
const character = scanTarget[cursor];
|
|
52
|
+
if (quote) {
|
|
53
|
+
if (character === quote) quote = void 0;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (character === "\"" || character === "'") {
|
|
57
|
+
quote = character;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (character === ">") return scanTarget.slice(index, cursor + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
6
63
|
const detectDocxConformanceClass = (documentXml) => {
|
|
7
64
|
if (documentXml === null) return DOCX_CONFORMANCE_CLASSES.UNKNOWN;
|
|
8
|
-
const
|
|
65
|
+
const rootStartTag = scanRootStartTag(documentXml.length <= ROOT_START_TAG_SCAN_LIMIT ? documentXml : documentXml.slice(0, ROOT_START_TAG_SCAN_LIMIT));
|
|
66
|
+
const root = (rootStartTag === void 0 ? null : parseXmlDocument(rootStartTag.endsWith("/>") ? rootStartTag : `${rootStartTag.slice(0, -1)}/>`)) ?? parseXmlDocument(documentXml);
|
|
67
|
+
return detectDocxConformanceClassFromRoot(root);
|
|
68
|
+
};
|
|
69
|
+
const detectDocxConformanceClassFromRoot = (root) => {
|
|
9
70
|
if (root === null || getLocalName(root.name ?? "") !== "document") return DOCX_CONFORMANCE_CLASSES.UNKNOWN;
|
|
10
71
|
const prefix = getNamespacePrefix(root.name ?? "");
|
|
11
72
|
const namespaceAttribute = prefix === null ? "xmlns" : `xmlns:${prefix}`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/docx/documentGrid.d.ts
|
|
2
|
+
type DocumentGrid = {
|
|
3
|
+
type?: "default" | "lines" | "linesAndChars" | "snapToChars";
|
|
4
|
+
linePitch?: number;
|
|
5
|
+
};
|
|
6
|
+
declare function resolveDocumentGridLinePitch(grid: DocumentGrid | undefined): number | undefined;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { resolveDocumentGridLinePitch };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/docx/documentGrid.ts
|
|
2
|
+
function resolveDocumentGridLinePitch(grid) {
|
|
3
|
+
if (grid?.type === void 0 || grid.type === "default") return;
|
|
4
|
+
if (grid.linePitch === void 0 || grid.linePitch <= 0) return;
|
|
5
|
+
return grid.linePitch;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { resolveDocumentGridLinePitch };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { findByFullName, getAttribute, getChildElements, getTextContent, parseNumericAttribute } from "./xmlParser.js";
|
|
2
1
|
import { ImageHorizontalAlignmentSchema, ImageHorizontalRelativeToSchema, ImageVerticalAlignmentSchema, ImageVerticalRelativeToSchema, ImageWrapTextSchema, ShapeOutlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
+
import { findByFullName, getAttribute, getChildElements, getTextContent, parseNumericAttribute } from "./xmlParser.js";
|
|
3
3
|
//#region src/docx/drawingUtils.ts
|
|
4
4
|
/**
|
|
5
5
|
* Map OOXML scheme names to standard theme color slots.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError } from "./errors.js";
|
|
2
2
|
import { bytesEqual, joinBytes, padToBlock, passwordToUtf16Le, toArrayBuffer, writeUint32Le } from "./cryptoBytes.js";
|
|
3
|
+
import { MAX_SPIN_COUNT } from "./encryptionInfo.js";
|
|
3
4
|
//#region src/docx/encryption/agileDecryption.ts
|
|
4
5
|
/**
|
|
5
6
|
* Agile Encryption decryption (Office 2010+) using Web Crypto.
|
|
@@ -115,11 +116,20 @@ const decryptAesCbc = async (rawKey, iv, ciphertext) => {
|
|
|
115
116
|
}, key, toArrayBuffer(paddedCiphertext));
|
|
116
117
|
return new Uint8Array(decrypted).subarray(0, ciphertext.length);
|
|
117
118
|
};
|
|
118
|
-
/**
|
|
119
|
+
/**
|
|
120
|
+
* [MS-OFFCRYPTO] 2.3.4.9 — iterated hash of password + salt.
|
|
121
|
+
*
|
|
122
|
+
* `iterations` is expected to already be validated by
|
|
123
|
+
* `encryptionInfo.ts`'s `spinCount` parsing, but this loop drives real
|
|
124
|
+
* WebCrypto work per iteration, so it is clamped again here as well —
|
|
125
|
+
* this function must stay safe to call with an untrusted iteration count
|
|
126
|
+
* regardless of caller.
|
|
127
|
+
*/
|
|
119
128
|
const hashPassword = async (password, salt, iterations, hashName) => {
|
|
120
129
|
const algorithm = webHashName(hashName);
|
|
130
|
+
const boundedIterations = Math.min(Math.max(iterations, 0), MAX_SPIN_COUNT);
|
|
121
131
|
let state = await digest(algorithm, joinBytes([salt, passwordToUtf16Le(password)]));
|
|
122
|
-
for (let i = 0; i <
|
|
132
|
+
for (let i = 0; i < boundedIterations; i++) state = await digest(algorithm, joinBytes([writeUint32Le(i), state]));
|
|
123
133
|
return state;
|
|
124
134
|
};
|
|
125
135
|
/** [MS-OFFCRYPTO] 2.3.4.9 — finalize a derived key with a block key suffix. */
|
|
@@ -83,9 +83,14 @@ const loadSector = (file, header, sectorId) => {
|
|
|
83
83
|
};
|
|
84
84
|
const loadFatSectorIds = (file, header) => {
|
|
85
85
|
const ids = [...header.headerDifat];
|
|
86
|
+
const maxDifatSectors = Math.ceil(file.length / header.sectorBytes);
|
|
87
|
+
const stepLimit = Math.min(header.difatSectorCount, maxDifatSectors);
|
|
88
|
+
const seen = /* @__PURE__ */ new Set();
|
|
86
89
|
let chain = header.difatStart;
|
|
87
|
-
for (let step = 0; step <
|
|
90
|
+
for (let step = 0; step < stepLimit; step++) {
|
|
88
91
|
if (chain === SECTOR_END || chain === SECTOR_FREE) break;
|
|
92
|
+
if (seen.has(chain)) throw new Error(`DIFAT chain loop at sector ${chain}`);
|
|
93
|
+
seen.add(chain);
|
|
89
94
|
const sector = loadSector(file, header, chain);
|
|
90
95
|
const view = viewOf(sector);
|
|
91
96
|
const slotsPerSector = header.sectorBytes / 4 - 1;
|
|
@@ -27,6 +27,14 @@ type AgileEncryptionDescriptor = {
|
|
|
27
27
|
scheme: "agile";
|
|
28
28
|
material: AgileKeyMaterial;
|
|
29
29
|
};
|
|
30
|
+
/**
|
|
31
|
+
* Cap on `encryptedKey/@spinCount`. MS-OFFCRYPTO's documented default is
|
|
32
|
+
* 100,000; a hostile/corrupt value here would drive `hashPassword`'s
|
|
33
|
+
* per-iteration WebCrypto digest loop (agileDecryption.ts) an
|
|
34
|
+
* attacker-chosen number of times, so reject anything past a generous
|
|
35
|
+
* multiple of that default instead of trusting the file.
|
|
36
|
+
*/
|
|
37
|
+
declare const MAX_SPIN_COUNT = 200000;
|
|
30
38
|
declare const parseAgileEncryptionInfo: (stream: Uint8Array) => AgileEncryptionDescriptor;
|
|
31
39
|
//#endregion
|
|
32
|
-
export { AgileEncryptionDescriptor, AgileKeyMaterial, parseAgileEncryptionInfo };
|
|
40
|
+
export { AgileEncryptionDescriptor, AgileKeyMaterial, MAX_SPIN_COUNT, parseAgileEncryptionInfo };
|
|
@@ -68,6 +68,22 @@ const requireInt = (attrs, name, element) => {
|
|
|
68
68
|
return parsed;
|
|
69
69
|
};
|
|
70
70
|
const requireBase64 = (attrs, name, element) => decodeBase64(requireAttr(attrs, name, element));
|
|
71
|
+
/**
|
|
72
|
+
* Cap on `encryptedKey/@spinCount`. MS-OFFCRYPTO's documented default is
|
|
73
|
+
* 100,000; a hostile/corrupt value here would drive `hashPassword`'s
|
|
74
|
+
* per-iteration WebCrypto digest loop (agileDecryption.ts) an
|
|
75
|
+
* attacker-chosen number of times, so reject anything past a generous
|
|
76
|
+
* multiple of that default instead of trusting the file.
|
|
77
|
+
*/
|
|
78
|
+
const MAX_SPIN_COUNT = 2e5;
|
|
79
|
+
const requireSpinCount = (attrs, element) => {
|
|
80
|
+
const spinCount = requireInt(attrs, "spinCount", element);
|
|
81
|
+
if (spinCount < 0 || spinCount > 2e5) throw new DocxEncryptionError({
|
|
82
|
+
code: DOCX_ENCRYPTION_ERROR_CODES.DECRYPTION_FAILED,
|
|
83
|
+
message: `EncryptionInfo <${element}> attribute "spinCount" out of range: ${spinCount}`
|
|
84
|
+
});
|
|
85
|
+
return spinCount;
|
|
86
|
+
};
|
|
71
87
|
const parseAgileXml = (xmlBytes) => {
|
|
72
88
|
const xml = new TextDecoder("utf-8").decode(xmlBytes);
|
|
73
89
|
const keyDataTag = findElement(xml, "keyData");
|
|
@@ -88,7 +104,7 @@ const parseAgileXml = (xmlBytes) => {
|
|
|
88
104
|
cipherBlockBytes: requireInt(keyData, "blockSize", "keyData"),
|
|
89
105
|
cipherName: requireAttr(keyData, "cipherAlgorithm", "keyData"),
|
|
90
106
|
cipherMode: requireAttr(keyData, "cipherChaining", "keyData"),
|
|
91
|
-
passwordIterations:
|
|
107
|
+
passwordIterations: requireSpinCount(encryptedKey, "encryptedKey"),
|
|
92
108
|
passwordSalt: requireBase64(encryptedKey, "saltValue", "encryptedKey"),
|
|
93
109
|
passwordHash: requireAttr(encryptedKey, "hashAlgorithm", "encryptedKey"),
|
|
94
110
|
passwordKeyBits: requireInt(encryptedKey, "keyBits", "encryptedKey"),
|
|
@@ -125,4 +141,4 @@ const parseAgileEncryptionInfo = (stream) => {
|
|
|
125
141
|
});
|
|
126
142
|
};
|
|
127
143
|
//#endregion
|
|
128
|
-
export { parseAgileEncryptionInfo };
|
|
144
|
+
export { MAX_SPIN_COUNT, parseAgileEncryptionInfo };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { DOCX_ENCRYPTION_ERROR_CODES, DocxEncryptionError } from "./errors.js";
|
|
2
2
|
import { toArrayBuffer } from "./cryptoBytes.js";
|
|
3
|
+
import { parseAgileEncryptionInfo } from "./encryptionInfo.js";
|
|
3
4
|
import { decryptAgileEncryptedPackage } from "./agileDecryption.js";
|
|
4
5
|
import { readEncryptedPackageStreams } from "./compoundFile.js";
|
|
5
6
|
import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "./containerFormat.js";
|
|
6
|
-
import { parseAgileEncryptionInfo } from "./encryptionInfo.js";
|
|
7
7
|
//#region src/docx/encryption/openEncryptedDocx.ts
|
|
8
8
|
/**
|
|
9
9
|
* Decrypt password-protected OOXML before ZIP extraction.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { isXmlNameBoundary } from "./selectiveXmlPatch.js";
|
|
2
1
|
import { deterministicHexId } from "../utils/hexId.js";
|
|
2
|
+
import { isXmlNameBoundary } from "./selectiveXmlPatch.js";
|
|
3
|
+
import { loadDocxArchive } from "./server/boundedArchive.js";
|
|
3
4
|
import { TaggedError } from "better-result";
|
|
4
5
|
import JSZip from "jszip";
|
|
5
6
|
//#region src/docx/ensureParaIds.ts
|
|
@@ -350,18 +351,17 @@ const hasDigitalSignatureParts = (zip) => Object.values(zip.files).some(({ dir,
|
|
|
350
351
|
* the buffer is not a WordprocessingML package or a part is malformed.
|
|
351
352
|
*/
|
|
352
353
|
const ensureParaIdsInternal = async (docx, options) => {
|
|
353
|
-
const
|
|
354
|
-
const xmlPartNames =
|
|
355
|
-
zip.forEach((relativePath, entry) => {
|
|
354
|
+
const archive = await loadDocxArchive(docx);
|
|
355
|
+
const xmlPartNames = archive.entries.filter((relativePath) => {
|
|
356
356
|
const lower = relativePath.toLowerCase();
|
|
357
|
-
|
|
357
|
+
return lower.startsWith("word/") && lower.endsWith(".xml");
|
|
358
358
|
});
|
|
359
359
|
const documentPartName = xmlPartNames.find((name) => name.toLowerCase() === DOCUMENT_PART);
|
|
360
360
|
if (documentPartName === void 0) throw createEnsureParaIdsError("word/document.xml not found: not a WordprocessingML package");
|
|
361
361
|
const partTexts = /* @__PURE__ */ new Map();
|
|
362
362
|
for (const name of xmlPartNames) {
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
363
|
+
const text = await archive.readEntryString(name);
|
|
364
|
+
if (text !== null) partTexts.set(name, text);
|
|
365
365
|
}
|
|
366
366
|
const taken = /* @__PURE__ */ new Set();
|
|
367
367
|
for (const text of partTexts.values()) collectExistingParaIds(text, taken);
|
|
@@ -390,6 +390,7 @@ const ensureParaIdsInternal = async (docx, options) => {
|
|
|
390
390
|
deduplicated: 0,
|
|
391
391
|
alreadyComplete: true
|
|
392
392
|
};
|
|
393
|
+
const zip = await JSZip.loadAsync(docx);
|
|
393
394
|
if (hasDigitalSignatureParts(zip) && options.allowSignedPackageMutation !== true) throw createEnsureParaIdsError("Refusing to normalize a digitally signed package because rewriting OOXML invalidates its signatures. Warn the user and pass allowSignedPackageMutation only if invalidation is acceptable.");
|
|
394
395
|
for (const [partPath, content] of updates) zip.file(partPath, content, {
|
|
395
396
|
compression: "DEFLATE",
|
package/dist/docx/fieldParser.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { findChildren, getAttribute } from "./xmlParser.js";
|
|
2
1
|
import { FieldTypeSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
+
import { findChildren, getAttribute } from "./xmlParser.js";
|
|
3
3
|
import { parseRun } from "./runParser.js";
|
|
4
4
|
//#region src/docx/fieldParser.ts
|
|
5
5
|
/**
|
|
@@ -150,12 +150,17 @@ const groupViewBox = (group, width, height) => {
|
|
|
150
150
|
};
|
|
151
151
|
};
|
|
152
152
|
const createSvg = (group, width, height, rels, media) => {
|
|
153
|
-
const
|
|
153
|
+
const children = getChildElements(group).slice(0, MAX_GROUP_SHAPES);
|
|
154
|
+
let content = "";
|
|
155
|
+
for (const [index, child] of children.entries()) {
|
|
154
156
|
const localName = getLocalName(child.name ?? "");
|
|
155
|
-
|
|
156
|
-
if (localName
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
let piece;
|
|
158
|
+
if (localName === "pic") piece = renderPicture(child, index, rels, media);
|
|
159
|
+
else if (localName === "wsp") piece = findChildByLocalName(child, "txbx") ? renderTextBox(child) : renderGeometry(child);
|
|
160
|
+
else continue;
|
|
161
|
+
content += piece;
|
|
162
|
+
if (content.length > MAX_SVG_CHARACTERS) return null;
|
|
163
|
+
}
|
|
159
164
|
if (!content) return null;
|
|
160
165
|
const viewBox = groupViewBox(group, width, height);
|
|
161
166
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}" width="${emuToPixels(width)}" height="${emuToPixels(height)}">${content}</svg>`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getAttribute, getChildElements, mergeXmlnsDeclarations, parseNumericAttribute } from "./xmlParser.js";
|
|
2
|
-
import { parseRun } from "./runParser.js";
|
|
3
2
|
import { sanitizeExternalUrl, sanitizeLinkTarget } from "../utils/urlSecurity.js";
|
|
3
|
+
import { parseRun } from "./runParser.js";
|
|
4
4
|
//#region src/docx/hyperlinkParser.ts
|
|
5
5
|
/**
|
|
6
6
|
* Get the local name of an element (without namespace prefix)
|
package/dist/docx/imageParser.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { findByFullName, findChild, getAttribute, getChildElements, parseNumericAttribute } from "./xmlParser.js";
|
|
2
2
|
import { emuToPixels } from "../utils/units.js";
|
|
3
|
+
import { sanitizeExternalUrl } from "../utils/urlSecurity.js";
|
|
4
|
+
import { sanitizeImageSrc } from "../utils/sanitizeImageSrc.js";
|
|
3
5
|
import { WRAP_ELEMENT_NAMES, parsePositionH, parsePositionV, parseWrapElement } from "./drawingUtils.js";
|
|
4
6
|
import { resolveTarget } from "./relsParser.js";
|
|
5
7
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
@@ -326,7 +328,8 @@ function parseInline(inlineEl, rels, media) {
|
|
|
326
328
|
if (props.alt) image.alt = props.alt;
|
|
327
329
|
if (props.title) image.title = props.title;
|
|
328
330
|
if (props.decorative) image.decorative = true;
|
|
329
|
-
|
|
331
|
+
const safeSrc = sanitizeImageSrc(imageData.src);
|
|
332
|
+
if (safeSrc) image.src = safeSrc;
|
|
330
333
|
if (imageData.mimeType) image.mimeType = imageData.mimeType;
|
|
331
334
|
if (imageData.filename) image.filename = imageData.filename;
|
|
332
335
|
if (padding) image.padding = padding;
|
|
@@ -334,8 +337,8 @@ function parseInline(inlineEl, rels, media) {
|
|
|
334
337
|
if (crop) image.crop = crop;
|
|
335
338
|
if (opacity !== void 0) image.opacity = opacity;
|
|
336
339
|
if (props.hlinkRId && rels) {
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
340
|
+
const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
|
|
341
|
+
if (safeHref) image.hlinkHref = safeHref;
|
|
339
342
|
}
|
|
340
343
|
return image;
|
|
341
344
|
}
|
|
@@ -391,7 +394,8 @@ function parseAnchor(anchorEl, rels, media) {
|
|
|
391
394
|
if (props.alt) image.alt = props.alt;
|
|
392
395
|
if (props.title) image.title = props.title;
|
|
393
396
|
if (props.decorative) image.decorative = true;
|
|
394
|
-
|
|
397
|
+
const safeSrc = sanitizeImageSrc(imageData.src);
|
|
398
|
+
if (safeSrc) image.src = safeSrc;
|
|
395
399
|
if (imageData.mimeType) image.mimeType = imageData.mimeType;
|
|
396
400
|
if (imageData.filename) image.filename = imageData.filename;
|
|
397
401
|
if (position) image.position = position;
|
|
@@ -402,8 +406,8 @@ function parseAnchor(anchorEl, rels, media) {
|
|
|
402
406
|
if (layoutInCell !== void 0) image.layoutInCell = layoutInCell;
|
|
403
407
|
if (allowOverlap !== void 0) image.allowOverlap = allowOverlap;
|
|
404
408
|
if (props.hlinkRId && rels) {
|
|
405
|
-
const
|
|
406
|
-
if (
|
|
409
|
+
const safeHref = sanitizeExternalUrl(resolveTarget(rels, props.hlinkRId));
|
|
410
|
+
if (safeHref) image.hlinkHref = safeHref;
|
|
407
411
|
}
|
|
408
412
|
return image;
|
|
409
413
|
}
|
|
@@ -44,6 +44,20 @@ const XML_DECLARATION_PATTERN = /^\s*<\?xml[^?]*\?>/u;
|
|
|
44
44
|
const MAX_INPUT_BYTES = 50 * 1024 * 1024;
|
|
45
45
|
const MAX_ARCHIVE_ENTRIES = 5e3;
|
|
46
46
|
const MAX_CORE_PROPERTIES_BYTES = 1024 * 1024;
|
|
47
|
+
const CORE_PROPERTIES_PATH_LOWER = "docprops/core.xml";
|
|
48
|
+
/**
|
|
49
|
+
* Look up a package part by its conventional lowercase path, falling back to
|
|
50
|
+
* a case-insensitive scan. OPC part names are case-insensitive, so a
|
|
51
|
+
* producer that wrote `docProps/Core.xml` (or another casing) must still be
|
|
52
|
+
* found — otherwise the privacy rewrite silently no-ops and the unscrubbed
|
|
53
|
+
* buffer round-trips untouched.
|
|
54
|
+
*/
|
|
55
|
+
const findZipEntryCaseInsensitive = (zip, lowerPath) => {
|
|
56
|
+
const direct = zip.file(lowerPath);
|
|
57
|
+
if (direct) return direct;
|
|
58
|
+
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && path.toLowerCase() === lowerPath) return file;
|
|
59
|
+
return null;
|
|
60
|
+
};
|
|
47
61
|
const loadPrivacyArchive = async (buffer) => {
|
|
48
62
|
if (buffer.byteLength > MAX_INPUT_BYTES) throw new FolioDocumentPrivacyArchiveError({
|
|
49
63
|
message: "Document privacy input exceeded the compressed-size limit",
|
|
@@ -63,7 +77,7 @@ const loadPrivacyArchive = async (buffer) => {
|
|
|
63
77
|
message: "Document privacy input exceeded the package-entry limit",
|
|
64
78
|
reason: "too-many-entries"
|
|
65
79
|
});
|
|
66
|
-
const coreProperties = zip
|
|
80
|
+
const coreProperties = findZipEntryCaseInsensitive(zip, CORE_PROPERTIES_PATH_LOWER);
|
|
67
81
|
if (!coreProperties) return zip;
|
|
68
82
|
const data = "_data" in coreProperties ? coreProperties._data : void 0;
|
|
69
83
|
const declaredBytes = typeof data === "object" && data !== null && "uncompressedSize" in data ? data.uncompressedSize : void 0;
|
|
@@ -99,7 +113,7 @@ const rewriteCorePropertiesPrivacy = (xml, transforms) => {
|
|
|
99
113
|
const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
|
|
100
114
|
const appliedTransforms = resolveFolioDocumentPrivacyTransforms(transforms);
|
|
101
115
|
const zip = await loadPrivacyArchive(buffer);
|
|
102
|
-
const coreProperties = zip
|
|
116
|
+
const coreProperties = findZipEntryCaseInsensitive(zip, CORE_PROPERTIES_PATH_LOWER);
|
|
103
117
|
if (!coreProperties) return {
|
|
104
118
|
buffer,
|
|
105
119
|
privacyReport: {
|
|
@@ -115,7 +129,7 @@ const rewriteDocxMetadataPrivacy = async (buffer, { transforms }) => {
|
|
|
115
129
|
removedMetadataProperties: []
|
|
116
130
|
}
|
|
117
131
|
};
|
|
118
|
-
zip.file(
|
|
132
|
+
zip.file(coreProperties.name, rewritten.xml);
|
|
119
133
|
return {
|
|
120
134
|
buffer: await zip.generateAsync({
|
|
121
135
|
type: "arraybuffer",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { findChild, getAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
2
1
|
import { NumberFormatSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
+
import { findChild, getAttribute, parseNumericAttribute } from "./xmlParser.js";
|
|
3
3
|
//#region src/docx/notePropertiesParser.ts
|
|
4
4
|
function parseNumberFormat(numFmtAttr) {
|
|
5
5
|
return narrowEnum(numFmtAttr, NumberFormatSchema);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { findChild, findChildren, getAttribute, parseBooleanElement, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
|
|
2
1
|
import { LevelSuffixSchema, ThemeColorSlotSchema, narrowEnum } from "./parserEnums.js";
|
|
2
|
+
import { findChild, findChildren, getAttribute, parseBooleanElement, parseNumericAttribute, parseXmlDocument } from "./xmlParser.js";
|
|
3
3
|
//#region src/docx/numberingParser.ts
|
|
4
4
|
const NUMBER_FORMAT_MAP = {
|
|
5
5
|
decimal: "decimal",
|