@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,194 +0,0 @@
|
|
|
1
|
-
import { TaggedError } from "better-result";
|
|
2
|
-
// src/docx/xmlResourceLimits.ts
|
|
3
|
-
const MEBIBYTE = 1048576;
|
|
4
|
-
const XmlResourceLimitErrorBase = TaggedError("XmlResourceLimitError");
|
|
5
|
-
/**
|
|
6
|
-
* Shared bounds for XML parts parsed by Folio.
|
|
7
|
-
*
|
|
8
|
-
* `maxBytes` and the package expansion ceiling bound the *markup*; they do not
|
|
9
|
-
* bound what parsing that markup allocates. A parsed element retains far more
|
|
10
|
-
* than the bytes it was written as, so a part that satisfies a byte bound can
|
|
11
|
-
* still cost multiples of it in tree. Measured on this repository's corpus
|
|
12
|
-
* generators (Bun 1.4, arm64, heap retained after a forced GC):
|
|
13
|
-
*
|
|
14
|
-
* shape bytes/element tree heap / part bytes
|
|
15
|
-
* `<w:r/>` 49.5 B 8.3x
|
|
16
|
-
* `<w:r a=".." x4/>` 105-124 B 2.8-3.3x
|
|
17
|
-
* `<w:r><w:t>x</w:t></w:r>` 162-171 B 14.1-14.8x
|
|
18
|
-
*
|
|
19
|
-
* An element is therefore the unit a memory bound has to count, because the
|
|
20
|
-
* adversarial shape is the cheap one: `<w:r/>` is 7 bytes of markup and 49.5
|
|
21
|
-
* bytes of tree, so 128 MiB of markup buys ~19M elements and ~950 MB of tree
|
|
22
|
-
* inside a byte budget that never trips.
|
|
23
|
-
*
|
|
24
|
-
* Corpus distribution (5,314 readable packages, every XML and .rels part):
|
|
25
|
-
*
|
|
26
|
-
* metric p50 p90 p99 p99.9 max
|
|
27
|
-
* elements / part 16 227 1,590 24,719 596,668
|
|
28
|
-
* elements / package 917 3,230 29,106 71,975 602,212
|
|
29
|
-
* attributes / part 27 577 1,993 21,037 630,374
|
|
30
|
-
* attributes / package 1,783 4,711 25,896 91,946 639,110
|
|
31
|
-
* depth / part 3 9 15 22 15,005
|
|
32
|
-
* elements per byte 0.012 0.027 0.036 0.043 0.111
|
|
33
|
-
*
|
|
34
|
-
* The defaults below reject no corpus package that today's bounds accept. The
|
|
35
|
-
* package budget is the one that matters: it caps a whole package at ~2.5M
|
|
36
|
-
* elements and ~3M attributes, which is at most ~425 MB of tree at the densest
|
|
37
|
-
* measured shape and ~124 MB at the cheapest. Before it existed, only
|
|
38
|
-
* `word/document.xml`, `word/styles.xml` and `word/numbering.xml` were counted
|
|
39
|
-
* at all, so a package of many merely-large parts could reach the expansion
|
|
40
|
-
* ceiling of 250 MiB, ~37M elements and well past 1.8 GB of tree while passing
|
|
41
|
-
* every bound. Lower these to trade format reach for a smaller ceiling.
|
|
42
|
-
*/
|
|
43
|
-
const FOLIO_XML_RESOURCE_LIMITS = {
|
|
44
|
-
maxBytes: 128 * MEBIBYTE,
|
|
45
|
-
maxDepth: 100,
|
|
46
|
-
/** 1.68x the corpus maximum (596,668). Unchanged; a shipped bound is not loosened. */
|
|
47
|
-
maxElementsPerPart: 1e6,
|
|
48
|
-
/** 3.97x the corpus maximum (630,374). */
|
|
49
|
-
maxAttributesPerPart: 25e5,
|
|
50
|
-
/** 4.15x the corpus maximum (602,212). */
|
|
51
|
-
maxElementsPerPackage: 25e5,
|
|
52
|
-
/** 4.69x the corpus maximum (639,110). */
|
|
53
|
-
maxAttributesPerPackage: 3e6
|
|
54
|
-
};
|
|
55
|
-
/** XML input exceeded a parser resource bound or could not be scanned safely. */
|
|
56
|
-
var XmlResourceLimitError = class extends XmlResourceLimitErrorBase {};
|
|
57
|
-
const createXmlPackageBudget = () => ({
|
|
58
|
-
elements: 0,
|
|
59
|
-
attributes: 0
|
|
60
|
-
});
|
|
61
|
-
/** No UTF-16 code unit encodes to more than three UTF-8 bytes; a pair encodes to four. */
|
|
62
|
-
const MAX_UTF8_BYTES_PER_CODE_UNIT = 3;
|
|
63
|
-
const exceedsUtf8ByteLimit = (value, maxBytes) => {
|
|
64
|
-
if (value.length * MAX_UTF8_BYTES_PER_CODE_UNIT <= maxBytes) return false;
|
|
65
|
-
let bytes = 0;
|
|
66
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
67
|
-
const codeUnit = value.charCodeAt(index);
|
|
68
|
-
if (codeUnit <= 127) bytes += 1;
|
|
69
|
-
else if (codeUnit <= 2047) bytes += 2;
|
|
70
|
-
else if (codeUnit >= 55296 && codeUnit <= 56319 && index + 1 < value.length && value.charCodeAt(index + 1) >= 56320 && value.charCodeAt(index + 1) <= 57343) {
|
|
71
|
-
bytes += 4;
|
|
72
|
-
index += 1;
|
|
73
|
-
} else bytes += 3;
|
|
74
|
-
if (bytes > maxBytes) return true;
|
|
75
|
-
}
|
|
76
|
-
return false;
|
|
77
|
-
};
|
|
78
|
-
const isXmlWhitespace = (code) => code === 9 || code === 10 || code === 13 || code === 32;
|
|
79
|
-
const scanTag = (xml, start) => {
|
|
80
|
-
let quote = 0;
|
|
81
|
-
let attributes = 0;
|
|
82
|
-
for (let cursor = start; cursor < xml.length; cursor += 1) {
|
|
83
|
-
const code = xml.charCodeAt(cursor);
|
|
84
|
-
if (quote !== 0) {
|
|
85
|
-
if (code === quote) quote = 0;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
if (code === 34 || code === 39) {
|
|
89
|
-
quote = code;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
if (code === 61) {
|
|
93
|
-
attributes += 1;
|
|
94
|
-
continue;
|
|
95
|
-
}
|
|
96
|
-
if (code === 62) return {
|
|
97
|
-
close: cursor,
|
|
98
|
-
attributes
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
close: -1,
|
|
103
|
-
attributes
|
|
104
|
-
};
|
|
105
|
-
};
|
|
106
|
-
const throwSyntaxLimit = () => {
|
|
107
|
-
throw new XmlResourceLimitError({
|
|
108
|
-
message: "XML resource preflight could not safely scan malformed markup",
|
|
109
|
-
limit: "syntax",
|
|
110
|
-
observed: 0,
|
|
111
|
-
allowed: 0
|
|
112
|
-
});
|
|
113
|
-
};
|
|
114
|
-
/**
|
|
115
|
-
* Bound XML bytes, element count, attribute count and nesting before building
|
|
116
|
-
* an object tree. The lexical scan is iterative, so deeply nested input cannot
|
|
117
|
-
* consume the JS call stack before the depth limit is enforced, and every
|
|
118
|
-
* bound is checked at the point it is crossed, so the work done before a
|
|
119
|
-
* refusal is proportional to the limit rather than to the input.
|
|
120
|
-
*/
|
|
121
|
-
const assertXmlResourceLimits = ({ xml, limits = FOLIO_XML_RESOURCE_LIMITS, partPath, budget }) => {
|
|
122
|
-
const refuse = (limit, message, observed, allowed) => {
|
|
123
|
-
throw new XmlResourceLimitError({
|
|
124
|
-
message: partPath === void 0 ? message : `${message} (part ${partPath})`,
|
|
125
|
-
limit,
|
|
126
|
-
...partPath === void 0 ? {} : { partPath },
|
|
127
|
-
observed,
|
|
128
|
-
allowed
|
|
129
|
-
});
|
|
130
|
-
};
|
|
131
|
-
if (exceedsUtf8ByteLimit(xml, limits.maxBytes)) refuse("bytes", `XML part exceeds ${String(limits.maxBytes)} bytes`, limits.maxBytes + 1, limits.maxBytes);
|
|
132
|
-
const budgetElements = budget?.elements ?? 0;
|
|
133
|
-
const budgetAttributes = budget?.attributes ?? 0;
|
|
134
|
-
let cursor = 0;
|
|
135
|
-
let depth = 0;
|
|
136
|
-
let maxDepth = 0;
|
|
137
|
-
let nodes = 0;
|
|
138
|
-
let attributes = 0;
|
|
139
|
-
while (cursor < xml.length) {
|
|
140
|
-
const open = xml.indexOf("<", cursor);
|
|
141
|
-
if (open === -1) break;
|
|
142
|
-
if (xml.startsWith("<!--", open)) {
|
|
143
|
-
const close = xml.indexOf("-->", open + 4);
|
|
144
|
-
if (close === -1) throwSyntaxLimit();
|
|
145
|
-
cursor = close + 3;
|
|
146
|
-
continue;
|
|
147
|
-
}
|
|
148
|
-
if (xml.startsWith("<![CDATA[", open)) {
|
|
149
|
-
const close = xml.indexOf("]]>", open + 9);
|
|
150
|
-
if (close === -1) throwSyntaxLimit();
|
|
151
|
-
cursor = close + 3;
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
if (xml.startsWith("<?", open)) {
|
|
155
|
-
const close = xml.indexOf("?>", open + 2);
|
|
156
|
-
if (close === -1) throwSyntaxLimit();
|
|
157
|
-
cursor = close + 2;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (xml.startsWith("<!", open)) throwSyntaxLimit();
|
|
161
|
-
const { close, attributes: tagAttributes } = scanTag(xml, open + 1);
|
|
162
|
-
if (close === -1) throwSyntaxLimit();
|
|
163
|
-
if (xml.charCodeAt(open + 1) === 47) {
|
|
164
|
-
if (depth === 0) throwSyntaxLimit();
|
|
165
|
-
depth -= 1;
|
|
166
|
-
cursor = close + 1;
|
|
167
|
-
continue;
|
|
168
|
-
}
|
|
169
|
-
nodes += 1;
|
|
170
|
-
if (nodes > limits.maxElementsPerPart) refuse("elements", `XML part contains more than ${String(limits.maxElementsPerPart)} elements`, nodes, limits.maxElementsPerPart);
|
|
171
|
-
if (budgetElements + nodes > limits.maxElementsPerPackage) refuse("package-elements", `DOCX package contains more than ${String(limits.maxElementsPerPackage)} XML elements`, budgetElements + nodes, limits.maxElementsPerPackage);
|
|
172
|
-
attributes += tagAttributes;
|
|
173
|
-
if (attributes > limits.maxAttributesPerPart) refuse("attributes", `XML part contains more than ${String(limits.maxAttributesPerPart)} attributes`, attributes, limits.maxAttributesPerPart);
|
|
174
|
-
if (budgetAttributes + attributes > limits.maxAttributesPerPackage) refuse("package-attributes", `DOCX package contains more than ${String(limits.maxAttributesPerPackage)} XML attributes`, budgetAttributes + attributes, limits.maxAttributesPerPackage);
|
|
175
|
-
let lastContent = close - 1;
|
|
176
|
-
while (lastContent > open && isXmlWhitespace(xml.charCodeAt(lastContent))) lastContent -= 1;
|
|
177
|
-
const elementDepth = depth + 1;
|
|
178
|
-
if (elementDepth > limits.maxDepth) refuse("depth", `XML part is nested deeper than ${String(limits.maxDepth)} elements`, elementDepth, limits.maxDepth);
|
|
179
|
-
if (elementDepth > maxDepth) maxDepth = elementDepth;
|
|
180
|
-
if (xml.charCodeAt(lastContent) !== 47) depth = elementDepth;
|
|
181
|
-
cursor = close + 1;
|
|
182
|
-
}
|
|
183
|
-
if (depth !== 0) throwSyntaxLimit();
|
|
184
|
-
if (budget) {
|
|
185
|
-
budget.elements += nodes;
|
|
186
|
-
budget.attributes += attributes;
|
|
187
|
-
}
|
|
188
|
-
return {
|
|
189
|
-
elements: nodes,
|
|
190
|
-
attributes,
|
|
191
|
-
maxDepth
|
|
192
|
-
};
|
|
193
|
-
};
|
|
194
|
-
export { FOLIO_XML_RESOURCE_LIMITS, XmlResourceLimitError, assertXmlResourceLimits, createXmlPackageBudget, exceedsUtf8ByteLimit };
|