@stll/folio-core 0.64.1 → 0.66.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.js +31 -9
- package/dist/ai-edits/headless.js +3 -3
- package/dist/compare/reproducible-package.d.ts +5 -0
- package/dist/compare/reproducible-package.js +10 -23
- package/dist/docx/archiveInflation.d.ts +8 -103
- package/dist/docx/archiveInflation.js +8 -145
- package/dist/docx/attributeRemainder.js +1 -1
- package/dist/docx/canonicalStoryRepack.js +2 -2
- package/dist/docx/documentSectionFacts.js +1 -1
- package/dist/docx/ensureParaIds.js +9 -24
- package/dist/docx/importedIdCollisions.js +1 -1
- package/dist/docx/mathToMathml.js +1 -1
- package/dist/docx/metadataPrivacy.js +38 -48
- package/dist/docx/numericIdNormalization.js +2 -2
- package/dist/docx/packageParts.d.ts +4 -4
- package/dist/docx/packageParts.js +17 -24
- package/dist/docx/parser.js +5 -6
- package/dist/docx/removeHeaderFooterParts.d.ts +2 -2
- package/dist/docx/removeHeaderFooterParts.js +19 -36
- package/dist/docx/revisionIdNormalization.js +2 -2
- package/dist/docx/rezip.d.ts +7 -7
- package/dist/docx/rezip.js +144 -532
- package/dist/docx/sdtProperties.js +1 -2
- package/dist/docx/selectedXmlIdentityScan.d.ts +1 -1
- package/dist/docx/selectedXmlIdentityScan.js +1 -1
- package/dist/docx/selectiveSave.js +37 -41
- package/dist/docx/selectiveSaveFlags.d.ts +1 -1
- package/dist/docx/selectiveSaveTripwire.js +5 -11
- package/dist/docx/selectiveXmlPatch.js +2 -2
- package/dist/docx/server/applyDocxXmlPatchProposal.js +20 -41
- package/dist/docx/server/boundedArchive.d.ts +2 -3
- package/dist/docx/server/boundedArchive.js +78 -99
- package/dist/docx/server/inspectDocxPackage.d.ts +1 -1
- package/dist/docx/server/inspectDocxPackage.js +1 -1
- package/dist/docx/server/validateDocxConformance.js +1 -1
- package/dist/docx/shapeAlternateContent.js +1 -1
- package/dist/docx/storyBlockReplay.js +1 -1
- package/dist/docx/unzip.d.ts +2 -3
- package/dist/docx/unzip.js +127 -155
- package/dist/docx/verbatimCapture.js +1 -1
- package/dist/docx/xmlParser.d.ts +5 -36
- package/dist/docx/xmlParser.js +14 -78
- package/dist/docx/xmlSafety.js +5 -5
- package/dist/layout-painter/renderPage.js +4 -2
- package/dist/markdown/countParagraphList.d.ts +18 -0
- package/dist/markdown/countParagraphList.js +90 -0
- package/dist/markdown/renderParagraph.js +2 -62
- package/dist/markdown/renderRuns.js +4 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +5 -3
- package/dist/prosemirror/conversion/toProseDoc.js +4 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +6 -0
- package/dist/prosemirror/replacedAnnotations.js +30 -8
- package/dist/prosemirror/schema/nodes.d.ts +7 -0
- package/dist/prosemirror/textBoxAnchorAttrs.js +9 -1
- package/package.json +3 -5
- package/dist/docx/streamingXmlParser.d.ts +0 -60
- package/dist/docx/streamingXmlParser.js +0 -406
- package/dist/docx/xmlNamespaceContext.d.ts +0 -6
- package/dist/docx/xmlNamespaceContext.js +0 -53
- package/dist/docx/xmlResourceLimits.d.ts +0 -107
- package/dist/docx/xmlResourceLimits.js +0 -194
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { decodeText } from "../../utils/textEncoding.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
2
|
+
import { DOCX_COMPRESSION_RATIO_GRACE_BYTES, isRatioBoundedPart } from "../archiveInflation.js";
|
|
3
|
+
import { Result, TaggedError } from "better-result";
|
|
4
|
+
import { FOLIO_XML_RESOURCE_LIMITS, assertXmlResourceLimits, createXmlPackageBudget } from "@stll/docx-core/xml/limits";
|
|
5
|
+
import { ZipArchive, ZipError } from "@stll/docx-core/zip";
|
|
6
6
|
// src/docx/server/boundedArchive.ts
|
|
7
7
|
const DOCX_MAX_ENTRY_BYTES = 134217728;
|
|
8
8
|
const DOCX_MAX_TOTAL_BYTES = 268435456;
|
|
@@ -11,27 +11,42 @@ const DOCX_MAX_INPUT_BYTES = 52428800;
|
|
|
11
11
|
const DocxArchiveErrorBase = TaggedError("DocxArchiveError");
|
|
12
12
|
/** Error raised when a DOCX archive cannot be loaded within configured limits. */
|
|
13
13
|
var DocxArchiveError = class extends DocxArchiveErrorBase {};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
/** The archive error for a read the ZIP reader refused. */
|
|
15
|
+
const archiveErrorFor = (error, { maxEntryBytes, maxTotalBytes, maxEntries, maxCompressionRatio }) => {
|
|
16
|
+
const entry = error.path === void 0 ? "" : ` "${error.path}"`;
|
|
17
|
+
switch (error.reason) {
|
|
18
|
+
case "too-many-entries": return new DocxArchiveError({
|
|
19
|
+
message: `DOCX archive holds more than ${maxEntries} entries`,
|
|
20
|
+
reason: "too-many-entries"
|
|
19
21
|
});
|
|
20
|
-
case "
|
|
21
|
-
message: `DOCX entry
|
|
22
|
-
reason: "
|
|
22
|
+
case "entry-too-large": return new DocxArchiveError({
|
|
23
|
+
message: `DOCX entry${entry} declares more than ${maxEntryBytes} bytes`,
|
|
24
|
+
reason: "entry-too-large"
|
|
23
25
|
});
|
|
24
|
-
case "
|
|
25
|
-
message: `DOCX entry
|
|
26
|
+
case "declared-size-exceeded": return new DocxArchiveError({
|
|
27
|
+
message: `DOCX entry${entry} inflated past its declared size`,
|
|
26
28
|
reason: "entry-too-large"
|
|
27
29
|
});
|
|
28
|
-
case "total":
|
|
29
|
-
|
|
30
|
-
message: `DOCX archive exceeded the ${maxTotalBytes}-byte cumulative limit while reading "${path}"`,
|
|
30
|
+
case "total-too-large": return new DocxArchiveError({
|
|
31
|
+
message: `DOCX archive declares more than ${maxTotalBytes} cumulative bytes`,
|
|
31
32
|
reason: "total-too-large"
|
|
32
33
|
});
|
|
34
|
+
case "compression-ratio": return new DocxArchiveError({
|
|
35
|
+
message: error.path === void 0 ? `DOCX archive declares more than ${maxCompressionRatio} bytes per archive byte` : `DOCX entry${entry} declares more than ${maxCompressionRatio} bytes per compressed byte`,
|
|
36
|
+
reason: "compression-ratio-exceeded"
|
|
37
|
+
});
|
|
38
|
+
case "malformed":
|
|
39
|
+
case "unsupported":
|
|
40
|
+
case "invalid-entry-name":
|
|
41
|
+
case "duplicate-entry":
|
|
42
|
+
case "corrupt-entry": return new DocxArchiveError({
|
|
43
|
+
message: "Failed to parse DOCX archive",
|
|
44
|
+
reason: "load-failed",
|
|
45
|
+
cause: error
|
|
46
|
+
});
|
|
33
47
|
}
|
|
34
48
|
};
|
|
49
|
+
const toArchiveError = (cause, limits) => cause instanceof ZipError ? archiveErrorFor(cause, limits) : cause;
|
|
35
50
|
const resolveByteLimit = ({ value, fallback, name }) => {
|
|
36
51
|
const limit = value ?? fallback;
|
|
37
52
|
if (!Number.isSafeInteger(limit) || limit < 0) throw new DocxArchiveError({
|
|
@@ -40,7 +55,6 @@ const resolveByteLimit = ({ value, fallback, name }) => {
|
|
|
40
55
|
});
|
|
41
56
|
return limit;
|
|
42
57
|
};
|
|
43
|
-
const asBytes = (bytes) => bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
44
58
|
const loadDocxArchive = async (bytes, options = {}) => {
|
|
45
59
|
const maxInputBytes = resolveByteLimit({
|
|
46
60
|
value: options.maxInputBytes,
|
|
@@ -71,103 +85,68 @@ const loadDocxArchive = async (bytes, options = {}) => {
|
|
|
71
85
|
message: `DOCX input contains ${bytes.byteLength} bytes (max ${maxInputBytes})`,
|
|
72
86
|
reason: "input-too-large"
|
|
73
87
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
93
|
-
let declaredTotalBytes = 0;
|
|
94
|
-
let declaredTotalKnown = true;
|
|
95
|
-
for (const entry of archiveEntries) {
|
|
96
|
-
if (entry.dir) continue;
|
|
97
|
-
const { compressedBytes, uncompressedBytes } = getZipEntrySizes(entry);
|
|
98
|
-
if (uncompressedBytes === null) {
|
|
99
|
-
declaredTotalKnown = false;
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (uncompressedBytes > maxEntryBytes) throw new DocxArchiveError({
|
|
103
|
-
message: `DOCX entry "${entry.name}" declares ${uncompressedBytes} bytes (max ${maxEntryBytes})`,
|
|
104
|
-
reason: "entry-too-large"
|
|
105
|
-
});
|
|
106
|
-
if (exceedsCompressionRatio({
|
|
107
|
-
inflatedBytes: uncompressedBytes,
|
|
108
|
-
compressedBytes,
|
|
109
|
-
maxRatio: compressionRatioLimitFor(entry.name, maxCompressionRatio)
|
|
110
|
-
})) throw new DocxArchiveError({
|
|
111
|
-
message: `DOCX entry "${entry.name}" declares more than ${maxCompressionRatio} bytes per compressed byte`,
|
|
112
|
-
reason: "compression-ratio-exceeded"
|
|
113
|
-
});
|
|
114
|
-
declaredTotalBytes += uncompressedBytes;
|
|
115
|
-
}
|
|
116
|
-
if (declaredTotalKnown && declaredTotalBytes > maxTotalBytes) throw new DocxArchiveError({
|
|
117
|
-
message: `DOCX archive declares ${declaredTotalBytes} cumulative bytes (max ${maxTotalBytes})`,
|
|
118
|
-
reason: "total-too-large"
|
|
119
|
-
});
|
|
120
|
-
if (exceedsCompressionRatio({
|
|
121
|
-
inflatedBytes: declaredTotalBytes,
|
|
122
|
-
compressedBytes: bytes.byteLength,
|
|
123
|
-
maxRatio: maxCompressionRatio
|
|
124
|
-
})) throw new DocxArchiveError({
|
|
125
|
-
message: `DOCX archive declares more than ${maxCompressionRatio} bytes per archive byte`,
|
|
126
|
-
reason: "compression-ratio-exceeded"
|
|
88
|
+
const limits = {
|
|
89
|
+
maxEntryBytes,
|
|
90
|
+
maxTotalBytes,
|
|
91
|
+
maxEntries,
|
|
92
|
+
maxCompressionRatio
|
|
93
|
+
};
|
|
94
|
+
const read = await Result.tryPromise({
|
|
95
|
+
try: async () => await ZipArchive.read(bytes, {
|
|
96
|
+
limits: {
|
|
97
|
+
maxEntries,
|
|
98
|
+
maxEntryBytes,
|
|
99
|
+
maxTotalBytes,
|
|
100
|
+
maxCompressionRatio,
|
|
101
|
+
compressionRatioGraceBytes: DOCX_COMPRESSION_RATIO_GRACE_BYTES
|
|
102
|
+
},
|
|
103
|
+
isCompressionRatioExempt: (path) => !isRatioBoundedPart(path)
|
|
104
|
+
}),
|
|
105
|
+
catch: (cause) => toArchiveError(cause, limits)
|
|
127
106
|
});
|
|
107
|
+
if (Result.isError(read)) throw read.error;
|
|
108
|
+
const zip = read.value;
|
|
109
|
+
const archiveEntries = zip.entries();
|
|
128
110
|
const xmlLimits = {
|
|
129
111
|
...FOLIO_XML_RESOURCE_LIMITS,
|
|
130
112
|
...options.xmlLimits
|
|
131
113
|
};
|
|
132
114
|
const xmlBudget = createXmlPackageBudget();
|
|
133
115
|
const countedParts = /* @__PURE__ */ new Set();
|
|
134
|
-
|
|
135
|
-
let readChain = Promise.resolve();
|
|
116
|
+
let readBytes = 0;
|
|
136
117
|
const readEntry = async (path, readOptions = {}) => {
|
|
137
118
|
const requestedMaxBytes = resolveByteLimit({
|
|
138
119
|
value: readOptions.maxBytes,
|
|
139
120
|
fallback: maxEntryBytes,
|
|
140
121
|
name: "DOCX entry read byte limit"
|
|
141
122
|
});
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return await next;
|
|
123
|
+
const entry = zip.entry(path);
|
|
124
|
+
if (!entry) return null;
|
|
125
|
+
const entryLimit = Math.min(requestedMaxBytes, maxEntryBytes);
|
|
126
|
+
if (entry.size > entryLimit) throw new DocxArchiveError({
|
|
127
|
+
message: `DOCX entry "${path}" exceeded the ${entryLimit}-byte limit`,
|
|
128
|
+
reason: "entry-too-large"
|
|
129
|
+
});
|
|
130
|
+
if (readBytes + entry.size > maxTotalBytes) throw new DocxArchiveError({
|
|
131
|
+
message: `DOCX archive exceeded the ${maxTotalBytes}-byte cumulative limit while reading "${path}"`,
|
|
132
|
+
reason: "total-too-large"
|
|
133
|
+
});
|
|
134
|
+
readBytes += entry.size;
|
|
135
|
+
const content = await Result.tryPromise({
|
|
136
|
+
try: async () => await entry.bytes(),
|
|
137
|
+
catch: (cause) => toArchiveError(cause, limits)
|
|
138
|
+
});
|
|
139
|
+
if (Result.isError(content)) {
|
|
140
|
+
readBytes -= entry.size;
|
|
141
|
+
throw content.error;
|
|
142
|
+
}
|
|
143
|
+
return content.value;
|
|
164
144
|
};
|
|
165
145
|
return {
|
|
166
|
-
entries: Object.freeze(archiveEntries.map(({
|
|
146
|
+
entries: Object.freeze(archiveEntries.map(({ path }) => path)),
|
|
167
147
|
entryMetadata: Object.freeze(archiveEntries.map((entry) => ({
|
|
168
|
-
path: entry.
|
|
169
|
-
|
|
170
|
-
declaredUncompressedBytes: getZipEntrySizes(entry).uncompressedBytes
|
|
148
|
+
path: entry.path,
|
|
149
|
+
declaredUncompressedBytes: entry.size
|
|
171
150
|
}))),
|
|
172
151
|
async readEntryString(path) {
|
|
173
152
|
const content = await readEntry(path);
|
|
@@ -30,7 +30,7 @@ export type FolioDocxPackagePart = {
|
|
|
30
30
|
readonly path: string;
|
|
31
31
|
readonly kind: FolioDocxPackagePartKind;
|
|
32
32
|
readonly contentType: string | null;
|
|
33
|
-
readonly declaredUncompressedBytes: number
|
|
33
|
+
readonly declaredUncompressedBytes: number;
|
|
34
34
|
};
|
|
35
35
|
export type FolioDocxInspectedXmlPart = {
|
|
36
36
|
readonly path: string;
|
|
@@ -200,7 +200,7 @@ const inspectDocxPackage = async (bytes, options = {}) => {
|
|
|
200
200
|
const archive = await loadDocxArchive(bytes, options.archive);
|
|
201
201
|
const contentTypesBytes = await archive.readEntryUint8(CONTENT_TYPES_PATH, { maxBytes: CONTENT_TYPES_READ_LIMIT });
|
|
202
202
|
const declarations = parseContentTypes(contentTypesBytes === null ? null : decodeXml(contentTypesBytes, CONTENT_TYPES_PATH));
|
|
203
|
-
const parts = archive.entryMetadata.
|
|
203
|
+
const parts = archive.entryMetadata.map((entry) => toPackagePart(entry, declarations)).toSorted(({ path: left }, { path: right }) => comparePackagePaths(left, right));
|
|
204
204
|
const partsByPath = new Map(parts.map((part) => [part.path, part]));
|
|
205
205
|
validateRequestedParts({
|
|
206
206
|
requestedPaths,
|
|
@@ -4,10 +4,10 @@ import { DOCX_CONTAINER_TYPES, detectDocxContainerType } from "../encryption/con
|
|
|
4
4
|
import { DocxModelValidationError, validateFolioDocumentModel } from "../modelValidation.js";
|
|
5
5
|
import { DocxParseError, parseDocx } from "../parser.js";
|
|
6
6
|
import { findChildByLocalName, getAttributeAnyPrefix, getLocalName, getNamespacePrefix, parseXmlDocument } from "../xmlParser.js";
|
|
7
|
-
import { XmlResourceLimitError } from "../xmlResourceLimits.js";
|
|
8
7
|
import { getDocxXmlSafetyIssue } from "../xmlSafety.js";
|
|
9
8
|
import { DocxArchiveError, loadDocxArchive } from "./boundedArchive.js";
|
|
10
9
|
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
10
|
+
import { XmlResourceLimitError } from "@stll/docx-core/xml/limits";
|
|
11
11
|
// src/docx/server/validateDocxConformance.ts
|
|
12
12
|
const FOLIO_DOCX_CONFORMANCE_REPORT_VERSION = 1;
|
|
13
13
|
const FOLIO_DOCX_CONFORMANCE_PROFILE = "folio-supported-v1";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
2
|
import { captureVerbatimXml, isSafeCapturedXmlDocument } from "./verbatimCapture.js";
|
|
3
3
|
import { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, parseXmlDocument } from "./xmlParser.js";
|
|
4
|
-
import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
5
4
|
import { panic } from "better-result";
|
|
5
|
+
import { assertXmlResourceLimits } from "@stll/docx-core/xml/limits";
|
|
6
6
|
// src/docx/shapeAlternateContent.ts
|
|
7
7
|
/**
|
|
8
8
|
* Every modeled field, text body included: the Fallback repeats the shape's
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
2
|
import { spliceXml } from "./selectiveXmlPatch.js";
|
|
3
|
-
import { getXmlSourceRange, parseStreamingXmlWithSourceRanges } from "./streamingXmlParser.js";
|
|
4
3
|
import { TRANSITIONAL_NAMESPACE_BY_STRICT_URI } from "./strictValueEncodings.gen.js";
|
|
5
4
|
import { captureSourceProfileXml, isSafeCapturedXmlDocument } from "./verbatimCapture.js";
|
|
6
5
|
import { WORDPROCESSINGML_NAMESPACE_URIS, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
7
6
|
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
8
7
|
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
8
|
+
import { getXmlSourceRange, parseStreamingXmlWithSourceRanges } from "@stll/docx-core/xml";
|
|
9
9
|
// src/docx/storyBlockReplay.ts
|
|
10
10
|
const BLOCK_ELEMENT_NAMES = {
|
|
11
11
|
paragraph: "p",
|
package/dist/docx/unzip.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ZipArchive } from "@stll/docx-core/zip";
|
|
2
2
|
export declare class DocxSecurityError extends Error {
|
|
3
3
|
constructor(message: string);
|
|
4
4
|
}
|
|
@@ -75,7 +75,7 @@ export type RawDocxContent = {
|
|
|
75
75
|
media: Map<string, ArrayBuffer>;
|
|
76
76
|
fonts: Map<string, ArrayBuffer>;
|
|
77
77
|
allXml: Map<string, string>;
|
|
78
|
-
originalZip:
|
|
78
|
+
originalZip: ZipArchive;
|
|
79
79
|
originalBuffer: ArrayBuffer;
|
|
80
80
|
warnings: string[];
|
|
81
81
|
/** True when the input was a password-protected CFB container. */
|
|
@@ -100,7 +100,6 @@ export type UnzipDocxBehavior = {
|
|
|
100
100
|
export declare function unzipDocx(buffer: ArrayBuffer, options?: DocxUnzipOptions, { verifyUnreadEntries }?: UnzipDocxBehavior): Promise<RawDocxContent>;
|
|
101
101
|
/** Keep extracted projections and the source ZIP aligned after parser normalization. */
|
|
102
102
|
export declare const replaceRawDocxXmlParts: (content: RawDocxContent, parts: ReadonlyMap<string, string>) => Promise<void>;
|
|
103
|
-
export declare function getEntryUncompressedSize(file: JSZip.JSZipObject): number | null;
|
|
104
103
|
/**
|
|
105
104
|
* Get a list of all files in the DOCX
|
|
106
105
|
*
|