ooxml-core 0.12.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/crypto/index.cjs +118 -31
- package/dist/crypto/index.mjs +118 -31
- package/dist/docx/load/index.cjs +1273 -110
- package/dist/docx/load/index.mjs +1273 -110
- package/dist/docx/load/legacy-doc.d.ts.map +1 -1
- package/dist/pptx/automation/index.cjs +1479 -304
- package/dist/pptx/automation/index.d.ts +1 -1
- package/dist/pptx/automation/index.mjs +1479 -304
- package/dist/pptx/chunks/{index-LoCVDu8Z.d.ts → index-CT9y69AZ.d.ts} +22 -12
- package/dist/pptx/chunks/index-CT9y69AZ.d.ts.map +1 -0
- package/dist/pptx/cli/index.cjs +307 -152
- package/dist/pptx/cli/index.mjs +307 -152
- package/dist/pptx/index.cjs +1479 -304
- package/dist/pptx/index.d.ts +1 -1
- package/dist/pptx/index.mjs +1479 -304
- package/dist/visio/index.cjs +7498 -127
- package/dist/visio/index.d.ts +3 -1
- package/dist/visio/index.d.ts.map +1 -1
- package/dist/visio/index.mjs +7497 -128
- package/dist/visio/legacy.d.ts +10 -0
- package/dist/visio/legacy.d.ts.map +1 -0
- package/dist/visio/load.d.ts +5 -0
- package/dist/visio/load.d.ts.map +1 -0
- package/dist/visio/model.d.ts +1 -1
- package/dist/visio/model.d.ts.map +1 -1
- package/dist/xlsx/load/index.cjs +144 -52
- package/dist/xlsx/load/index.mjs +144 -52
- package/package.json +2 -2
- package/dist/pptx/chunks/index-LoCVDu8Z.d.ts.map +0 -1
package/dist/docx/load/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ function isOoxmlCryptoError(error) {
|
|
|
35
35
|
return code === "password-required" || code === "incorrect-password" || code === "data-integrity";
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
38
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-parser-types.js
|
|
39
39
|
var OLE_MAGIC = new Uint8Array([208, 207, 17, 224, 161, 177, 26, 225]);
|
|
40
40
|
var ENDOFCHAIN = 4294967294;
|
|
41
41
|
var FREESECT = 4294967295;
|
|
@@ -53,10 +53,13 @@ var Ole2ParseError = class extends Error {
|
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
56
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-parser-read.js
|
|
57
57
|
function parseOle2(buffer) {
|
|
58
58
|
const data = new Uint8Array(buffer);
|
|
59
59
|
const view = new DataView(buffer);
|
|
60
|
+
if (data.length < 512) {
|
|
61
|
+
throw new Ole2ParseError("Truncated OLE2 compound file header");
|
|
62
|
+
}
|
|
60
63
|
for (let i = 0; i < OLE_MAGIC.length; i++) {
|
|
61
64
|
if (data[i] !== OLE_MAGIC[i]) {
|
|
62
65
|
throw new Ole2ParseError("Not a valid OLE2 compound file");
|
|
@@ -70,8 +73,15 @@ function parseOle2(buffer) {
|
|
|
70
73
|
}
|
|
71
74
|
const sectorSizePower = view.getUint16(30, true);
|
|
72
75
|
const miniSectorSizePower = view.getUint16(32, true);
|
|
76
|
+
if (majorVersion !== 3 && majorVersion !== 4 || sectorSizePower !== (majorVersion === 3 ? 9 : 12) || miniSectorSizePower !== 6) {
|
|
77
|
+
throw new Ole2ParseError("Unsupported OLE2 version or sector size");
|
|
78
|
+
}
|
|
73
79
|
const sectorSize = 1 << sectorSizePower;
|
|
74
80
|
const miniSectorSize = 1 << miniSectorSizePower;
|
|
81
|
+
if (data.length < sectorSize || data.length % sectorSize !== 0) {
|
|
82
|
+
throw new Ole2ParseError("Truncated OLE2 sector");
|
|
83
|
+
}
|
|
84
|
+
const sectorCount = data.length / sectorSize - 1;
|
|
75
85
|
const totalFATSectors = view.getUint32(44, true);
|
|
76
86
|
const firstDirectorySector = view.getUint32(48, true);
|
|
77
87
|
const miniStreamCutoff = view.getUint32(56, true);
|
|
@@ -79,6 +89,9 @@ function parseOle2(buffer) {
|
|
|
79
89
|
const totalMiniFATSectors = view.getUint32(64, true);
|
|
80
90
|
const firstDIFATSector = view.getUint32(68, true);
|
|
81
91
|
const totalDIFATSectors = view.getUint32(72, true);
|
|
92
|
+
if (totalFATSectors > sectorCount || totalMiniFATSectors > sectorCount || totalDIFATSectors > sectorCount) {
|
|
93
|
+
throw new Ole2ParseError("Allocation table count exceeds file size");
|
|
94
|
+
}
|
|
82
95
|
function sectorOffset(sector) {
|
|
83
96
|
return (sector + 1) * sectorSize;
|
|
84
97
|
}
|
|
@@ -97,7 +110,11 @@ function parseOle2(buffer) {
|
|
|
97
110
|
}
|
|
98
111
|
}
|
|
99
112
|
let difatSector = firstDIFATSector;
|
|
113
|
+
const difatSeen = /* @__PURE__ */ new Set();
|
|
100
114
|
for (let d = 0; d < totalDIFATSectors && difatSector <= MAXREGSECT; d++) {
|
|
115
|
+
if (difatSeen.has(difatSector))
|
|
116
|
+
throw new Ole2ParseError("Circular DIFAT chain");
|
|
117
|
+
difatSeen.add(difatSector);
|
|
101
118
|
const difatData = readSector(difatSector);
|
|
102
119
|
const difatView = new DataView(difatData.buffer, difatData.byteOffset, difatData.byteLength);
|
|
103
120
|
const entriesPerSector = (sectorSize - 4) / 4;
|
|
@@ -109,11 +126,14 @@ function parseOle2(buffer) {
|
|
|
109
126
|
}
|
|
110
127
|
difatSector = difatView.getUint32(sectorSize - 4, true);
|
|
111
128
|
}
|
|
129
|
+
if (difatSeen.size !== totalDIFATSectors || fatSectors.length !== totalFATSectors || new Set(fatSectors).size !== fatSectors.length || fatSectors.some((id) => difatSeen.has(id))) {
|
|
130
|
+
throw new Ole2ParseError("Invalid DIFAT allocation table");
|
|
131
|
+
}
|
|
112
132
|
const fatEntries = [];
|
|
113
133
|
for (const fatSector of fatSectors) {
|
|
114
134
|
const fatData = readSector(fatSector);
|
|
115
135
|
const fatView = new DataView(fatData.buffer, fatData.byteOffset, fatData.byteLength);
|
|
116
|
-
for (let i = 0; i < sectorSize / 4; i++) {
|
|
136
|
+
for (let i = 0; i < sectorSize / 4 && fatEntries.length < sectorCount; i++) {
|
|
117
137
|
fatEntries.push(fatView.getUint32(i * 4, true));
|
|
118
138
|
}
|
|
119
139
|
}
|
|
@@ -127,8 +147,12 @@ function parseOle2(buffer) {
|
|
|
127
147
|
}
|
|
128
148
|
visited.add(current);
|
|
129
149
|
sectors.push(readSector(current));
|
|
130
|
-
|
|
150
|
+
if (fatEntries[current] === void 0)
|
|
151
|
+
throw new Ole2ParseError("Missing FAT chain entry");
|
|
152
|
+
current = fatEntries[current];
|
|
131
153
|
}
|
|
154
|
+
if (current !== ENDOFCHAIN)
|
|
155
|
+
throw new Ole2ParseError("Invalid FAT chain terminator");
|
|
132
156
|
const totalLength = sectors.length * sectorSize;
|
|
133
157
|
const result = new Uint8Array(totalLength);
|
|
134
158
|
let offset = 0;
|
|
@@ -139,16 +163,14 @@ function parseOle2(buffer) {
|
|
|
139
163
|
return result;
|
|
140
164
|
}
|
|
141
165
|
function readStream(startSector, size) {
|
|
166
|
+
if (size === 0)
|
|
167
|
+
return new Uint8Array(0);
|
|
168
|
+
if (size > sectorCount * sectorSize)
|
|
169
|
+
throw new Ole2ParseError("Stream size exceeds file size");
|
|
142
170
|
const raw = readSectorChain(startSector);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (firstMiniFATSector <= MAXREGSECT && totalMiniFATSectors > 0) {
|
|
147
|
-
const miniFatRaw = readSectorChain(firstMiniFATSector);
|
|
148
|
-
const miniFatView = new DataView(miniFatRaw.buffer, miniFatRaw.byteOffset, miniFatRaw.byteLength);
|
|
149
|
-
for (let i = 0; i < miniFatRaw.length / 4; i++) {
|
|
150
|
-
miniFatEntries.push(miniFatView.getUint32(i * 4, true));
|
|
151
|
-
}
|
|
171
|
+
if (raw.length < size)
|
|
172
|
+
throw new Ole2ParseError("Truncated stream sector chain");
|
|
173
|
+
return raw.subarray(0, size);
|
|
152
174
|
}
|
|
153
175
|
const dirRaw = readSectorChain(firstDirectorySector);
|
|
154
176
|
const numEntries = Math.floor(dirRaw.length / DIR_ENTRY_SIZE);
|
|
@@ -161,6 +183,9 @@ function parseOle2(buffer) {
|
|
|
161
183
|
if (objectType === ENTRY_TYPE_EMPTY) {
|
|
162
184
|
continue;
|
|
163
185
|
}
|
|
186
|
+
if (nameLen < 2 || nameLen > 64 || nameLen % 2 !== 0 || entryView.getUint16(nameLen - 2, true) !== 0) {
|
|
187
|
+
throw new Ole2ParseError("Invalid directory entry name");
|
|
188
|
+
}
|
|
164
189
|
const nameBytes = Math.max(0, nameLen - 2);
|
|
165
190
|
let name = "";
|
|
166
191
|
for (let j = 0; j < nameBytes; j += 2) {
|
|
@@ -174,8 +199,11 @@ function parseOle2(buffer) {
|
|
|
174
199
|
const sizeLow = entryView.getUint32(120, true);
|
|
175
200
|
let size = sizeLow;
|
|
176
201
|
if (majorVersion === 4) {
|
|
177
|
-
entryView.getUint32(124, true);
|
|
178
|
-
size = sizeLow;
|
|
202
|
+
const sizeHigh = entryView.getUint32(124, true);
|
|
203
|
+
size = sizeLow + sizeHigh * 4294967296;
|
|
204
|
+
if (!Number.isSafeInteger(size) || size > sectorCount * sectorSize) {
|
|
205
|
+
throw new Ole2ParseError("Stream size exceeds supported file bounds");
|
|
206
|
+
}
|
|
179
207
|
}
|
|
180
208
|
entries.push({
|
|
181
209
|
name,
|
|
@@ -189,11 +217,28 @@ function parseOle2(buffer) {
|
|
|
189
217
|
});
|
|
190
218
|
}
|
|
191
219
|
const rootEntry = entries.find((e) => e.type === ENTRY_TYPE_ROOT);
|
|
220
|
+
if (!rootEntry || entries.filter((e) => e.type === ENTRY_TYPE_ROOT).length !== 1) {
|
|
221
|
+
throw new Ole2ParseError("Invalid root directory entry");
|
|
222
|
+
}
|
|
192
223
|
let miniStreamData;
|
|
193
224
|
if (rootEntry && rootEntry.startSector <= MAXREGSECT) {
|
|
194
|
-
miniStreamData =
|
|
225
|
+
miniStreamData = readStream(rootEntry.startSector, rootEntry.size);
|
|
226
|
+
}
|
|
227
|
+
const miniFatEntries = [];
|
|
228
|
+
if (firstMiniFATSector <= MAXREGSECT && totalMiniFATSectors > 0) {
|
|
229
|
+
const miniFatRaw = readSectorChain(firstMiniFATSector);
|
|
230
|
+
if (miniFatRaw.length !== totalMiniFATSectors * sectorSize) {
|
|
231
|
+
throw new Ole2ParseError("Unexpected mini FAT chain length");
|
|
232
|
+
}
|
|
233
|
+
const miniFatView = new DataView(miniFatRaw.buffer, miniFatRaw.byteOffset, miniFatRaw.byteLength);
|
|
234
|
+
const miniCapacity = Math.floor((miniStreamData?.length ?? 0) / miniSectorSize);
|
|
235
|
+
for (let i = 0; i < miniFatRaw.length / 4 && i < miniCapacity; i++) {
|
|
236
|
+
miniFatEntries.push(miniFatView.getUint32(i * 4, true));
|
|
237
|
+
}
|
|
195
238
|
}
|
|
196
239
|
function readMiniStream(startSector, size) {
|
|
240
|
+
if (size === 0)
|
|
241
|
+
return new Uint8Array(0);
|
|
197
242
|
if (!miniStreamData) {
|
|
198
243
|
throw new Ole2ParseError("Mini stream container not found");
|
|
199
244
|
}
|
|
@@ -206,17 +251,24 @@ function parseOle2(buffer) {
|
|
|
206
251
|
}
|
|
207
252
|
visited.add(current);
|
|
208
253
|
const offset2 = current * miniSectorSize;
|
|
254
|
+
if (offset2 + miniSectorSize > miniStreamData.length || miniFatEntries[current] === void 0) {
|
|
255
|
+
throw new Ole2ParseError("Mini stream sector exceeds allocation bounds");
|
|
256
|
+
}
|
|
209
257
|
sectors.push(miniStreamData.subarray(offset2, offset2 + miniSectorSize));
|
|
210
|
-
current = miniFatEntries[current]
|
|
258
|
+
current = miniFatEntries[current];
|
|
211
259
|
}
|
|
260
|
+
if (current !== ENDOFCHAIN)
|
|
261
|
+
throw new Ole2ParseError("Invalid mini FAT chain terminator");
|
|
212
262
|
const totalLength = sectors.length * miniSectorSize;
|
|
263
|
+
if (totalLength < size)
|
|
264
|
+
throw new Ole2ParseError("Truncated mini stream sector chain");
|
|
213
265
|
const result = new Uint8Array(totalLength);
|
|
214
266
|
let offset = 0;
|
|
215
267
|
for (const sector of sectors) {
|
|
216
268
|
result.set(sector, offset);
|
|
217
269
|
offset += miniSectorSize;
|
|
218
270
|
}
|
|
219
|
-
return result.subarray(0,
|
|
271
|
+
return result.subarray(0, size);
|
|
220
272
|
}
|
|
221
273
|
function getStream(name) {
|
|
222
274
|
const entry = entries.find((e) => (e.type === ENTRY_TYPE_STREAM || e.type === ENTRY_TYPE_ROOT) && e.name === name);
|
|
@@ -231,7 +283,7 @@ function parseOle2(buffer) {
|
|
|
231
283
|
return { entries, getStream };
|
|
232
284
|
}
|
|
233
285
|
|
|
234
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
286
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/utils/ooxml-crypto-primitives.js
|
|
235
287
|
function getSubtle() {
|
|
236
288
|
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
237
289
|
return globalThis.crypto.subtle;
|
|
@@ -1117,13 +1169,16 @@ function isEncryptedOoxmlPackage(input) {
|
|
|
1117
1169
|
}
|
|
1118
1170
|
}
|
|
1119
1171
|
|
|
1120
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
1172
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-parser-write-helpers.js
|
|
1121
1173
|
function compareDirEntryNames(a, b) {
|
|
1174
|
+
const ua = a.toUpperCase();
|
|
1175
|
+
const ub = b.toUpperCase();
|
|
1176
|
+
if (ua.length !== a.length || ub.length !== b.length) {
|
|
1177
|
+
throw new Error("CFB stream names with expanding uppercase mappings are unsupported");
|
|
1178
|
+
}
|
|
1122
1179
|
if (a.length !== b.length) {
|
|
1123
1180
|
return a.length - b.length;
|
|
1124
1181
|
}
|
|
1125
|
-
const ua = a.toUpperCase();
|
|
1126
|
-
const ub = b.toUpperCase();
|
|
1127
1182
|
for (let i = 0; i < ua.length; i++) {
|
|
1128
1183
|
const diff = ua.charCodeAt(i) - ub.charCodeAt(i);
|
|
1129
1184
|
if (diff !== 0) {
|
|
@@ -1140,9 +1195,27 @@ function encodeName(name) {
|
|
|
1140
1195
|
}
|
|
1141
1196
|
return bytes;
|
|
1142
1197
|
}
|
|
1143
|
-
function
|
|
1144
|
-
|
|
1145
|
-
|
|
1198
|
+
function buildDirectoryTree(entryCount) {
|
|
1199
|
+
const links = /* @__PURE__ */ new Map();
|
|
1200
|
+
let redLevel = 0;
|
|
1201
|
+
for (let remaining = entryCount - 2; remaining >= 0; remaining = Math.floor(remaining / 2) - 1)
|
|
1202
|
+
redLevel++;
|
|
1203
|
+
const build = (low, high, level) => {
|
|
1204
|
+
if (low > high)
|
|
1205
|
+
return 4294967295;
|
|
1206
|
+
const id = Math.floor((low + high) / 2);
|
|
1207
|
+
links.set(id, {
|
|
1208
|
+
left: build(low, id - 1, level + 1),
|
|
1209
|
+
right: build(id + 1, high, level + 1),
|
|
1210
|
+
color: level === redLevel ? 0 : 1
|
|
1211
|
+
});
|
|
1212
|
+
return id;
|
|
1213
|
+
};
|
|
1214
|
+
return { root: build(1, entryCount - 1, 0), links };
|
|
1215
|
+
}
|
|
1216
|
+
function writeFatChain(fat, chain2) {
|
|
1217
|
+
for (let i = 0; i < chain2.sectors.length; i++) {
|
|
1218
|
+
fat[chain2.sectors[i]] = i < chain2.sectors.length - 1 ? chain2.sectors[i + 1] : ENDOFCHAIN;
|
|
1146
1219
|
}
|
|
1147
1220
|
}
|
|
1148
1221
|
function writeFatRun(fat, firstSector, count) {
|
|
@@ -1163,7 +1236,7 @@ function writeInt32Sectors(outBytes, int32Data, firstSector, numSectors, sectorS
|
|
|
1163
1236
|
}
|
|
1164
1237
|
}
|
|
1165
1238
|
|
|
1166
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
1239
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-parser-write-serialize.js
|
|
1167
1240
|
function writeHeader(outView, outBytes, params) {
|
|
1168
1241
|
outBytes.set(OLE_MAGIC, 0);
|
|
1169
1242
|
outView.setUint16(24, 62, true);
|
|
@@ -1221,6 +1294,7 @@ function writeDifatSectors(outView, params) {
|
|
|
1221
1294
|
function serializeDirectoryEntries(dirEntries, numDirSectors, sectorSize) {
|
|
1222
1295
|
const dirData = new Uint8Array(numDirSectors * sectorSize);
|
|
1223
1296
|
const dirView = new DataView(dirData.buffer);
|
|
1297
|
+
const tree = buildDirectoryTree(dirEntries.length);
|
|
1224
1298
|
for (let i = 0; i < dirEntries.length; i++) {
|
|
1225
1299
|
const entry = dirEntries[i];
|
|
1226
1300
|
const entryOffset = i * DIR_ENTRY_SIZE;
|
|
@@ -1228,17 +1302,17 @@ function serializeDirectoryEntries(dirEntries, numDirSectors, sectorSize) {
|
|
|
1228
1302
|
dirData.set(nameBytes.subarray(0, Math.min(nameBytes.length, 64)), entryOffset);
|
|
1229
1303
|
dirView.setUint16(entryOffset + 64, Math.min((entry.name.length + 1) * 2, 64), true);
|
|
1230
1304
|
dirData[entryOffset + 66] = entry.type;
|
|
1231
|
-
dirData[entryOffset + 67] = 1;
|
|
1305
|
+
dirData[entryOffset + 67] = i === 0 ? 1 : tree.links.get(i).color;
|
|
1232
1306
|
if (entry.clsid) {
|
|
1233
1307
|
dirData.set(entry.clsid.subarray(0, 16), entryOffset + 80);
|
|
1234
1308
|
}
|
|
1235
1309
|
if (i === 0) {
|
|
1236
1310
|
dirView.setUint32(entryOffset + 68, 4294967295, true);
|
|
1237
1311
|
dirView.setUint32(entryOffset + 72, 4294967295, true);
|
|
1238
|
-
dirView.setUint32(entryOffset + 76,
|
|
1312
|
+
dirView.setUint32(entryOffset + 76, tree.root, true);
|
|
1239
1313
|
} else {
|
|
1240
|
-
dirView.setUint32(entryOffset + 68,
|
|
1241
|
-
dirView.setUint32(entryOffset + 72, i
|
|
1314
|
+
dirView.setUint32(entryOffset + 68, tree.links.get(i).left, true);
|
|
1315
|
+
dirView.setUint32(entryOffset + 72, tree.links.get(i).right, true);
|
|
1242
1316
|
dirView.setUint32(entryOffset + 76, 4294967295, true);
|
|
1243
1317
|
}
|
|
1244
1318
|
dirView.setUint32(entryOffset + 116, entry.startSector, true);
|
|
@@ -1246,19 +1320,32 @@ function serializeDirectoryEntries(dirEntries, numDirSectors, sectorSize) {
|
|
|
1246
1320
|
}
|
|
1247
1321
|
return dirData;
|
|
1248
1322
|
}
|
|
1249
|
-
function writeStreamSectors(outBytes, streamData,
|
|
1250
|
-
for (let i = 0; i <
|
|
1251
|
-
const sectorOffset = (
|
|
1323
|
+
function writeStreamSectors(outBytes, streamData, chain2, sectorSize) {
|
|
1324
|
+
for (let i = 0; i < chain2.sectors.length; i++) {
|
|
1325
|
+
const sectorOffset = (chain2.sectors[i] + 1) * sectorSize;
|
|
1252
1326
|
const srcOffset = i * sectorSize;
|
|
1253
1327
|
const srcEnd = Math.min(srcOffset + sectorSize, streamData.length);
|
|
1254
1328
|
outBytes.set(streamData.subarray(srcOffset, srcEnd), sectorOffset);
|
|
1255
1329
|
}
|
|
1256
1330
|
}
|
|
1257
1331
|
|
|
1258
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
1332
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-parser-write.js
|
|
1259
1333
|
function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
1260
1334
|
const sectorSize = 512;
|
|
1261
1335
|
const miniSectorSize = 64;
|
|
1336
|
+
const names = [...streams2.keys()].sort(compareDirEntryNames);
|
|
1337
|
+
for (let i = 0; i < names.length; i++) {
|
|
1338
|
+
const name = names[i];
|
|
1339
|
+
if (name.toUpperCase().length !== name.length) {
|
|
1340
|
+
throw new Error("CFB stream names with expanding uppercase mappings are unsupported");
|
|
1341
|
+
}
|
|
1342
|
+
if (!name.length || name.length > 31 || /[\x00\\/:!]/.test(name)) {
|
|
1343
|
+
throw new Error("CFB stream names must be 1-31 UTF-16 code units without NUL, \\, /, :, or !");
|
|
1344
|
+
}
|
|
1345
|
+
if (i > 0 && compareDirEntryNames(names[i - 1], name) === 0) {
|
|
1346
|
+
throw new Error("Duplicate CFB stream names");
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1262
1349
|
const regularStreams = [];
|
|
1263
1350
|
const miniStreams = [];
|
|
1264
1351
|
for (const [name, data] of streams2) {
|
|
@@ -1276,7 +1363,7 @@ function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
|
1276
1363
|
for (let i = 0; i < numSectors; i++) {
|
|
1277
1364
|
sectors.push(nextSector++);
|
|
1278
1365
|
}
|
|
1279
|
-
fatChains.set(stream.name, { start: sectors[0] ??
|
|
1366
|
+
fatChains.set(stream.name, { start: sectors[0] ?? ENDOFCHAIN, sectors });
|
|
1280
1367
|
}
|
|
1281
1368
|
let miniStreamContainer = new Uint8Array(0);
|
|
1282
1369
|
const miniFatChains = /* @__PURE__ */ new Map();
|
|
@@ -1299,7 +1386,7 @@ function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
|
1299
1386
|
miniOffset += miniSectorSize;
|
|
1300
1387
|
}
|
|
1301
1388
|
miniFatChains.set(stream.name, {
|
|
1302
|
-
start: miniSectors[0] ??
|
|
1389
|
+
start: miniSectors[0] ?? ENDOFCHAIN,
|
|
1303
1390
|
sectors: miniSectors
|
|
1304
1391
|
});
|
|
1305
1392
|
}
|
|
@@ -1322,20 +1409,20 @@ function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
|
1322
1409
|
clsid: rootClsid
|
|
1323
1410
|
});
|
|
1324
1411
|
for (const stream of regularStreams) {
|
|
1325
|
-
const
|
|
1412
|
+
const chain2 = fatChains.get(stream.name);
|
|
1326
1413
|
dirEntries.push({
|
|
1327
1414
|
name: stream.name,
|
|
1328
1415
|
type: ENTRY_TYPE_STREAM,
|
|
1329
|
-
startSector:
|
|
1416
|
+
startSector: chain2.start,
|
|
1330
1417
|
size: stream.data.length
|
|
1331
1418
|
});
|
|
1332
1419
|
}
|
|
1333
1420
|
for (const stream of miniStreams) {
|
|
1334
|
-
const
|
|
1421
|
+
const chain2 = miniFatChains.get(stream.name);
|
|
1335
1422
|
dirEntries.push({
|
|
1336
1423
|
name: stream.name,
|
|
1337
1424
|
type: ENTRY_TYPE_STREAM,
|
|
1338
|
-
startSector:
|
|
1425
|
+
startSector: chain2.start,
|
|
1339
1426
|
size: stream.data.length
|
|
1340
1427
|
});
|
|
1341
1428
|
}
|
|
@@ -1347,7 +1434,7 @@ function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
|
1347
1434
|
nextSector += numDirSectors;
|
|
1348
1435
|
let firstMiniFATSector = ENDOFCHAIN;
|
|
1349
1436
|
let numMiniFATSectors = 0;
|
|
1350
|
-
if (
|
|
1437
|
+
if (nextMiniSector > 0) {
|
|
1351
1438
|
numMiniFATSectors = Math.ceil(nextMiniSector * 4 / sectorSize);
|
|
1352
1439
|
firstMiniFATSector = nextSector;
|
|
1353
1440
|
nextSector += numMiniFATSectors;
|
|
@@ -1360,8 +1447,8 @@ function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
|
1360
1447
|
const totalSectors = nextSector;
|
|
1361
1448
|
const fat = new Int32Array(numFATSectors * (sectorSize / 4));
|
|
1362
1449
|
fat.fill(-1);
|
|
1363
|
-
for (const [,
|
|
1364
|
-
writeFatChain(fat,
|
|
1450
|
+
for (const [, chain2] of fatChains) {
|
|
1451
|
+
writeFatChain(fat, chain2);
|
|
1365
1452
|
}
|
|
1366
1453
|
writeFatChain(fat, { start: rootSectors[0] ?? 0, sectors: rootSectors });
|
|
1367
1454
|
writeFatRun(fat, firstDirSector, numDirSectors);
|
|
@@ -1378,8 +1465,8 @@ function buildOle2(streams2, rootClsid, miniStreamCutoff = 4096) {
|
|
|
1378
1465
|
if (miniStreams.length > 0) {
|
|
1379
1466
|
miniFat = new Int32Array(numMiniFATSectors * (sectorSize / 4));
|
|
1380
1467
|
miniFat.fill(-1);
|
|
1381
|
-
for (const [,
|
|
1382
|
-
writeFatChain(miniFat,
|
|
1468
|
+
for (const [, chain2] of miniFatChains) {
|
|
1469
|
+
writeFatChain(miniFat, chain2);
|
|
1383
1470
|
}
|
|
1384
1471
|
}
|
|
1385
1472
|
const fileSize = (totalSectors + 1) * sectorSize;
|
|
@@ -9495,7 +9582,7 @@ function createDocument() {
|
|
|
9495
9582
|
};
|
|
9496
9583
|
}
|
|
9497
9584
|
|
|
9498
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
9585
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-fib.js
|
|
9499
9586
|
var FC_LCB_INDEX = {
|
|
9500
9587
|
plcfbteChpx: 12,
|
|
9501
9588
|
plcfbtePapx: 13,
|
|
@@ -9509,8 +9596,13 @@ function readDocFib(wordDoc) {
|
|
|
9509
9596
|
if (wIdent !== 42476) {
|
|
9510
9597
|
throw new Error("Not a WordDocument FIB (bad wIdent)");
|
|
9511
9598
|
}
|
|
9599
|
+
const nFibBase = view.getUint16(2, true);
|
|
9600
|
+
if (![193, 217, 257, 268, 274].includes(nFibBase))
|
|
9601
|
+
throw new Error("Unsupported Word FIB version");
|
|
9512
9602
|
const flags1Offset = 10;
|
|
9513
9603
|
const flags1 = view.getUint16(flags1Offset, true);
|
|
9604
|
+
if ((flags1 & 33024) !== 0)
|
|
9605
|
+
throw new Error("Encrypted or obfuscated Word document");
|
|
9514
9606
|
const fWhichTblStm = flags1 >> 9 & 1;
|
|
9515
9607
|
const csw = view.getUint16(32, true);
|
|
9516
9608
|
const fibRgW97Offset = 34;
|
|
@@ -9519,6 +9611,21 @@ function readDocFib(wordDoc) {
|
|
|
9519
9611
|
const fibRgLw97Offset = cslwOffset + 2;
|
|
9520
9612
|
const cbRgFcLcbCountOffset = fibRgLw97Offset + cslw * 4;
|
|
9521
9613
|
const fibRgFcLcbOffset = cbRgFcLcbCountOffset + 2;
|
|
9614
|
+
const fibRgFcLcbCount = view.getUint16(cbRgFcLcbCountOffset, true);
|
|
9615
|
+
if (csw !== 14 || cslw !== 22 || fibRgFcLcbOffset + fibRgFcLcbCount * 8 + 2 > wordDoc.length)
|
|
9616
|
+
throw new Error("Truncated Word FIB arrays");
|
|
9617
|
+
const cswNewOffset = fibRgFcLcbOffset + fibRgFcLcbCount * 8;
|
|
9618
|
+
const cswNew = view.getUint16(cswNewOffset, true);
|
|
9619
|
+
if (cswNewOffset + 2 + cswNew * 2 > wordDoc.length)
|
|
9620
|
+
throw new Error("Truncated Word FIB extension");
|
|
9621
|
+
const nFib = cswNew === 0 ? nFibBase : view.getUint16(cswNewOffset + 2, true);
|
|
9622
|
+
const expectedCounts = /* @__PURE__ */ new Map([[193, 93], [217, 108], [257, 136], [268, 164], [274, 183]]);
|
|
9623
|
+
if (expectedCounts.get(nFib) !== fibRgFcLcbCount || cswNew !== 0 && cswNew !== (nFib === 274 ? 5 : 2))
|
|
9624
|
+
throw new Error("Word FIB version and array counts disagree");
|
|
9625
|
+
const ccpText = view.getInt32(fibRgLw97Offset + 12, true);
|
|
9626
|
+
const otherStories = [4, 5, 7, 8, 9, 10].map((i) => view.getInt32(fibRgLw97Offset + i * 4, true));
|
|
9627
|
+
if (ccpText < 0 || otherStories.some((cp) => cp < 0))
|
|
9628
|
+
throw new Error("Negative Word story length");
|
|
9522
9629
|
function fcLcbPair(index) {
|
|
9523
9630
|
const off = fibRgFcLcbOffset + index * 8;
|
|
9524
9631
|
return { fc: view.getUint32(off, true), lcb: view.getUint32(off + 4, true) };
|
|
@@ -9526,18 +9633,22 @@ function readDocFib(wordDoc) {
|
|
|
9526
9633
|
const cbMacOffset = fibRgLw97Offset + 0;
|
|
9527
9634
|
const ccpTextOffset = fibRgLw97Offset + 12;
|
|
9528
9635
|
return {
|
|
9636
|
+
nFib,
|
|
9637
|
+
nFibBase,
|
|
9529
9638
|
flags1Offset,
|
|
9530
9639
|
flags1,
|
|
9531
9640
|
tableStreamName: fWhichTblStm ? "1Table" : "0Table",
|
|
9532
9641
|
cbMacOffset,
|
|
9533
9642
|
cbMac: view.getUint32(cbMacOffset, true),
|
|
9534
9643
|
ccpTextOffset,
|
|
9535
|
-
ccpText
|
|
9644
|
+
ccpText,
|
|
9645
|
+
ccpOtherStories: otherStories.reduce((sum, cp) => sum + cp, 0),
|
|
9536
9646
|
plcfbteChpx: fcLcbPair(FC_LCB_INDEX.plcfbteChpx),
|
|
9537
9647
|
plcfbtePapx: fcLcbPair(FC_LCB_INDEX.plcfbtePapx),
|
|
9538
9648
|
sed: fcLcbPair(FC_LCB_INDEX.sed),
|
|
9539
9649
|
clx: fcLcbPair(FC_LCB_INDEX.clx),
|
|
9540
|
-
fibRgFcLcbOffset
|
|
9650
|
+
fibRgFcLcbOffset,
|
|
9651
|
+
fibRgFcLcbCount
|
|
9541
9652
|
};
|
|
9542
9653
|
}
|
|
9543
9654
|
function fcLcbFieldOffset(fib, key) {
|
|
@@ -9545,6 +9656,9 @@ function fcLcbFieldOffset(fib, key) {
|
|
|
9545
9656
|
}
|
|
9546
9657
|
function readFcLcbAt(wordDoc, fib, index) {
|
|
9547
9658
|
const view = new DataView(wordDoc.buffer, wordDoc.byteOffset, wordDoc.byteLength);
|
|
9659
|
+
const count = view.getUint16(fib.fibRgFcLcbOffset - 2, true);
|
|
9660
|
+
if (!Number.isInteger(index) || index < 0 || index >= count || fib.fibRgFcLcbCount !== void 0 && fib.fibRgFcLcbCount !== count)
|
|
9661
|
+
throw new Error("FIB fc/lcb index outside declared array");
|
|
9548
9662
|
const off = fib.fibRgFcLcbOffset + index * 8;
|
|
9549
9663
|
return { fc: view.getUint32(off, true), lcb: view.getUint32(off + 4, true) };
|
|
9550
9664
|
}
|
|
@@ -9564,40 +9678,799 @@ function patchDocFib(wordDoc, fib, patch) {
|
|
|
9564
9678
|
view.setUint32(papxOff + 4, patch.plcfbtePapx.lcb, true);
|
|
9565
9679
|
}
|
|
9566
9680
|
|
|
9567
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
9681
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-stream-edit.js
|
|
9682
|
+
var END = 4294967294;
|
|
9683
|
+
var MAX = 4294967290;
|
|
9684
|
+
var NIL = 4294967295;
|
|
9685
|
+
var SIZE = 128;
|
|
9686
|
+
function chain(table, start, limit) {
|
|
9687
|
+
if (start === END && limit === 0)
|
|
9688
|
+
return [];
|
|
9689
|
+
const out = [], seen = /* @__PURE__ */ new Set();
|
|
9690
|
+
let id = start;
|
|
9691
|
+
while (id !== END) {
|
|
9692
|
+
if (id > MAX || id >= table.length || seen.has(id) || out.length >= limit)
|
|
9693
|
+
throw new Error("Invalid CFB chain");
|
|
9694
|
+
seen.add(id);
|
|
9695
|
+
out.push(id);
|
|
9696
|
+
id = table[id];
|
|
9697
|
+
}
|
|
9698
|
+
if (out.length !== limit)
|
|
9699
|
+
throw new Error("Unexpected CFB chain length");
|
|
9700
|
+
return out;
|
|
9701
|
+
}
|
|
9702
|
+
function chainToEnd(table, start, max) {
|
|
9703
|
+
if (start === END)
|
|
9704
|
+
return [];
|
|
9705
|
+
const out = [], seen = /* @__PURE__ */ new Set();
|
|
9706
|
+
let id = start;
|
|
9707
|
+
while (id !== END) {
|
|
9708
|
+
if (id > MAX || id >= table.length || seen.has(id) || out.length >= max)
|
|
9709
|
+
throw new Error("Invalid CFB chain");
|
|
9710
|
+
seen.add(id);
|
|
9711
|
+
out.push(id);
|
|
9712
|
+
id = table[id];
|
|
9713
|
+
}
|
|
9714
|
+
return out;
|
|
9715
|
+
}
|
|
9716
|
+
function processCompoundFileStream(input, path, replacement) {
|
|
9717
|
+
const reject = () => replacement ? input : void 0;
|
|
9718
|
+
try {
|
|
9719
|
+
if (path.length === 0 || path.some((part) => !part) || input.length < 512)
|
|
9720
|
+
return reject();
|
|
9721
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
9722
|
+
const read32 = (offset2) => view.getUint32(offset2, true);
|
|
9723
|
+
if (input[0] !== 208 || input[1] !== 207 || input[2] !== 17 || input[3] !== 224 || input[4] !== 161 || input[5] !== 177 || input[6] !== 26 || input[7] !== 225 || view.getUint16(28, true) !== 65534)
|
|
9724
|
+
return reject();
|
|
9725
|
+
const major = view.getUint16(26, true), shift = view.getUint16(30, true), miniShift = view.getUint16(32, true);
|
|
9726
|
+
if (major !== 3 && major !== 4 || shift !== (major === 3 ? 9 : 12) || miniShift !== 6)
|
|
9727
|
+
return reject();
|
|
9728
|
+
const sectorSize = 1 << shift, miniSize = 1 << miniShift;
|
|
9729
|
+
if (input.length % sectorSize !== 0)
|
|
9730
|
+
return reject();
|
|
9731
|
+
const sectorCount = input.length / sectorSize - 1;
|
|
9732
|
+
const offset = (id) => (id + 1) * sectorSize;
|
|
9733
|
+
const sector = (id) => {
|
|
9734
|
+
if (id > MAX || id >= sectorCount)
|
|
9735
|
+
throw new Error("Invalid sector");
|
|
9736
|
+
return input.subarray(offset(id), offset(id) + sectorSize);
|
|
9737
|
+
};
|
|
9738
|
+
const fatCount = read32(44), difatCount = read32(72), difatStart = read32(68);
|
|
9739
|
+
const fatIds = [];
|
|
9740
|
+
for (let i = 0; i < 109 && fatIds.length < fatCount; i++) {
|
|
9741
|
+
const id = read32(76 + 4 * i);
|
|
9742
|
+
if (id <= MAX)
|
|
9743
|
+
fatIds.push(id);
|
|
9744
|
+
}
|
|
9745
|
+
let difat = difatStart;
|
|
9746
|
+
const difatIds = /* @__PURE__ */ new Set();
|
|
9747
|
+
for (let i = 0; i < difatCount; i++) {
|
|
9748
|
+
if (difatIds.has(difat) || difat > MAX)
|
|
9749
|
+
return reject();
|
|
9750
|
+
difatIds.add(difat);
|
|
9751
|
+
const bytes = sector(difat), dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
9752
|
+
for (let j = 0; j < (sectorSize - 4) / 4 && fatIds.length < fatCount; j++) {
|
|
9753
|
+
const id = dv.getUint32(j * 4, true);
|
|
9754
|
+
if (id <= MAX)
|
|
9755
|
+
fatIds.push(id);
|
|
9756
|
+
}
|
|
9757
|
+
difat = dv.getUint32(sectorSize - 4, true);
|
|
9758
|
+
}
|
|
9759
|
+
if (fatIds.length !== fatCount || new Set(fatIds).size !== fatIds.length || fatIds.some((id) => difatIds.has(id)))
|
|
9760
|
+
return reject();
|
|
9761
|
+
const fat = [];
|
|
9762
|
+
for (const id of fatIds) {
|
|
9763
|
+
const bytes = sector(id), dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
9764
|
+
for (let j = 0; j < sectorSize / 4 && fat.length < sectorCount; j++)
|
|
9765
|
+
fat.push(dv.getUint32(j * 4, true));
|
|
9766
|
+
}
|
|
9767
|
+
const exactChain = (table, start, bytes, unit) => chain(table, start, Math.ceil(bytes / unit));
|
|
9768
|
+
const dirStart = read32(48), dirSectors = chainToEnd(fat, dirStart, sectorCount);
|
|
9769
|
+
const dirBytes = new Uint8Array(dirSectors.length * sectorSize);
|
|
9770
|
+
dirSectors.forEach((id, i) => dirBytes.set(sector(id), i * sectorSize));
|
|
9771
|
+
const dvDir = new DataView(dirBytes.buffer);
|
|
9772
|
+
const entries = [];
|
|
9773
|
+
for (let p = 0, id = 0; p + SIZE <= dirBytes.length; p += SIZE, id++) {
|
|
9774
|
+
const type = dirBytes[p + 66];
|
|
9775
|
+
if (type === 0)
|
|
9776
|
+
continue;
|
|
9777
|
+
const nameLength = dvDir.getUint16(p + 64, true);
|
|
9778
|
+
if (nameLength < 2 || nameLength > 64 || nameLength % 2)
|
|
9779
|
+
return reject();
|
|
9780
|
+
let name = "";
|
|
9781
|
+
for (let i = 0; i < nameLength - 2; i += 2)
|
|
9782
|
+
name += String.fromCharCode(dvDir.getUint16(p + i, true));
|
|
9783
|
+
const sizeLow = dvDir.getUint32(p + 120, true), sizeHigh = dvDir.getUint32(p + 124, true);
|
|
9784
|
+
if (sizeHigh !== 0)
|
|
9785
|
+
return reject();
|
|
9786
|
+
entries.push({
|
|
9787
|
+
id,
|
|
9788
|
+
name,
|
|
9789
|
+
type,
|
|
9790
|
+
left: dvDir.getUint32(p + 68, true),
|
|
9791
|
+
right: dvDir.getUint32(p + 72, true),
|
|
9792
|
+
child: dvDir.getUint32(p + 76, true),
|
|
9793
|
+
start: dvDir.getUint32(p + 116, true),
|
|
9794
|
+
size: sizeLow
|
|
9795
|
+
});
|
|
9796
|
+
}
|
|
9797
|
+
const byId = new Map(entries.map((e) => [e.id, e]));
|
|
9798
|
+
const root = entries.find((e) => e.type === 5);
|
|
9799
|
+
if (!root)
|
|
9800
|
+
return reject();
|
|
9801
|
+
let parent = root, target;
|
|
9802
|
+
for (let partIndex = 0; partIndex < path.length; partIndex++) {
|
|
9803
|
+
const matches = [], seen = /* @__PURE__ */ new Set();
|
|
9804
|
+
const visit = (id) => {
|
|
9805
|
+
if (id === NIL)
|
|
9806
|
+
return;
|
|
9807
|
+
if (seen.has(id))
|
|
9808
|
+
throw new Error("Cyclic directory tree");
|
|
9809
|
+
seen.add(id);
|
|
9810
|
+
const e = byId.get(id);
|
|
9811
|
+
if (!e)
|
|
9812
|
+
throw new Error("Invalid directory ID");
|
|
9813
|
+
visit(e.left);
|
|
9814
|
+
if (e.name.toLocaleLowerCase() === path[partIndex].toLocaleLowerCase())
|
|
9815
|
+
matches.push(e);
|
|
9816
|
+
visit(e.right);
|
|
9817
|
+
};
|
|
9818
|
+
visit(parent.child);
|
|
9819
|
+
if (matches.length !== 1)
|
|
9820
|
+
return reject();
|
|
9821
|
+
target = matches[0];
|
|
9822
|
+
if (partIndex < path.length - 1) {
|
|
9823
|
+
if (target.type !== 1)
|
|
9824
|
+
return reject();
|
|
9825
|
+
parent = target;
|
|
9826
|
+
} else if (target.type !== 2 || replacement && replacement.length !== target.size)
|
|
9827
|
+
return reject();
|
|
9828
|
+
}
|
|
9829
|
+
if (!target)
|
|
9830
|
+
return reject();
|
|
9831
|
+
const names = entries.map((e) => e.name.toLowerCase());
|
|
9832
|
+
if (replacement && (names.includes("encryptioninfo") || names.includes("encryptedpackage")))
|
|
9833
|
+
return reject();
|
|
9834
|
+
for (const name of replacement ? ["book", "workbook"] : []) {
|
|
9835
|
+
if (!names.includes(name))
|
|
9836
|
+
continue;
|
|
9837
|
+
const workbook = readCompoundFileStream(input, [name]);
|
|
9838
|
+
if (!workbook)
|
|
9839
|
+
continue;
|
|
9840
|
+
for (let p = 0; p + 4 <= workbook.length; ) {
|
|
9841
|
+
const record = workbook[p] | workbook[p + 1] << 8;
|
|
9842
|
+
const length = workbook[p + 2] | workbook[p + 3] << 8;
|
|
9843
|
+
if (record === 47)
|
|
9844
|
+
return reject();
|
|
9845
|
+
if (p + 4 + length > workbook.length || record === 10)
|
|
9846
|
+
break;
|
|
9847
|
+
p += 4 + length;
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
if (replacement && names.includes("worddocument")) {
|
|
9851
|
+
const word = readCompoundFileStream(input, ["WordDocument"]);
|
|
9852
|
+
if (word && word.length >= 12 && ((word[10] | word[11] << 8) & 256) !== 0)
|
|
9853
|
+
return reject();
|
|
9854
|
+
}
|
|
9855
|
+
const miniCutoff = read32(56), miniFatCount = read32(64), miniFatStart = read32(60);
|
|
9856
|
+
const miniFatSectors = miniFatCount ? chain(fat, miniFatStart, miniFatCount) : [];
|
|
9857
|
+
const metadataSectors = [...fatIds, ...difatIds, ...dirSectors, ...miniFatSectors];
|
|
9858
|
+
if (new Set(metadataSectors).size !== metadataSectors.length)
|
|
9859
|
+
return reject();
|
|
9860
|
+
const miniFat = [];
|
|
9861
|
+
const miniCapacity = Math.floor(Math.min(root.size, sectorCount * sectorSize) / miniSize);
|
|
9862
|
+
for (const id of miniFatSectors) {
|
|
9863
|
+
const bytes = sector(id), dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
9864
|
+
for (let j = 0; j < sectorSize / 4 && miniFat.length < miniCapacity; j++)
|
|
9865
|
+
miniFat.push(dv.getUint32(j * 4, true));
|
|
9866
|
+
}
|
|
9867
|
+
const reserved = /* @__PURE__ */ new Set([...fatIds, ...difatIds, ...dirSectors, ...miniFatSectors]);
|
|
9868
|
+
const regularUsed = new Set(reserved);
|
|
9869
|
+
const rootMini = entries.find((e) => e.type === 5);
|
|
9870
|
+
for (const entry of entries) {
|
|
9871
|
+
if (entry.type !== 2 && entry.type !== 5 || entry.size === 0)
|
|
9872
|
+
continue;
|
|
9873
|
+
if (entry.type === 2 && entry.size < miniCutoff)
|
|
9874
|
+
continue;
|
|
9875
|
+
for (const id of exactChain(fat, entry.start, entry.size, sectorSize)) {
|
|
9876
|
+
sector(id);
|
|
9877
|
+
if (regularUsed.has(id))
|
|
9878
|
+
return reject();
|
|
9879
|
+
regularUsed.add(id);
|
|
9880
|
+
}
|
|
9881
|
+
}
|
|
9882
|
+
const miniUsed = /* @__PURE__ */ new Set();
|
|
9883
|
+
for (const entry of entries) {
|
|
9884
|
+
if (entry.type !== 2 || entry.size === 0 || entry.size >= miniCutoff)
|
|
9885
|
+
continue;
|
|
9886
|
+
const ids = exactChain(miniFat, entry.start, entry.size, miniSize);
|
|
9887
|
+
for (const id of ids) {
|
|
9888
|
+
if ((id + 1) * miniSize > rootMini.size || miniUsed.has(id))
|
|
9889
|
+
return reject();
|
|
9890
|
+
miniUsed.add(id);
|
|
9891
|
+
}
|
|
9892
|
+
}
|
|
9893
|
+
if (!replacement) {
|
|
9894
|
+
if (target.size >= miniCutoff) {
|
|
9895
|
+
const ids2 = exactChain(fat, target.start, target.size, sectorSize);
|
|
9896
|
+
const result2 = new Uint8Array(ids2.length * sectorSize);
|
|
9897
|
+
ids2.forEach((id, i) => result2.set(sector(id), i * sectorSize));
|
|
9898
|
+
return result2.slice(0, target.size);
|
|
9899
|
+
}
|
|
9900
|
+
const ids = exactChain(miniFat, target.start, target.size, miniSize);
|
|
9901
|
+
const rootSectors = exactChain(fat, rootMini.start, rootMini.size, sectorSize);
|
|
9902
|
+
const result = new Uint8Array(ids.length * miniSize);
|
|
9903
|
+
ids.forEach((id, i) => {
|
|
9904
|
+
const start = id * miniSize;
|
|
9905
|
+
if (start + miniSize > rootMini.size)
|
|
9906
|
+
throw new Error("Mini stream out of bounds");
|
|
9907
|
+
const rootSector = rootSectors[Math.floor(start / sectorSize)];
|
|
9908
|
+
const source = sector(rootSector).subarray(start % sectorSize, start % sectorSize + miniSize);
|
|
9909
|
+
result.set(source, i * miniSize);
|
|
9910
|
+
});
|
|
9911
|
+
return result.slice(0, target.size);
|
|
9912
|
+
}
|
|
9913
|
+
if (replacement.length === 0)
|
|
9914
|
+
return input.slice();
|
|
9915
|
+
const output = input.slice();
|
|
9916
|
+
if (target.size >= miniCutoff) {
|
|
9917
|
+
const sectors = exactChain(fat, target.start, target.size, sectorSize);
|
|
9918
|
+
sectors.forEach((id) => sector(id));
|
|
9919
|
+
for (let i = 0; i < sectors.length; i++)
|
|
9920
|
+
output.set(replacement.subarray(i * sectorSize, Math.min((i + 1) * sectorSize, replacement.length)), offset(sectors[i]));
|
|
9921
|
+
} else {
|
|
9922
|
+
if (!miniFat.length)
|
|
9923
|
+
return reject();
|
|
9924
|
+
const miniSectors = exactChain(miniFat, target.start, target.size, miniSize);
|
|
9925
|
+
const rootMini2 = entries.find((e) => e.type === 5);
|
|
9926
|
+
const rootSectors = exactChain(fat, rootMini2.start, rootMini2.size, sectorSize);
|
|
9927
|
+
rootSectors.forEach((id) => sector(id));
|
|
9928
|
+
const rootData = new Uint8Array(rootMini2.size);
|
|
9929
|
+
rootSectors.forEach((id, i) => rootData.set(sector(id).subarray(0, Math.min(sectorSize, rootData.length - i * sectorSize)), i * sectorSize));
|
|
9930
|
+
for (let i = 0; i < replacement.length; i++) {
|
|
9931
|
+
const miniOffset = miniSectors[Math.floor(i / miniSize)] * miniSize + i % miniSize;
|
|
9932
|
+
if (miniOffset >= rootData.length)
|
|
9933
|
+
return reject();
|
|
9934
|
+
const rootSectorIndex = Math.floor(miniOffset / sectorSize);
|
|
9935
|
+
const rootSector = rootSectors[rootSectorIndex];
|
|
9936
|
+
if (rootSector === void 0)
|
|
9937
|
+
return reject();
|
|
9938
|
+
const physical = offset(rootSector) + miniOffset % sectorSize;
|
|
9939
|
+
output[physical] = replacement[i];
|
|
9940
|
+
}
|
|
9941
|
+
}
|
|
9942
|
+
return output;
|
|
9943
|
+
} catch {
|
|
9944
|
+
return reject();
|
|
9945
|
+
}
|
|
9946
|
+
}
|
|
9947
|
+
function readCompoundFileStream(input, path) {
|
|
9948
|
+
return processCompoundFileStream(input, path);
|
|
9949
|
+
}
|
|
9950
|
+
function replaceCompoundFileStream(input, path, replacement) {
|
|
9951
|
+
return processCompoundFileStream(input, path, replacement) ?? input;
|
|
9952
|
+
}
|
|
9953
|
+
|
|
9954
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-stream-resize-v4.js
|
|
9955
|
+
var END2 = 4294967294;
|
|
9956
|
+
var FREE = 4294967295;
|
|
9957
|
+
var FAT = 4294967293;
|
|
9958
|
+
function follow(table, start) {
|
|
9959
|
+
const ids = [], seen = /* @__PURE__ */ new Set();
|
|
9960
|
+
for (let id = start; id !== END2; id = table[id]) {
|
|
9961
|
+
if (id >= table.length || seen.has(id))
|
|
9962
|
+
throw new Error("Invalid allocation chain");
|
|
9963
|
+
seen.add(id);
|
|
9964
|
+
ids.push(id);
|
|
9965
|
+
}
|
|
9966
|
+
return ids;
|
|
9967
|
+
}
|
|
9968
|
+
function findSlot(slots, path) {
|
|
9969
|
+
let parent = slots.get(0);
|
|
9970
|
+
if (!parent || parent.type !== 5)
|
|
9971
|
+
throw new Error("Invalid root");
|
|
9972
|
+
const parents = /* @__PURE__ */ new Set();
|
|
9973
|
+
for (let part = 0; part < path.length; part++) {
|
|
9974
|
+
if (parents.has(parent.id))
|
|
9975
|
+
throw new Error("Cyclic storage hierarchy");
|
|
9976
|
+
parents.add(parent.id);
|
|
9977
|
+
const stack = [parent.child], seen = /* @__PURE__ */ new Set(), matches = [];
|
|
9978
|
+
while (stack.length) {
|
|
9979
|
+
const id = stack.pop();
|
|
9980
|
+
if (id === FREE)
|
|
9981
|
+
continue;
|
|
9982
|
+
if (seen.has(id))
|
|
9983
|
+
throw new Error("Invalid directory tree");
|
|
9984
|
+
seen.add(id);
|
|
9985
|
+
const slot2 = slots.get(id);
|
|
9986
|
+
if (!slot2)
|
|
9987
|
+
throw new Error("Missing directory slot");
|
|
9988
|
+
if (slot2.name.toLocaleLowerCase() === path[part].toLocaleLowerCase())
|
|
9989
|
+
matches.push(slot2);
|
|
9990
|
+
stack.push(slot2.left, slot2.right);
|
|
9991
|
+
}
|
|
9992
|
+
if (matches.length !== 1)
|
|
9993
|
+
throw new Error("Missing or ambiguous stream");
|
|
9994
|
+
parent = matches[0];
|
|
9995
|
+
if (parent.type !== (part === path.length - 1 ? 2 : 1))
|
|
9996
|
+
throw new Error("Invalid storage path");
|
|
9997
|
+
}
|
|
9998
|
+
return parent;
|
|
9999
|
+
}
|
|
10000
|
+
function validateHierarchy(slots) {
|
|
10001
|
+
const owners = /* @__PURE__ */ new Set(), storages = [0];
|
|
10002
|
+
while (storages.length) {
|
|
10003
|
+
const storage = slots.get(storages.pop());
|
|
10004
|
+
const stack = [storage.child], names = /* @__PURE__ */ new Set();
|
|
10005
|
+
while (stack.length) {
|
|
10006
|
+
const id = stack.pop();
|
|
10007
|
+
if (id === FREE)
|
|
10008
|
+
continue;
|
|
10009
|
+
const slot2 = slots.get(id);
|
|
10010
|
+
if (!slot2 || slot2.type !== 1 && slot2.type !== 2 || owners.has(id))
|
|
10011
|
+
throw new Error("Ambiguous directory ownership");
|
|
10012
|
+
const name = slot2.name.toLocaleLowerCase();
|
|
10013
|
+
if (names.has(name))
|
|
10014
|
+
throw new Error("Duplicate sibling name");
|
|
10015
|
+
names.add(name);
|
|
10016
|
+
owners.add(id);
|
|
10017
|
+
stack.push(slot2.left, slot2.right);
|
|
10018
|
+
if (slot2.type === 1)
|
|
10019
|
+
storages.push(id);
|
|
10020
|
+
}
|
|
10021
|
+
}
|
|
10022
|
+
if (owners.size !== slots.size - 1)
|
|
10023
|
+
throw new Error("Unreachable directory entries");
|
|
10024
|
+
}
|
|
10025
|
+
function resizeCompoundFileStreamV4(input, path, replacement) {
|
|
10026
|
+
const fail = (reason) => ({ ok: false, bytes: input, reason });
|
|
10027
|
+
try {
|
|
10028
|
+
if (input.length < 512 || !path.length || path.some((part) => !part))
|
|
10029
|
+
return fail("invalid-or-missing-stream");
|
|
10030
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
10031
|
+
const read = (offset) => view.getUint32(offset, true);
|
|
10032
|
+
const version = view.getUint16(26, true);
|
|
10033
|
+
if (version !== 4)
|
|
10034
|
+
return fail("unsupported-version");
|
|
10035
|
+
const SECTOR3 = 4096, FAT_ENTRIES2 = SECTOR3 / 4, DIRECTORY_ENTRIES = SECTOR3 / 128;
|
|
10036
|
+
if (read(72) !== 0 || read(44) > 109)
|
|
10037
|
+
return fail("unsupported-difat");
|
|
10038
|
+
const cutoff = read(56);
|
|
10039
|
+
if (cutoff !== 0 && cutoff !== 4096)
|
|
10040
|
+
return fail("unsupported-layout");
|
|
10041
|
+
const original = readCompoundFileStream(input, path);
|
|
10042
|
+
if (!original)
|
|
10043
|
+
return fail("invalid-or-missing-stream");
|
|
10044
|
+
const name = path[path.length - 1].toLocaleLowerCase();
|
|
10045
|
+
if (name === "worddocument" && original.length >= 12 && (original[10] | original[11] << 8) & 33024)
|
|
10046
|
+
return fail("unsafe-edit");
|
|
10047
|
+
if (name === "book" || name === "workbook") {
|
|
10048
|
+
for (let offset = 0; offset + 4 <= original.length; ) {
|
|
10049
|
+
const record = original[offset] | original[offset + 1] << 8;
|
|
10050
|
+
const length = original[offset + 2] | original[offset + 3] << 8;
|
|
10051
|
+
if (record === 47)
|
|
10052
|
+
return fail("unsafe-edit");
|
|
10053
|
+
if (record === 10 || offset + 4 + length > original.length)
|
|
10054
|
+
break;
|
|
10055
|
+
offset += 4 + length;
|
|
10056
|
+
}
|
|
10057
|
+
}
|
|
10058
|
+
const validated = replaceCompoundFileStream(input, path, original);
|
|
10059
|
+
if (validated === input)
|
|
10060
|
+
return fail("unsafe-edit");
|
|
10061
|
+
const sameLength = replacement.length === original.length;
|
|
10062
|
+
if (!sameLength && original.length < cutoff)
|
|
10063
|
+
return fail("unsupported-mini-stream");
|
|
10064
|
+
if (!sameLength && replacement.length < cutoff)
|
|
10065
|
+
return fail("unsupported-mini-transition");
|
|
10066
|
+
const sectorCount = input.length / SECTOR3 - 1, fatCount = read(44);
|
|
10067
|
+
if (sectorCount > fatCount * FAT_ENTRIES2)
|
|
10068
|
+
return fail("unsafe-edit");
|
|
10069
|
+
const fatIds = Array.from({ length: fatCount }, (_, i) => read(76 + 4 * i));
|
|
10070
|
+
const fat = [];
|
|
10071
|
+
for (const id of fatIds) {
|
|
10072
|
+
for (let i = 0; i < FAT_ENTRIES2 && fat.length < sectorCount; i++)
|
|
10073
|
+
fat.push(read((id + 1) * SECTOR3 + i * 4));
|
|
10074
|
+
}
|
|
10075
|
+
if (fatIds.some((id) => fat[id] !== FAT))
|
|
10076
|
+
return fail("unsafe-edit");
|
|
10077
|
+
const slots = /* @__PURE__ */ new Map();
|
|
10078
|
+
const directoryChain = follow(fat, read(48));
|
|
10079
|
+
if (read(40) !== directoryChain.length)
|
|
10080
|
+
return fail("unsafe-edit");
|
|
10081
|
+
for (const [block, sector] of directoryChain.entries()) {
|
|
10082
|
+
for (let entry = 0; entry < DIRECTORY_ENTRIES; entry++) {
|
|
10083
|
+
const offset = (sector + 1) * SECTOR3 + entry * 128, type = view.getUint8(offset + 66);
|
|
10084
|
+
if (!type)
|
|
10085
|
+
continue;
|
|
10086
|
+
const nameLength = view.getUint16(offset + 64, true);
|
|
10087
|
+
if (![1, 2, 5].includes(type) || nameLength < 2 || nameLength > 64 || nameLength % 2 || view.getUint16(offset + nameLength - 2, true))
|
|
10088
|
+
return fail("unsafe-edit");
|
|
10089
|
+
let name2 = "";
|
|
10090
|
+
for (let i = 0; i < nameLength - 2; i += 2)
|
|
10091
|
+
name2 += String.fromCharCode(view.getUint16(offset + i, true));
|
|
10092
|
+
if (read(offset + 124) !== 0)
|
|
10093
|
+
return fail("unsafe-edit");
|
|
10094
|
+
const id = block * DIRECTORY_ENTRIES + entry;
|
|
10095
|
+
slots.set(id, { id, name: name2, type, left: read(offset + 68), right: read(offset + 72), child: read(offset + 76), start: read(offset + 116), size: read(offset + 120), offset });
|
|
10096
|
+
}
|
|
10097
|
+
}
|
|
10098
|
+
if ([...slots.values()].filter((slot2) => slot2.type === 5).length !== 1)
|
|
10099
|
+
return fail("unsafe-edit");
|
|
10100
|
+
const target = findSlot(slots, path);
|
|
10101
|
+
validateHierarchy(slots);
|
|
10102
|
+
if (sameLength) {
|
|
10103
|
+
const bytes = replaceCompoundFileStream(input, path, replacement);
|
|
10104
|
+
return bytes === input ? fail("unsafe-edit") : { ok: true, bytes };
|
|
10105
|
+
}
|
|
10106
|
+
const oldChain = follow(fat, target.start);
|
|
10107
|
+
if (target.size !== original.length || oldChain.length !== Math.ceil(target.size / SECTOR3))
|
|
10108
|
+
return fail("unsafe-edit");
|
|
10109
|
+
const payloadSectors = Math.ceil(replacement.length / SECTOR3);
|
|
10110
|
+
let newFatCount = fatCount;
|
|
10111
|
+
while (sectorCount + payloadSectors + newFatCount - fatCount > newFatCount * FAT_ENTRIES2)
|
|
10112
|
+
newFatCount++;
|
|
10113
|
+
if (newFatCount > 109)
|
|
10114
|
+
return fail("allocation-limit");
|
|
10115
|
+
const totalSectors = sectorCount + payloadSectors + newFatCount - fatCount;
|
|
10116
|
+
const output = new Uint8Array((totalSectors + 1) * SECTOR3);
|
|
10117
|
+
output.set(input);
|
|
10118
|
+
output.set(replacement, (sectorCount + 1) * SECTOR3);
|
|
10119
|
+
const outView = new DataView(output.buffer);
|
|
10120
|
+
for (let i = fatCount; i < newFatCount; i++) {
|
|
10121
|
+
const id = sectorCount + payloadSectors + i - fatCount;
|
|
10122
|
+
fatIds.push(id);
|
|
10123
|
+
output.fill(255, (id + 1) * SECTOR3, (id + 2) * SECTOR3);
|
|
10124
|
+
outView.setUint32(76 + i * 4, id, true);
|
|
10125
|
+
}
|
|
10126
|
+
outView.setUint32(44, newFatCount, true);
|
|
10127
|
+
const writeFat = (id, value) => outView.setUint32((fatIds[Math.floor(id / FAT_ENTRIES2)] + 1) * SECTOR3 + id % FAT_ENTRIES2 * 4, value, true);
|
|
10128
|
+
for (const id of oldChain)
|
|
10129
|
+
writeFat(id, FREE);
|
|
10130
|
+
for (let i = 0; i < payloadSectors; i++)
|
|
10131
|
+
writeFat(sectorCount + i, i + 1 === payloadSectors ? END2 : sectorCount + i + 1);
|
|
10132
|
+
for (const id of fatIds)
|
|
10133
|
+
writeFat(id, FAT);
|
|
10134
|
+
outView.setUint32(target.offset + 116, payloadSectors ? sectorCount : END2, true);
|
|
10135
|
+
outView.setUint32(target.offset + 120, replacement.length, true);
|
|
10136
|
+
return { ok: true, bytes: output };
|
|
10137
|
+
} catch {
|
|
10138
|
+
return fail("unsafe-edit");
|
|
10139
|
+
}
|
|
10140
|
+
}
|
|
10141
|
+
|
|
10142
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-stream-resize-allocation.js
|
|
10143
|
+
function appendCompoundAllocations(input, existingFatIds, payloads, sectorSize = 512) {
|
|
10144
|
+
const sectorCount = input.length / sectorSize - 1;
|
|
10145
|
+
const entriesPerFat = sectorSize / 4;
|
|
10146
|
+
const counts = payloads.map((payload) => Math.ceil(payload.length / sectorSize));
|
|
10147
|
+
const payloadSectors = counts.reduce((sum, count) => sum + count, 0);
|
|
10148
|
+
let fatCount = existingFatIds.length;
|
|
10149
|
+
while (sectorCount + payloadSectors + fatCount - existingFatIds.length > fatCount * entriesPerFat)
|
|
10150
|
+
fatCount++;
|
|
10151
|
+
if (fatCount > 109)
|
|
10152
|
+
return void 0;
|
|
10153
|
+
const totalSectors = sectorCount + payloadSectors + fatCount - existingFatIds.length;
|
|
10154
|
+
const bytes = new Uint8Array((totalSectors + 1) * sectorSize);
|
|
10155
|
+
bytes.set(input);
|
|
10156
|
+
const view = new DataView(bytes.buffer), fatIds = [...existingFatIds];
|
|
10157
|
+
for (let i = existingFatIds.length; i < fatCount; i++) {
|
|
10158
|
+
const id = sectorCount + payloadSectors + i - existingFatIds.length;
|
|
10159
|
+
fatIds.push(id);
|
|
10160
|
+
bytes.fill(255, (id + 1) * sectorSize, (id + 2) * sectorSize);
|
|
10161
|
+
view.setUint32(76 + i * 4, id, true);
|
|
10162
|
+
}
|
|
10163
|
+
view.setUint32(44, fatCount, true);
|
|
10164
|
+
const writeFat = (id, value) => view.setUint32((fatIds[Math.floor(id / entriesPerFat)] + 1) * sectorSize + id % entriesPerFat * 4, value, true);
|
|
10165
|
+
const chains = [];
|
|
10166
|
+
let next = sectorCount;
|
|
10167
|
+
for (let payload = 0; payload < payloads.length; payload++) {
|
|
10168
|
+
bytes.set(payloads[payload], (next + 1) * sectorSize);
|
|
10169
|
+
const chain2 = Array.from({ length: counts[payload] }, (_, i) => next + i);
|
|
10170
|
+
chain2.forEach((id, i) => writeFat(id, chain2[i + 1] ?? 4294967294));
|
|
10171
|
+
chains.push(chain2);
|
|
10172
|
+
next += counts[payload];
|
|
10173
|
+
}
|
|
10174
|
+
for (const id of fatIds)
|
|
10175
|
+
writeFat(id, 4294967293);
|
|
10176
|
+
return { bytes, view, chains, writeFat };
|
|
10177
|
+
}
|
|
10178
|
+
function followCompoundAllocation(table, start) {
|
|
10179
|
+
const ids = [], seen = /* @__PURE__ */ new Set();
|
|
10180
|
+
for (let id = start; id !== 4294967294; id = table[id]) {
|
|
10181
|
+
if (id >= table.length || seen.has(id))
|
|
10182
|
+
throw new Error("Invalid allocation chain");
|
|
10183
|
+
seen.add(id);
|
|
10184
|
+
ids.push(id);
|
|
10185
|
+
}
|
|
10186
|
+
return ids;
|
|
10187
|
+
}
|
|
10188
|
+
|
|
10189
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-stream-resize-mini.js
|
|
10190
|
+
var END3 = 4294967294;
|
|
10191
|
+
var FREE2 = 4294967295;
|
|
10192
|
+
var SECTOR = 512;
|
|
10193
|
+
var MINI = 64;
|
|
10194
|
+
function resizeMiniCompoundStream(input, fat, fatIds, root, target, replacement, cutoff) {
|
|
10195
|
+
const inputView = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
10196
|
+
const miniFatCount = inputView.getUint32(64, true);
|
|
10197
|
+
const miniFatSectors = miniFatCount ? followCompoundAllocation(fat, inputView.getUint32(60, true)) : [];
|
|
10198
|
+
if (miniFatSectors.length !== miniFatCount)
|
|
10199
|
+
throw new Error("Invalid mini FAT count");
|
|
10200
|
+
const rootSectors = root.size ? followCompoundAllocation(fat, root.start) : [];
|
|
10201
|
+
if (rootSectors.length !== Math.ceil(root.size / SECTOR))
|
|
10202
|
+
throw new Error("Invalid root mini stream");
|
|
10203
|
+
const miniCapacity = Math.floor(root.size / MINI), miniFat = [];
|
|
10204
|
+
for (const id of miniFatSectors) {
|
|
10205
|
+
for (let slot2 = 0; slot2 < SECTOR / 4 && miniFat.length < miniCapacity; slot2++)
|
|
10206
|
+
miniFat.push(inputView.getUint32((id + 1) * SECTOR + slot2 * 4, true));
|
|
10207
|
+
}
|
|
10208
|
+
const wasMini = target.size < cutoff;
|
|
10209
|
+
const oldChain = followCompoundAllocation(wasMini ? miniFat : fat, target.start);
|
|
10210
|
+
if (oldChain.length !== Math.ceil(target.size / (wasMini ? MINI : SECTOR)))
|
|
10211
|
+
throw new Error("Invalid stream allocation");
|
|
10212
|
+
if (!replacement.length) {
|
|
10213
|
+
const bytes = input.slice(), view = new DataView(bytes.buffer);
|
|
10214
|
+
for (const id of oldChain) {
|
|
10215
|
+
const table = wasMini ? miniFatSectors : fatIds;
|
|
10216
|
+
view.setUint32((table[Math.floor(id / 128)] + 1) * SECTOR + id % 128 * 4, FREE2, true);
|
|
10217
|
+
}
|
|
10218
|
+
view.setUint32(target.offset + 116, END3, true);
|
|
10219
|
+
view.setUint32(target.offset + 120, 0, true);
|
|
10220
|
+
return bytes;
|
|
10221
|
+
}
|
|
10222
|
+
if (replacement.length >= cutoff) {
|
|
10223
|
+
const output2 = appendCompoundAllocations(input, fatIds, [replacement]);
|
|
10224
|
+
if (!output2)
|
|
10225
|
+
return void 0;
|
|
10226
|
+
for (const id of oldChain)
|
|
10227
|
+
output2.view.setUint32((miniFatSectors[Math.floor(id / 128)] + 1) * SECTOR + id % 128 * 4, FREE2, true);
|
|
10228
|
+
output2.view.setUint32(target.offset + 116, output2.chains[0][0] ?? END3, true);
|
|
10229
|
+
output2.view.setUint32(target.offset + 120, replacement.length, true);
|
|
10230
|
+
return output2.bytes;
|
|
10231
|
+
}
|
|
10232
|
+
const firstMini = Math.ceil(root.size / MINI), count = Math.ceil(replacement.length / MINI);
|
|
10233
|
+
const newRootSize = count ? (firstMini + count) * MINI : root.size;
|
|
10234
|
+
const newRoot = new Uint8Array(newRootSize);
|
|
10235
|
+
rootSectors.forEach((id, i) => newRoot.set(input.subarray((id + 1) * SECTOR, (id + 1) * SECTOR + Math.min(SECTOR, root.size - i * SECTOR)), i * SECTOR));
|
|
10236
|
+
if (count)
|
|
10237
|
+
newRoot.set(replacement, firstMini * MINI);
|
|
10238
|
+
const newMiniFatCount = Math.max(miniFatCount, Math.ceil((firstMini + count) / 128));
|
|
10239
|
+
const newMiniFat = new Uint8Array(newMiniFatCount * SECTOR).fill(255);
|
|
10240
|
+
miniFatSectors.forEach((id, i) => newMiniFat.set(input.subarray((id + 1) * SECTOR, (id + 2) * SECTOR), i * SECTOR));
|
|
10241
|
+
const miniView = new DataView(newMiniFat.buffer);
|
|
10242
|
+
if (wasMini)
|
|
10243
|
+
for (const id of oldChain)
|
|
10244
|
+
miniView.setUint32(id * 4, FREE2, true);
|
|
10245
|
+
for (let i = 0; i < count; i++)
|
|
10246
|
+
miniView.setUint32((firstMini + i) * 4, i + 1 === count ? END3 : firstMini + i + 1, true);
|
|
10247
|
+
const output = appendCompoundAllocations(input, fatIds, [newRoot, newMiniFat]);
|
|
10248
|
+
if (!output)
|
|
10249
|
+
return void 0;
|
|
10250
|
+
for (const id of [...rootSectors, ...miniFatSectors, ...wasMini ? [] : oldChain])
|
|
10251
|
+
output.writeFat(id, FREE2);
|
|
10252
|
+
output.view.setUint32(root.offset + 116, output.chains[0][0] ?? END3, true);
|
|
10253
|
+
output.view.setUint32(root.offset + 120, newRootSize, true);
|
|
10254
|
+
output.view.setUint32(60, output.chains[1][0] ?? END3, true);
|
|
10255
|
+
output.view.setUint32(64, newMiniFatCount, true);
|
|
10256
|
+
output.view.setUint32(target.offset + 116, count ? firstMini : END3, true);
|
|
10257
|
+
output.view.setUint32(target.offset + 120, replacement.length, true);
|
|
10258
|
+
return output.bytes;
|
|
10259
|
+
}
|
|
10260
|
+
|
|
10261
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole2-stream-resize.js
|
|
10262
|
+
var END4 = 4294967294;
|
|
10263
|
+
var FREE3 = 4294967295;
|
|
10264
|
+
var FAT2 = 4294967293;
|
|
10265
|
+
var SECTOR2 = 512;
|
|
10266
|
+
var FAT_ENTRIES = SECTOR2 / 4;
|
|
10267
|
+
function follow2(table, start) {
|
|
10268
|
+
const ids = [], seen = /* @__PURE__ */ new Set();
|
|
10269
|
+
for (let id = start; id !== END4; id = table[id]) {
|
|
10270
|
+
if (id >= table.length || seen.has(id))
|
|
10271
|
+
throw new Error("Invalid allocation chain");
|
|
10272
|
+
seen.add(id);
|
|
10273
|
+
ids.push(id);
|
|
10274
|
+
}
|
|
10275
|
+
return ids;
|
|
10276
|
+
}
|
|
10277
|
+
function findSlot2(slots, path) {
|
|
10278
|
+
let parent = slots.get(0);
|
|
10279
|
+
if (!parent || parent.type !== 5)
|
|
10280
|
+
throw new Error("Invalid root");
|
|
10281
|
+
const parents = /* @__PURE__ */ new Set();
|
|
10282
|
+
for (let part = 0; part < path.length; part++) {
|
|
10283
|
+
if (parents.has(parent.id))
|
|
10284
|
+
throw new Error("Cyclic storage hierarchy");
|
|
10285
|
+
parents.add(parent.id);
|
|
10286
|
+
const stack = [parent.child], seen = /* @__PURE__ */ new Set(), matches = [];
|
|
10287
|
+
while (stack.length) {
|
|
10288
|
+
const id = stack.pop();
|
|
10289
|
+
if (id === FREE3)
|
|
10290
|
+
continue;
|
|
10291
|
+
if (seen.has(id))
|
|
10292
|
+
throw new Error("Invalid directory tree");
|
|
10293
|
+
seen.add(id);
|
|
10294
|
+
const slot2 = slots.get(id);
|
|
10295
|
+
if (!slot2)
|
|
10296
|
+
throw new Error("Missing directory slot");
|
|
10297
|
+
if (slot2.name.toLocaleLowerCase() === path[part].toLocaleLowerCase())
|
|
10298
|
+
matches.push(slot2);
|
|
10299
|
+
stack.push(slot2.left, slot2.right);
|
|
10300
|
+
}
|
|
10301
|
+
if (matches.length !== 1)
|
|
10302
|
+
throw new Error("Missing or ambiguous stream");
|
|
10303
|
+
parent = matches[0];
|
|
10304
|
+
if (parent.type !== (part === path.length - 1 ? 2 : 1))
|
|
10305
|
+
throw new Error("Invalid storage path");
|
|
10306
|
+
}
|
|
10307
|
+
return parent;
|
|
10308
|
+
}
|
|
10309
|
+
function validateHierarchy2(slots) {
|
|
10310
|
+
const owners = /* @__PURE__ */ new Set(), storages = [0];
|
|
10311
|
+
while (storages.length) {
|
|
10312
|
+
const storage = slots.get(storages.pop());
|
|
10313
|
+
const stack = [storage.child], names = /* @__PURE__ */ new Set();
|
|
10314
|
+
while (stack.length) {
|
|
10315
|
+
const id = stack.pop();
|
|
10316
|
+
if (id === FREE3)
|
|
10317
|
+
continue;
|
|
10318
|
+
const slot2 = slots.get(id);
|
|
10319
|
+
if (!slot2 || slot2.type !== 1 && slot2.type !== 2 || owners.has(id))
|
|
10320
|
+
throw new Error("Ambiguous directory ownership");
|
|
10321
|
+
const name = slot2.name.toLocaleLowerCase();
|
|
10322
|
+
if (names.has(name))
|
|
10323
|
+
throw new Error("Duplicate sibling name");
|
|
10324
|
+
names.add(name);
|
|
10325
|
+
owners.add(id);
|
|
10326
|
+
stack.push(slot2.left, slot2.right);
|
|
10327
|
+
if (slot2.type === 1)
|
|
10328
|
+
storages.push(id);
|
|
10329
|
+
}
|
|
10330
|
+
}
|
|
10331
|
+
if (owners.size !== slots.size - 1)
|
|
10332
|
+
throw new Error("Unreachable directory entries");
|
|
10333
|
+
}
|
|
10334
|
+
function resizeCompoundFileStream(input, path, replacement) {
|
|
10335
|
+
if (input.length >= 512 && new DataView(input.buffer, input.byteOffset, input.byteLength).getUint16(26, true) === 4)
|
|
10336
|
+
return resizeCompoundFileStreamV4(input, path, replacement);
|
|
10337
|
+
const fail = (reason) => ({ ok: false, bytes: input, reason });
|
|
10338
|
+
try {
|
|
10339
|
+
if (input.length < SECTOR2 || !path.length || path.some((part) => !part))
|
|
10340
|
+
return fail("invalid-or-missing-stream");
|
|
10341
|
+
const view = new DataView(input.buffer, input.byteOffset, input.byteLength);
|
|
10342
|
+
const read = (offset) => view.getUint32(offset, true);
|
|
10343
|
+
if (view.getUint16(26, true) !== 3)
|
|
10344
|
+
return fail("unsupported-version");
|
|
10345
|
+
if (read(72) !== 0 || read(44) > 109)
|
|
10346
|
+
return fail("unsupported-difat");
|
|
10347
|
+
const cutoff = read(56);
|
|
10348
|
+
if (cutoff !== 0 && cutoff !== 4096)
|
|
10349
|
+
return fail("unsupported-layout");
|
|
10350
|
+
const original = readCompoundFileStream(input, path);
|
|
10351
|
+
if (!original)
|
|
10352
|
+
return fail("invalid-or-missing-stream");
|
|
10353
|
+
const name = path[path.length - 1].toLocaleLowerCase();
|
|
10354
|
+
if (name === "worddocument" && original.length >= 12 && (original[10] | original[11] << 8) & 33024)
|
|
10355
|
+
return fail("unsafe-edit");
|
|
10356
|
+
if (name === "book" || name === "workbook") {
|
|
10357
|
+
for (let offset = 0; offset + 4 <= original.length; ) {
|
|
10358
|
+
const record = original[offset] | original[offset + 1] << 8;
|
|
10359
|
+
const length = original[offset + 2] | original[offset + 3] << 8;
|
|
10360
|
+
if (record === 47)
|
|
10361
|
+
return fail("unsafe-edit");
|
|
10362
|
+
if (record === 10 || offset + 4 + length > original.length)
|
|
10363
|
+
break;
|
|
10364
|
+
offset += 4 + length;
|
|
10365
|
+
}
|
|
10366
|
+
}
|
|
10367
|
+
const validated = replaceCompoundFileStream(input, path, original);
|
|
10368
|
+
if (validated === input)
|
|
10369
|
+
return fail("unsafe-edit");
|
|
10370
|
+
const sameLength = replacement.length === original.length;
|
|
10371
|
+
const sectorCount = input.length / SECTOR2 - 1, fatCount = read(44);
|
|
10372
|
+
if (sectorCount > fatCount * FAT_ENTRIES)
|
|
10373
|
+
return fail("unsafe-edit");
|
|
10374
|
+
const fatIds = Array.from({ length: fatCount }, (_, i) => read(76 + 4 * i));
|
|
10375
|
+
const fat = [];
|
|
10376
|
+
for (const id of fatIds) {
|
|
10377
|
+
for (let i = 0; i < FAT_ENTRIES && fat.length < sectorCount; i++)
|
|
10378
|
+
fat.push(read((id + 1) * SECTOR2 + i * 4));
|
|
10379
|
+
}
|
|
10380
|
+
if (fatIds.some((id) => fat[id] !== FAT2))
|
|
10381
|
+
return fail("unsafe-edit");
|
|
10382
|
+
const slots = /* @__PURE__ */ new Map();
|
|
10383
|
+
for (const [block, sector] of follow2(fat, read(48)).entries()) {
|
|
10384
|
+
for (let entry = 0; entry < 4; entry++) {
|
|
10385
|
+
const offset = (sector + 1) * SECTOR2 + entry * 128, type = view.getUint8(offset + 66);
|
|
10386
|
+
if (!type)
|
|
10387
|
+
continue;
|
|
10388
|
+
const nameLength = view.getUint16(offset + 64, true);
|
|
10389
|
+
if (![1, 2, 5].includes(type) || nameLength < 2 || nameLength > 64 || nameLength % 2 || view.getUint16(offset + nameLength - 2, true))
|
|
10390
|
+
return fail("unsafe-edit");
|
|
10391
|
+
let name2 = "";
|
|
10392
|
+
for (let i = 0; i < nameLength - 2; i += 2)
|
|
10393
|
+
name2 += String.fromCharCode(view.getUint16(offset + i, true));
|
|
10394
|
+
const id = block * 4 + entry;
|
|
10395
|
+
slots.set(id, { id, name: name2, type, left: read(offset + 68), right: read(offset + 72), child: read(offset + 76), start: read(offset + 116), size: read(offset + 120), offset });
|
|
10396
|
+
}
|
|
10397
|
+
}
|
|
10398
|
+
if ([...slots.values()].filter((slot2) => slot2.type === 5).length !== 1)
|
|
10399
|
+
return fail("unsafe-edit");
|
|
10400
|
+
const target = findSlot2(slots, path);
|
|
10401
|
+
validateHierarchy2(slots);
|
|
10402
|
+
if (sameLength) {
|
|
10403
|
+
const bytes = replaceCompoundFileStream(input, path, replacement);
|
|
10404
|
+
return bytes === input ? fail("unsafe-edit") : { ok: true, bytes };
|
|
10405
|
+
}
|
|
10406
|
+
if (original.length < cutoff || replacement.length < cutoff) {
|
|
10407
|
+
const bytes = resizeMiniCompoundStream(input, fat, fatIds, slots.get(0), target, replacement, cutoff);
|
|
10408
|
+
return bytes ? { ok: true, bytes } : fail("allocation-limit");
|
|
10409
|
+
}
|
|
10410
|
+
const oldChain = follow2(fat, target.start);
|
|
10411
|
+
if (target.size !== original.length || oldChain.length !== Math.ceil(target.size / SECTOR2))
|
|
10412
|
+
return fail("unsafe-edit");
|
|
10413
|
+
const output = appendCompoundAllocations(input, fatIds, [replacement]);
|
|
10414
|
+
if (!output)
|
|
10415
|
+
return fail("allocation-limit");
|
|
10416
|
+
for (const id of oldChain)
|
|
10417
|
+
output.writeFat(id, FREE3);
|
|
10418
|
+
output.view.setUint32(target.offset + 116, output.chains[0][0] ?? END4, true);
|
|
10419
|
+
output.view.setUint32(target.offset + 120, replacement.length, true);
|
|
10420
|
+
return { ok: true, bytes: output.bytes };
|
|
10421
|
+
} catch {
|
|
10422
|
+
return fail("unsafe-edit");
|
|
10423
|
+
}
|
|
10424
|
+
}
|
|
10425
|
+
|
|
10426
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-cfb.js
|
|
10427
|
+
var DocCfbRewriteError = class extends Error {
|
|
10428
|
+
constructor(message) {
|
|
10429
|
+
super(message);
|
|
10430
|
+
this.name = "DocCfbRewriteError";
|
|
10431
|
+
}
|
|
10432
|
+
};
|
|
9568
10433
|
function unwrapDocBytes(bytes) {
|
|
9569
10434
|
if (bytes.length < 8 || bytes[0] !== 208 || bytes[1] !== 207 || bytes[2] !== 17 || bytes[3] !== 224 || bytes[4] !== 161 || bytes[5] !== 177 || bytes[6] !== 26 || bytes[7] !== 225) {
|
|
9570
10435
|
return void 0;
|
|
9571
10436
|
}
|
|
9572
10437
|
try {
|
|
9573
|
-
const
|
|
9574
|
-
const ole = parseOle2(buffer);
|
|
9575
|
-
const wordDocBytes = ole.getStream("WordDocument");
|
|
10438
|
+
const wordDocBytes = readCompoundFileStream(bytes, ["WordDocument"]);
|
|
9576
10439
|
if (!wordDocBytes) {
|
|
9577
10440
|
return void 0;
|
|
9578
10441
|
}
|
|
9579
10442
|
const fib = readDocFib(wordDocBytes);
|
|
9580
|
-
const tableBytes =
|
|
10443
|
+
const tableBytes = readCompoundFileStream(bytes, [fib.tableStreamName]);
|
|
9581
10444
|
if (!tableBytes) {
|
|
9582
10445
|
return void 0;
|
|
9583
10446
|
}
|
|
9584
|
-
const
|
|
10447
|
+
const header = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
10448
|
+
const cutoff = header.getUint32(56, true);
|
|
10449
|
+
const canRewrite = header.getUint16(26, true) === 3 && header.getUint32(72, true) === 0 && header.getUint32(44, true) <= 109 && (cutoff === 0 || cutoff === 4096) && wordDocBytes.length >= cutoff && tableBytes.length >= cutoff;
|
|
9585
10450
|
return {
|
|
9586
10451
|
wordDocBytes,
|
|
9587
10452
|
tableStreamName: fib.tableStreamName,
|
|
9588
10453
|
tableBytes,
|
|
10454
|
+
canRewrite,
|
|
9589
10455
|
rewrap: (editedWordDocBytes, editedTableBytes) => {
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
if (
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
10456
|
+
if (editedWordDocBytes.length === wordDocBytes.length && editedTableBytes.length === tableBytes.length) {
|
|
10457
|
+
const patchedWord = replaceCompoundFileStream(bytes, ["WordDocument"], editedWordDocBytes);
|
|
10458
|
+
if (patchedWord === bytes)
|
|
10459
|
+
throw new DocCfbRewriteError("Cannot safely patch WordDocument");
|
|
10460
|
+
if (editedTableBytes.every((value, i) => value === tableBytes[i]))
|
|
10461
|
+
return patchedWord;
|
|
10462
|
+
const patchedTable = replaceCompoundFileStream(patchedWord, [fib.tableStreamName], editedTableBytes);
|
|
10463
|
+
if (patchedTable === patchedWord)
|
|
10464
|
+
throw new DocCfbRewriteError("Cannot safely patch DOC table stream");
|
|
10465
|
+
return patchedTable;
|
|
9599
10466
|
}
|
|
9600
|
-
|
|
10467
|
+
const word = resizeCompoundFileStream(bytes, ["WordDocument"], editedWordDocBytes);
|
|
10468
|
+
if (!word.ok)
|
|
10469
|
+
throw new DocCfbRewriteError(`Cannot resize WordDocument: ${word.reason}`);
|
|
10470
|
+
const table = resizeCompoundFileStream(word.bytes, [fib.tableStreamName], editedTableBytes);
|
|
10471
|
+
if (!table.ok)
|
|
10472
|
+
throw new DocCfbRewriteError(`Cannot resize DOC table stream: ${table.reason}`);
|
|
10473
|
+
return table.bytes;
|
|
9601
10474
|
}
|
|
9602
10475
|
};
|
|
9603
10476
|
} catch {
|
|
@@ -9605,14 +10478,19 @@ function unwrapDocBytes(bytes) {
|
|
|
9605
10478
|
}
|
|
9606
10479
|
}
|
|
9607
10480
|
|
|
9608
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
10481
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-fkp.js
|
|
9609
10482
|
var PAGE_SIZE = 512;
|
|
9610
10483
|
function parseBteTable(tableStream, at) {
|
|
9611
10484
|
const view = new DataView(tableStream.buffer, tableStream.byteOffset, tableStream.byteLength);
|
|
9612
|
-
|
|
10485
|
+
if (!Number.isInteger(at.fc) || !Number.isInteger(at.lcb) || at.fc < 0 || at.lcb < 4 || (at.lcb - 4) % 8 !== 0 || at.fc > tableStream.length - at.lcb)
|
|
10486
|
+
throw new Error("Invalid BTE table bounds");
|
|
10487
|
+
const n = (at.lcb - 4) / 8;
|
|
9613
10488
|
const fcs = [];
|
|
9614
10489
|
for (let i = 0; i <= n; i++) {
|
|
9615
|
-
|
|
10490
|
+
const fc = view.getInt32(at.fc + i * 4, true);
|
|
10491
|
+
if (fc < 0 || i > 0 && fc <= fcs[i - 1])
|
|
10492
|
+
throw new Error("Invalid BTE FC boundaries");
|
|
10493
|
+
fcs.push(fc);
|
|
9616
10494
|
}
|
|
9617
10495
|
const pns = [];
|
|
9618
10496
|
for (let i = 0; i < n; i++) {
|
|
@@ -9633,10 +10511,17 @@ function buildBteTableBytes(table) {
|
|
|
9633
10511
|
return bytes;
|
|
9634
10512
|
}
|
|
9635
10513
|
function findBteRangeIndex(table, fc) {
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
|
|
9639
|
-
|
|
10514
|
+
let low = 0, high = table.pns.length;
|
|
10515
|
+
while (low < high) {
|
|
10516
|
+
const middle = low + Math.floor((high - low) / 2);
|
|
10517
|
+
if (fc < table.fcs[middle])
|
|
10518
|
+
high = middle;
|
|
10519
|
+
else if (fc >= table.fcs[middle + 1])
|
|
10520
|
+
low = middle + 1;
|
|
10521
|
+
else if (fc >= table.fcs[middle])
|
|
10522
|
+
return middle;
|
|
10523
|
+
else
|
|
10524
|
+
break;
|
|
9640
10525
|
}
|
|
9641
10526
|
throw new Error(`FC ${fc} is not covered by any BTE range`);
|
|
9642
10527
|
}
|
|
@@ -9652,7 +10537,11 @@ function extractPapxBlob(wordDoc, papxBte, fc) {
|
|
|
9652
10537
|
const rangeIndex = findBteRangeIndex(papxBte, fc);
|
|
9653
10538
|
const pageOffset = papxBte.pns[rangeIndex] * PAGE_SIZE;
|
|
9654
10539
|
const page = wordDoc.subarray(pageOffset, pageOffset + PAGE_SIZE);
|
|
10540
|
+
if (page.length !== PAGE_SIZE)
|
|
10541
|
+
throw new Error("Truncated PAPX page");
|
|
9655
10542
|
const crun = page[PAGE_SIZE - 1];
|
|
10543
|
+
if (crun === 0 || (crun + 1) * 4 + crun * 13 > 511)
|
|
10544
|
+
throw new Error("Invalid PAPX run count");
|
|
9656
10545
|
const pageView = new DataView(page.buffer, page.byteOffset, page.byteLength);
|
|
9657
10546
|
const rgbxOffset = (crun + 1) * 4;
|
|
9658
10547
|
for (let i = 0; i < crun; i++) {
|
|
@@ -9660,8 +10549,13 @@ function extractPapxBlob(wordDoc, papxBte, fc) {
|
|
|
9660
10549
|
const runFcEnd = pageView.getInt32((i + 1) * 4, true);
|
|
9661
10550
|
if (fc >= runFcStart && fc < runFcEnd) {
|
|
9662
10551
|
const bOffsetWord = page[rgbxOffset + i * 13];
|
|
10552
|
+
if (bOffsetWord === 0)
|
|
10553
|
+
return new Uint8Array();
|
|
9663
10554
|
const blobOffset = bOffsetWord * 2;
|
|
9664
|
-
|
|
10555
|
+
const blobLength = papxBlobLength(page, blobOffset);
|
|
10556
|
+
if (blobOffset < rgbxOffset + crun * 13 || !Number.isInteger(blobLength) || blobOffset + blobLength > 511)
|
|
10557
|
+
throw new Error("Invalid PAPX blob bounds");
|
|
10558
|
+
return page.slice(blobOffset, blobOffset + blobLength);
|
|
9665
10559
|
}
|
|
9666
10560
|
}
|
|
9667
10561
|
throw new Error(`FC ${fc} is not covered by any run in PAPX page ${papxBte.pns[rangeIndex]}`);
|
|
@@ -9670,7 +10564,11 @@ function extractChpxBlob(wordDoc, chpxBte, fc) {
|
|
|
9670
10564
|
const rangeIndex = findBteRangeIndex(chpxBte, fc);
|
|
9671
10565
|
const pageOffset = chpxBte.pns[rangeIndex] * PAGE_SIZE;
|
|
9672
10566
|
const page = wordDoc.subarray(pageOffset, pageOffset + PAGE_SIZE);
|
|
10567
|
+
if (page.length !== PAGE_SIZE)
|
|
10568
|
+
throw new Error("Truncated CHPX page");
|
|
9673
10569
|
const crun = page[PAGE_SIZE - 1];
|
|
10570
|
+
if (crun === 0 || (crun + 1) * 4 + crun > 511)
|
|
10571
|
+
throw new Error("Invalid CHPX run count");
|
|
9674
10572
|
const pageView = new DataView(page.buffer, page.byteOffset, page.byteLength);
|
|
9675
10573
|
const rgbOffset = (crun + 1) * 4;
|
|
9676
10574
|
for (let i = 0; i < crun; i++) {
|
|
@@ -9683,6 +10581,8 @@ function extractChpxBlob(wordDoc, chpxBte, fc) {
|
|
|
9683
10581
|
}
|
|
9684
10582
|
const blobOffset = bOffsetWord * 2;
|
|
9685
10583
|
const cb = page[blobOffset];
|
|
10584
|
+
if (blobOffset < rgbOffset + crun || blobOffset + 1 + cb > 511)
|
|
10585
|
+
throw new Error("Invalid CHPX blob bounds");
|
|
9686
10586
|
return page.slice(blobOffset, blobOffset + 1 + cb);
|
|
9687
10587
|
}
|
|
9688
10588
|
}
|
|
@@ -9694,8 +10594,12 @@ function buildSingleRunPapxPage(fcStart, fcEnd, blob) {
|
|
|
9694
10594
|
view.setInt32(0, fcStart, true);
|
|
9695
10595
|
view.setInt32(4, fcEnd, true);
|
|
9696
10596
|
const blobByteOffset = 22;
|
|
9697
|
-
|
|
9698
|
-
|
|
10597
|
+
if (blob.length > 511 - blobByteOffset)
|
|
10598
|
+
throw new Error("PAPX blob exceeds page capacity");
|
|
10599
|
+
if (blob.length > 0) {
|
|
10600
|
+
page[8] = blobByteOffset / 2;
|
|
10601
|
+
page.set(blob, blobByteOffset);
|
|
10602
|
+
}
|
|
9699
10603
|
page[PAGE_SIZE - 1] = 1;
|
|
9700
10604
|
return page;
|
|
9701
10605
|
}
|
|
@@ -9706,6 +10610,8 @@ function buildSingleRunChpxPage(fcStart, fcEnd, blob) {
|
|
|
9706
10610
|
view.setInt32(4, fcEnd, true);
|
|
9707
10611
|
if (blob.length > 0) {
|
|
9708
10612
|
const blobByteOffset = 10;
|
|
10613
|
+
if (blob.length > 511 - blobByteOffset)
|
|
10614
|
+
throw new Error("CHPX blob exceeds page capacity");
|
|
9709
10615
|
page[8] = blobByteOffset / 2;
|
|
9710
10616
|
page.set(blob, blobByteOffset);
|
|
9711
10617
|
}
|
|
@@ -9713,7 +10619,7 @@ function buildSingleRunChpxPage(fcStart, fcEnd, blob) {
|
|
|
9713
10619
|
return page;
|
|
9714
10620
|
}
|
|
9715
10621
|
|
|
9716
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
10622
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-cp1252.js
|
|
9717
10623
|
var CP1252_HIGH_BLOCK = [
|
|
9718
10624
|
8364,
|
|
9719
10625
|
129,
|
|
@@ -9781,23 +10687,50 @@ function encodeCp1252(text) {
|
|
|
9781
10687
|
return Uint8Array.from(bytes);
|
|
9782
10688
|
}
|
|
9783
10689
|
|
|
9784
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
10690
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-pieces.js
|
|
9785
10691
|
var CLXT_PRC = 1;
|
|
9786
10692
|
var CLXT_PCDT = 2;
|
|
9787
|
-
|
|
10693
|
+
var DocResourceLimitError = class extends Error {
|
|
10694
|
+
constructor(message) {
|
|
10695
|
+
super(message);
|
|
10696
|
+
this.name = "DocResourceLimitError";
|
|
10697
|
+
}
|
|
10698
|
+
};
|
|
10699
|
+
function parseDocClx(tableStream, clx, maxPieces) {
|
|
10700
|
+
if (maxPieces !== void 0 && (!Number.isSafeInteger(maxPieces) || maxPieces < 0))
|
|
10701
|
+
throw new Error("Invalid DOC piece limit");
|
|
9788
10702
|
const view = new DataView(tableStream.buffer, tableStream.byteOffset, tableStream.byteLength);
|
|
10703
|
+
if (!Number.isInteger(clx.fc) || !Number.isInteger(clx.lcb) || clx.fc < 0 || clx.lcb < 5 || clx.fc > tableStream.length - clx.lcb)
|
|
10704
|
+
throw new Error("Clx is outside the table stream");
|
|
9789
10705
|
let off = clx.fc;
|
|
9790
10706
|
const end = clx.fc + clx.lcb;
|
|
10707
|
+
let prcCount = 0;
|
|
9791
10708
|
while (off < end) {
|
|
9792
10709
|
const clxt = view.getUint8(off);
|
|
9793
10710
|
if (clxt === CLXT_PRC) {
|
|
10711
|
+
if (off + 3 > end)
|
|
10712
|
+
throw new Error("Truncated Prc header");
|
|
9794
10713
|
const cb = view.getUint16(off + 1, true);
|
|
10714
|
+
if (cb > end - off - 3)
|
|
10715
|
+
throw new Error("Prc extends beyond Clx");
|
|
9795
10716
|
off += 1 + 2 + cb;
|
|
10717
|
+
prcCount++;
|
|
9796
10718
|
continue;
|
|
9797
10719
|
}
|
|
9798
10720
|
if (clxt === CLXT_PCDT) {
|
|
10721
|
+
if (off + 5 > end)
|
|
10722
|
+
throw new Error("Truncated Pcdt header");
|
|
9799
10723
|
const lcb = view.getInt32(off + 1, true);
|
|
9800
|
-
|
|
10724
|
+
if (lcb < 4 || (lcb - 4) % 12 !== 0 || lcb !== end - off - 5)
|
|
10725
|
+
throw new Error("Invalid PlcPcd length");
|
|
10726
|
+
if (maxPieces !== void 0 && (lcb - 4) / 12 > maxPieces)
|
|
10727
|
+
throw new DocResourceLimitError("DOC piece count exceeds the processing limit");
|
|
10728
|
+
const pieces = parsePlcPcd(view, off + 5, lcb);
|
|
10729
|
+
for (const piece of pieces) {
|
|
10730
|
+
if ((piece.prm & 1) !== 0 && piece.prm >>> 1 >= prcCount)
|
|
10731
|
+
throw new Error("Prm1 references a missing Prc");
|
|
10732
|
+
}
|
|
10733
|
+
return { pieces, prcBytes: tableStream.slice(clx.fc, off) };
|
|
9801
10734
|
}
|
|
9802
10735
|
throw new Error(`Unrecognised Clx entry type ${clxt} at ${off}`);
|
|
9803
10736
|
}
|
|
@@ -9807,7 +10740,10 @@ function parsePlcPcd(view, plcStart, lcb) {
|
|
|
9807
10740
|
const n = Math.floor((lcb - 4) / (4 + 8));
|
|
9808
10741
|
const cps = [];
|
|
9809
10742
|
for (let i = 0; i <= n; i++) {
|
|
9810
|
-
|
|
10743
|
+
const cp = view.getInt32(plcStart + i * 4, true);
|
|
10744
|
+
if (i === 0 && cp !== 0 || i > 0 && cp <= cps[i - 1])
|
|
10745
|
+
throw new Error("Piece CPs must start at zero and increase");
|
|
10746
|
+
cps.push(cp);
|
|
9811
10747
|
}
|
|
9812
10748
|
const pcdStart = plcStart + (n + 1) * 4;
|
|
9813
10749
|
const pieces = [];
|
|
@@ -9815,6 +10751,8 @@ function parsePlcPcd(view, plcStart, lcb) {
|
|
|
9815
10751
|
const pOff = pcdStart + i * 8;
|
|
9816
10752
|
const flagsWord = view.getUint16(pOff, true);
|
|
9817
10753
|
const fcRaw = view.getUint32(pOff + 2, true);
|
|
10754
|
+
if ((fcRaw & 2147483648) !== 0)
|
|
10755
|
+
throw new Error("Reserved FcCompressed bit is set");
|
|
9818
10756
|
const prm = view.getUint16(pOff + 6, true);
|
|
9819
10757
|
const compressed = (fcRaw >>> 30 & 1) === 1;
|
|
9820
10758
|
const fcField = fcRaw & 1073741823;
|
|
@@ -9831,6 +10769,9 @@ function parsePlcPcd(view, plcStart, lcb) {
|
|
|
9831
10769
|
}
|
|
9832
10770
|
function decodePieceText(wordDoc, piece) {
|
|
9833
10771
|
const charLen = piece.cpEnd - piece.cpStart;
|
|
10772
|
+
const byteLen = charLen * (piece.compressed ? 1 : 2);
|
|
10773
|
+
if (!Number.isInteger(charLen) || charLen < 0 || !Number.isInteger(piece.fc) || piece.fc < 0 || piece.fc > wordDoc.length - byteLen)
|
|
10774
|
+
throw new Error("Piece text is outside WordDocument");
|
|
9834
10775
|
if (piece.compressed) {
|
|
9835
10776
|
return decodeCp1252(wordDoc.subarray(piece.fc, piece.fc + charLen));
|
|
9836
10777
|
}
|
|
@@ -9841,8 +10782,19 @@ function decodePieceText(wordDoc, piece) {
|
|
|
9841
10782
|
}
|
|
9842
10783
|
return out;
|
|
9843
10784
|
}
|
|
9844
|
-
function decodePiecesText(wordDoc, pieces) {
|
|
9845
|
-
|
|
10785
|
+
function decodePiecesText(wordDoc, pieces, maxChars) {
|
|
10786
|
+
if (maxChars !== void 0 && (!Number.isInteger(maxChars) || maxChars < 0))
|
|
10787
|
+
throw new Error("Invalid text character limit");
|
|
10788
|
+
const out = [];
|
|
10789
|
+
for (const piece of pieces) {
|
|
10790
|
+
const bytes = (piece.cpEnd - piece.cpStart) * (piece.compressed ? 1 : 2);
|
|
10791
|
+
if (piece.cpEnd < piece.cpStart || piece.fc < 0 || piece.fc > wordDoc.length - bytes)
|
|
10792
|
+
throw new Error("Piece text is outside WordDocument");
|
|
10793
|
+
const end = Math.min(piece.cpEnd, maxChars ?? piece.cpEnd);
|
|
10794
|
+
if (end > piece.cpStart)
|
|
10795
|
+
out.push(decodePieceText(wordDoc, { ...piece, cpEnd: end }));
|
|
10796
|
+
}
|
|
10797
|
+
return out.join("");
|
|
9846
10798
|
}
|
|
9847
10799
|
function encodePieceText(text) {
|
|
9848
10800
|
const compressedBytes = encodeCp1252(text);
|
|
@@ -9880,7 +10832,7 @@ function replacePieceRange(pieces, cpStart, cpEnd, newPiece) {
|
|
|
9880
10832
|
fc: newPiece.fc,
|
|
9881
10833
|
compressed: newPiece.compressed,
|
|
9882
10834
|
flagsWord: newPiece.flagsWord,
|
|
9883
|
-
prm: 0,
|
|
10835
|
+
prm: newPiece.prm ?? 0,
|
|
9884
10836
|
charLength: newPiece.charLength
|
|
9885
10837
|
});
|
|
9886
10838
|
inserted = true;
|
|
@@ -9901,7 +10853,7 @@ function replacePieceRange(pieces, cpStart, cpEnd, newPiece) {
|
|
|
9901
10853
|
fc: newPiece.fc,
|
|
9902
10854
|
compressed: newPiece.compressed,
|
|
9903
10855
|
flagsWord: newPiece.flagsWord,
|
|
9904
|
-
prm: 0,
|
|
10856
|
+
prm: newPiece.prm ?? 0,
|
|
9905
10857
|
charLength: newPiece.charLength
|
|
9906
10858
|
});
|
|
9907
10859
|
}
|
|
@@ -9919,7 +10871,7 @@ function replacePieceRange(pieces, cpStart, cpEnd, newPiece) {
|
|
|
9919
10871
|
};
|
|
9920
10872
|
});
|
|
9921
10873
|
}
|
|
9922
|
-
function buildClxBytes(pieces) {
|
|
10874
|
+
function buildClxBytes(pieces, prcBytes = new Uint8Array()) {
|
|
9923
10875
|
const n = pieces.length;
|
|
9924
10876
|
const plcLcb = (n + 1) * 4 + n * 8;
|
|
9925
10877
|
const bytes = new Uint8Array(1 + 4 + plcLcb);
|
|
@@ -9940,14 +10892,122 @@ function buildClxBytes(pieces) {
|
|
|
9940
10892
|
view.setUint16(off + 6, piece.prm, true);
|
|
9941
10893
|
off += 8;
|
|
9942
10894
|
}
|
|
9943
|
-
|
|
10895
|
+
const result = new Uint8Array(prcBytes.length + bytes.length);
|
|
10896
|
+
result.set(prcBytes);
|
|
10897
|
+
result.set(bytes, prcBytes.length);
|
|
10898
|
+
parseDocClx(result, { fc: 0, lcb: result.length });
|
|
10899
|
+
return result;
|
|
10900
|
+
}
|
|
10901
|
+
|
|
10902
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-fields.js
|
|
10903
|
+
var UnsupportedDocFieldEditError = class extends Error {
|
|
10904
|
+
constructor(message) {
|
|
10905
|
+
super(message);
|
|
10906
|
+
this.name = "UnsupportedDocFieldEditError";
|
|
10907
|
+
}
|
|
10908
|
+
};
|
|
10909
|
+
function checkFieldRanges(markers, editStart, editEnd, maxDepth) {
|
|
10910
|
+
const stack = [];
|
|
10911
|
+
for (const { cp, ch } of markers) {
|
|
10912
|
+
if (ch === 19) {
|
|
10913
|
+
if (stack.length >= maxDepth)
|
|
10914
|
+
throw new DocResourceLimitError("DOC field nesting exceeds the processing limit");
|
|
10915
|
+
stack.push({ start: cp, separated: false });
|
|
10916
|
+
} else if (ch === 20) {
|
|
10917
|
+
const field = stack.at(-1);
|
|
10918
|
+
if (!field || field.separated)
|
|
10919
|
+
throw new Error("Invalid DOC field separator nesting");
|
|
10920
|
+
field.separated = true;
|
|
10921
|
+
} else if (ch === 21) {
|
|
10922
|
+
const field = stack.pop();
|
|
10923
|
+
if (!field)
|
|
10924
|
+
throw new Error("Unmatched DOC field end");
|
|
10925
|
+
if (field.start < editEnd && cp + 1 > editStart)
|
|
10926
|
+
throw new UnsupportedDocFieldEditError("Paragraph overlaps a field code/result range");
|
|
10927
|
+
}
|
|
10928
|
+
}
|
|
10929
|
+
if (stack.length)
|
|
10930
|
+
throw new Error("Unterminated DOC field");
|
|
10931
|
+
}
|
|
10932
|
+
function* bodyMarkers(body) {
|
|
10933
|
+
for (let cp = 0; cp < body.length; cp++) {
|
|
10934
|
+
const ch = body.charCodeAt(cp);
|
|
10935
|
+
if (ch >= 19 && ch <= 21)
|
|
10936
|
+
yield { cp, ch };
|
|
10937
|
+
}
|
|
10938
|
+
}
|
|
10939
|
+
function shiftDocMainFieldCps(table, at, body, editStart, editEnd, delta, maxFields) {
|
|
10940
|
+
if (![editStart, editEnd, delta, maxFields].every(Number.isSafeInteger) || editStart < 0 || editEnd <= editStart || editEnd > body.length || maxFields < 0 || body.length + delta < 0)
|
|
10941
|
+
throw new Error("Invalid field edit bounds or budget");
|
|
10942
|
+
checkFieldRanges(bodyMarkers(body), editStart, editEnd, maxFields);
|
|
10943
|
+
if (at.lcb === 0)
|
|
10944
|
+
return void 0;
|
|
10945
|
+
if (!Number.isSafeInteger(at.fc) || at.fc < 0 || at.lcb < 4 || (at.lcb - 4) % 6 !== 0 || at.fc > table.length - at.lcb)
|
|
10946
|
+
throw new Error("Invalid main-story field PLC bounds");
|
|
10947
|
+
const n = (at.lcb - 4) / 6;
|
|
10948
|
+
if (n > maxFields)
|
|
10949
|
+
throw new DocResourceLimitError("DOC field count exceeds the processing limit");
|
|
10950
|
+
const source = table.subarray(at.fc, at.fc + at.lcb);
|
|
10951
|
+
const view = new DataView(source.buffer, source.byteOffset, source.byteLength);
|
|
10952
|
+
const markers = [];
|
|
10953
|
+
let previous = -1;
|
|
10954
|
+
for (let i = 0; i < n; i++) {
|
|
10955
|
+
const cp = view.getInt32(i * 4, true);
|
|
10956
|
+
const ch = source[(n + 1) * 4 + i * 2] & 31;
|
|
10957
|
+
if (cp <= previous || cp >= body.length || ![19, 20, 21].includes(ch) || body.charCodeAt(cp) !== ch)
|
|
10958
|
+
throw new Error("Invalid main-story field marker");
|
|
10959
|
+
markers.push({ cp, ch });
|
|
10960
|
+
previous = cp;
|
|
10961
|
+
}
|
|
10962
|
+
checkFieldRanges(markers, editStart, editEnd, maxFields);
|
|
10963
|
+
const sentinel = view.getInt32(n * 4, true);
|
|
10964
|
+
if (sentinel < 0 || sentinel <= previous)
|
|
10965
|
+
throw new Error("Invalid field PLC sentinel");
|
|
10966
|
+
const result = source.slice();
|
|
10967
|
+
const out = new DataView(result.buffer);
|
|
10968
|
+
let shiftedLast = -1;
|
|
10969
|
+
for (let i = 0; i < n; i++) {
|
|
10970
|
+
const cp = markers[i].cp;
|
|
10971
|
+
const shifted = cp >= editEnd ? cp + delta : cp;
|
|
10972
|
+
if (shifted < 0 || shifted >= body.length + delta || shifted <= shiftedLast)
|
|
10973
|
+
throw new Error("Field position shifts outside the main story");
|
|
10974
|
+
out.setInt32(i * 4, shifted, true);
|
|
10975
|
+
shiftedLast = shifted;
|
|
10976
|
+
}
|
|
10977
|
+
let shiftedSentinel = sentinel >= editEnd ? sentinel + delta : sentinel;
|
|
10978
|
+
if (shiftedSentinel > 2147483647 || shiftedSentinel <= shiftedLast)
|
|
10979
|
+
shiftedSentinel = sentinel;
|
|
10980
|
+
if (shiftedSentinel <= shiftedLast)
|
|
10981
|
+
throw new Error("No representable field PLC sentinel");
|
|
10982
|
+
out.setInt32(n * 4, shiftedSentinel, true);
|
|
10983
|
+
return result;
|
|
9944
10984
|
}
|
|
9945
10985
|
|
|
9946
|
-
// node_modules/.bun/@christophervr+ole2@0.
|
|
9947
|
-
var RISKY_PLCF_INDICES = [3, 4, 5,
|
|
10986
|
+
// node_modules/.bun/@christophervr+ole2@0.8.0/node_modules/@christophervr/ole2/dist/ole-document-doc-editor.js
|
|
10987
|
+
var RISKY_PLCF_INDICES = [2, 3, 4, 5, 17, 18, 19, 20, 40, 41, 42, 43, 46, 47, 48, 54, 55, 56, 57, 58, 59, 75, 76, 89, 90];
|
|
9948
10988
|
var RISKY_STTB_INDICES = [21, 22, 23];
|
|
10989
|
+
var SAFE_EXTENDED_FC_LCB_INDICES = /* @__PURE__ */ new Set([93, 94, 113, 181, 182]);
|
|
10990
|
+
var DEFAULT_OLE_DOC_PROCESSING_LIMITS = Object.freeze({ maxCharacters: 16777216, maxPieces: 65536, maxFieldRecords: 65536 });
|
|
10991
|
+
function processingLimits(limits) {
|
|
10992
|
+
const resolved = {
|
|
10993
|
+
maxCharacters: DEFAULT_OLE_DOC_PROCESSING_LIMITS.maxCharacters,
|
|
10994
|
+
maxPieces: DEFAULT_OLE_DOC_PROCESSING_LIMITS.maxPieces,
|
|
10995
|
+
maxFieldRecords: DEFAULT_OLE_DOC_PROCESSING_LIMITS.maxFieldRecords
|
|
10996
|
+
};
|
|
10997
|
+
if (!Number.isSafeInteger(resolved.maxCharacters) || resolved.maxCharacters < 0 || !Number.isSafeInteger(resolved.maxPieces) || resolved.maxPieces < 0 || !Number.isSafeInteger(resolved.maxFieldRecords) || resolved.maxFieldRecords < 0)
|
|
10998
|
+
throw new Error("DOC processing limits must be nonnegative safe integers");
|
|
10999
|
+
return resolved;
|
|
11000
|
+
}
|
|
9949
11001
|
function hasUnsupportedFeatures(wordDoc, fib) {
|
|
11002
|
+
if (fib.ccpOtherStories > 0)
|
|
11003
|
+
return true;
|
|
11004
|
+
for (let index = 93; index < fib.fibRgFcLcbCount; index++) {
|
|
11005
|
+
if (!SAFE_EXTENDED_FC_LCB_INDICES.has(index) && readFcLcbAt(wordDoc, fib, index).lcb !== 0)
|
|
11006
|
+
return true;
|
|
11007
|
+
}
|
|
9950
11008
|
for (const index of RISKY_PLCF_INDICES) {
|
|
11009
|
+
if (index >= fib.fibRgFcLcbCount)
|
|
11010
|
+
continue;
|
|
9951
11011
|
if (readFcLcbAt(wordDoc, fib, index).lcb > 4) {
|
|
9952
11012
|
return true;
|
|
9953
11013
|
}
|
|
@@ -9980,15 +11040,30 @@ function paragraphBoundaries(bodyText) {
|
|
|
9980
11040
|
}
|
|
9981
11041
|
return boundaries;
|
|
9982
11042
|
}
|
|
9983
|
-
function
|
|
11043
|
+
function encodePieceTextUtf16(text) {
|
|
11044
|
+
const bytes = new Uint8Array(text.length * 2);
|
|
11045
|
+
const view = new DataView(bytes.buffer);
|
|
11046
|
+
for (let i = 0; i < text.length; i++)
|
|
11047
|
+
view.setUint16(i * 2, text.charCodeAt(i), true);
|
|
11048
|
+
return bytes;
|
|
11049
|
+
}
|
|
11050
|
+
function readOleDocParagraphs(docBytes, limits) {
|
|
9984
11051
|
try {
|
|
11052
|
+
const budget = processingLimits(limits);
|
|
9985
11053
|
const cfb = unwrapDocBytes(docBytes);
|
|
9986
11054
|
if (!cfb) {
|
|
9987
11055
|
return void 0;
|
|
9988
11056
|
}
|
|
9989
11057
|
const fib = readDocFib(cfb.wordDocBytes);
|
|
9990
|
-
|
|
9991
|
-
|
|
11058
|
+
if (fib.ccpText > budget.maxCharacters)
|
|
11059
|
+
return void 0;
|
|
11060
|
+
const pieces = parseDocClx(cfb.tableBytes, fib.clx, budget.maxPieces).pieces;
|
|
11061
|
+
const totalCp = pieces.at(-1)?.cpEnd ?? 0;
|
|
11062
|
+
if (totalCp > fib.ccpText + fib.ccpOtherStories + 1)
|
|
11063
|
+
return void 0;
|
|
11064
|
+
const fullText = decodePiecesText(cfb.wordDocBytes, pieces, fib.ccpText);
|
|
11065
|
+
if (fullText.length < fib.ccpText)
|
|
11066
|
+
return void 0;
|
|
9992
11067
|
const bodyText = fullText.slice(0, fib.ccpText);
|
|
9993
11068
|
const boundaries = paragraphBoundaries(bodyText);
|
|
9994
11069
|
let start = 0;
|
|
@@ -10005,7 +11080,9 @@ function readOleDocParagraphs(docBytes) {
|
|
|
10005
11080
|
}
|
|
10006
11081
|
function shiftSectionTableCps(tableBytes, sed, editEndCp, delta) {
|
|
10007
11082
|
const view = new DataView(tableBytes.buffer, tableBytes.byteOffset, tableBytes.byteLength);
|
|
10008
|
-
|
|
11083
|
+
if (sed.lcb < 4 || (sed.lcb - 4) % 16 !== 0 || sed.fc > tableBytes.length - sed.lcb)
|
|
11084
|
+
throw new Error("Invalid section table");
|
|
11085
|
+
const n = (sed.lcb - 4) / 16;
|
|
10009
11086
|
for (let i = 0; i <= n; i++) {
|
|
10010
11087
|
const off = sed.fc + i * 4;
|
|
10011
11088
|
const cp = view.getInt32(off, true);
|
|
@@ -10014,31 +11091,101 @@ function shiftSectionTableCps(tableBytes, sed, editEndCp, delta) {
|
|
|
10014
11091
|
}
|
|
10015
11092
|
}
|
|
10016
11093
|
}
|
|
10017
|
-
function writeOleDocParagraphEdit(docBytes, paragraphIndex, text) {
|
|
11094
|
+
function writeOleDocParagraphEdit(docBytes, paragraphIndex, text, limits) {
|
|
11095
|
+
return tryWriteOleDocParagraphEdit(docBytes, paragraphIndex, text, limits).bytes;
|
|
11096
|
+
}
|
|
11097
|
+
function tryWriteOleDocParagraphEdit(docBytes, paragraphIndex, text, limits) {
|
|
11098
|
+
const rejected = (reason) => ({ status: "rejected", bytes: docBytes, reason });
|
|
11099
|
+
let budget;
|
|
11100
|
+
try {
|
|
11101
|
+
budget = processingLimits(limits);
|
|
11102
|
+
} catch {
|
|
11103
|
+
return rejected("invalid-limits");
|
|
11104
|
+
}
|
|
11105
|
+
if (text.length > budget.maxCharacters)
|
|
11106
|
+
return rejected("resource-limit");
|
|
11107
|
+
if (!Number.isInteger(paragraphIndex) || paragraphIndex < 0)
|
|
11108
|
+
return rejected("invalid-paragraph-index");
|
|
11109
|
+
if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/u.test(text))
|
|
11110
|
+
return rejected("invalid-text");
|
|
10018
11111
|
try {
|
|
10019
11112
|
const cfb = unwrapDocBytes(docBytes);
|
|
10020
11113
|
if (!cfb) {
|
|
10021
|
-
return
|
|
11114
|
+
return rejected("invalid-payload");
|
|
10022
11115
|
}
|
|
10023
11116
|
const fib = readDocFib(cfb.wordDocBytes);
|
|
10024
|
-
if (
|
|
10025
|
-
return
|
|
10026
|
-
}
|
|
10027
|
-
const
|
|
10028
|
-
|
|
11117
|
+
if (fib.ccpText > budget.maxCharacters)
|
|
11118
|
+
return rejected("resource-limit");
|
|
11119
|
+
const { pieces, prcBytes } = parseDocClx(cfb.tableBytes, fib.clx, budget.maxPieces);
|
|
11120
|
+
const totalCp = pieces.at(-1)?.cpEnd ?? 0;
|
|
11121
|
+
if (totalCp > fib.ccpText + fib.ccpOtherStories + 1)
|
|
11122
|
+
return rejected("invalid-document");
|
|
11123
|
+
const fullText = decodePiecesText(cfb.wordDocBytes, pieces, fib.ccpText);
|
|
11124
|
+
if (fullText.length < fib.ccpText)
|
|
11125
|
+
return rejected("invalid-document");
|
|
10029
11126
|
const bodyText = fullText.slice(0, fib.ccpText);
|
|
10030
11127
|
const boundaries = paragraphBoundaries(bodyText);
|
|
10031
11128
|
const paragraphEndCp = boundaries[paragraphIndex];
|
|
10032
11129
|
if (paragraphEndCp === void 0) {
|
|
10033
|
-
return
|
|
11130
|
+
return rejected("invalid-paragraph-index");
|
|
10034
11131
|
}
|
|
10035
11132
|
const paragraphStartCp = paragraphIndex === 0 ? 0 : boundaries[paragraphIndex - 1];
|
|
11133
|
+
if (/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/u.test(bodyText.slice(paragraphStartCp, paragraphEndCp)))
|
|
11134
|
+
return rejected("unsupported-features");
|
|
11135
|
+
const sanitizedText = text.replaceAll(/[\r\n]+/gu, " ");
|
|
11136
|
+
if (fib.ccpText - (paragraphEndCp - paragraphStartCp) + sanitizedText.length + 1 > budget.maxCharacters)
|
|
11137
|
+
return rejected("resource-limit");
|
|
11138
|
+
if (sanitizedText.length + 1 === paragraphEndCp - paragraphStartCp && bodyText[paragraphEndCp - 1] === "\r") {
|
|
11139
|
+
const replacement = `${sanitizedText}\r`;
|
|
11140
|
+
const patched = cfb.wordDocBytes.slice();
|
|
11141
|
+
let canPatch = true;
|
|
11142
|
+
for (const piece of pieces) {
|
|
11143
|
+
const start = Math.max(paragraphStartCp, piece.cpStart);
|
|
11144
|
+
const end = Math.min(paragraphEndCp, piece.cpEnd);
|
|
11145
|
+
if (start >= end)
|
|
11146
|
+
continue;
|
|
11147
|
+
const part = replacement.slice(start - paragraphStartCp, end - paragraphStartCp);
|
|
11148
|
+
const unit = piece.compressed ? 1 : 2;
|
|
11149
|
+
const fc = piece.fc + (start - piece.cpStart) * unit;
|
|
11150
|
+
const fcEnd = fc + part.length * unit;
|
|
11151
|
+
if (pieces.some((other) => other !== piece && other.fc < fcEnd && other.fc + (other.cpEnd - other.cpStart) * (other.compressed ? 1 : 2) > fc)) {
|
|
11152
|
+
canPatch = false;
|
|
11153
|
+
break;
|
|
11154
|
+
}
|
|
11155
|
+
const encoded = piece.compressed ? encodeCp1252(part) : encodePieceTextUtf16(part);
|
|
11156
|
+
if (!encoded) {
|
|
11157
|
+
canPatch = false;
|
|
11158
|
+
break;
|
|
11159
|
+
}
|
|
11160
|
+
patched.set(encoded, fc);
|
|
11161
|
+
}
|
|
11162
|
+
if (canPatch)
|
|
11163
|
+
return { status: "edited", strategy: "preserved-runs", bytes: cfb.rewrap(patched, cfb.tableBytes) };
|
|
11164
|
+
}
|
|
11165
|
+
if (!cfb.canRewrite)
|
|
11166
|
+
return rejected("unsupported-container");
|
|
11167
|
+
if (hasUnsupportedFeatures(cfb.wordDocBytes, fib))
|
|
11168
|
+
return rejected("unsupported-features");
|
|
11169
|
+
const delta = sanitizedText.length + 1 - (paragraphEndCp - paragraphStartCp);
|
|
11170
|
+
const mainFields = readFcLcbAt(cfb.wordDocBytes, fib, 16);
|
|
11171
|
+
if (mainFields.lcb > 0) {
|
|
11172
|
+
for (let index = 0; index < fib.fibRgFcLcbCount; index++) {
|
|
11173
|
+
if (index === 16 || index === 87)
|
|
11174
|
+
continue;
|
|
11175
|
+
const other = readFcLcbAt(cfb.wordDocBytes, fib, index);
|
|
11176
|
+
if (other.lcb > 0 && other.fc < mainFields.fc + mainFields.lcb && other.fc + other.lcb > mainFields.fc)
|
|
11177
|
+
return rejected("invalid-document");
|
|
11178
|
+
}
|
|
11179
|
+
}
|
|
11180
|
+
const shiftedFields = shiftDocMainFieldCps(cfb.tableBytes, mainFields, bodyText, paragraphStartCp, paragraphEndCp, delta, budget.maxFieldRecords);
|
|
10036
11181
|
const { piece: startPiece, fc: startFc } = pieceAndFcAtCp(pieces, paragraphStartCp);
|
|
11182
|
+
if (pieces.some((piece) => piece.cpStart < paragraphEndCp && piece.cpEnd > paragraphStartCp && piece.prm !== startPiece.prm))
|
|
11183
|
+
return rejected("unsupported-features");
|
|
11184
|
+
const { fc: paragraphMarkFc } = pieceAndFcAtCp(pieces, paragraphEndCp - 1);
|
|
10037
11185
|
const papxBte = parseBteTable(cfb.tableBytes, fib.plcfbtePapx);
|
|
10038
11186
|
const chpxBte = parseBteTable(cfb.tableBytes, fib.plcfbteChpx);
|
|
10039
|
-
const papxBlob = extractPapxBlob(cfb.wordDocBytes, papxBte,
|
|
11187
|
+
const papxBlob = extractPapxBlob(cfb.wordDocBytes, papxBte, paragraphMarkFc);
|
|
10040
11188
|
const chpxBlob = extractChpxBlob(cfb.wordDocBytes, chpxBte, startFc);
|
|
10041
|
-
const sanitizedText = text.replaceAll(/[\r\n]+/gu, " ");
|
|
10042
11189
|
const { bytes: textBytes, compressed } = encodePieceText(`${sanitizedText}\r`);
|
|
10043
11190
|
const originalLen = cfb.wordDocBytes.length;
|
|
10044
11191
|
const newTextFc = originalLen;
|
|
@@ -10066,15 +11213,19 @@ function writeOleDocParagraphEdit(docBytes, paragraphIndex, text) {
|
|
|
10066
11213
|
fc: newTextFc,
|
|
10067
11214
|
compressed,
|
|
10068
11215
|
charLength: sanitizedText.length + 1,
|
|
10069
|
-
flagsWord: startPiece.flagsWord
|
|
11216
|
+
flagsWord: startPiece.flagsWord,
|
|
11217
|
+
prm: startPiece.prm
|
|
10070
11218
|
});
|
|
10071
|
-
|
|
11219
|
+
if (newPieces.length > budget.maxPieces)
|
|
11220
|
+
return rejected("resource-limit");
|
|
10072
11221
|
const newCcpText = fib.ccpText + delta;
|
|
10073
|
-
const clxBytes = buildClxBytes(newPieces);
|
|
11222
|
+
const clxBytes = buildClxBytes(newPieces, prcBytes);
|
|
10074
11223
|
const papxBteBytes = buildBteTableBytes(newPapxBte);
|
|
10075
11224
|
const chpxBteBytes = buildBteTableBytes(newChpxBte);
|
|
10076
11225
|
const newTableBytes = new Uint8Array(cfb.tableBytes.length + clxBytes.length + papxBteBytes.length + chpxBteBytes.length);
|
|
10077
11226
|
newTableBytes.set(cfb.tableBytes, 0);
|
|
11227
|
+
if (shiftedFields)
|
|
11228
|
+
newTableBytes.set(shiftedFields, mainFields.fc);
|
|
10078
11229
|
let tableOff = cfb.tableBytes.length;
|
|
10079
11230
|
const newClx = { fc: tableOff, lcb: clxBytes.length };
|
|
10080
11231
|
newTableBytes.set(clxBytes, tableOff);
|
|
@@ -10092,9 +11243,15 @@ function writeOleDocParagraphEdit(docBytes, paragraphIndex, text) {
|
|
|
10092
11243
|
plcfbteChpx: newChpxBteFcLcb,
|
|
10093
11244
|
plcfbtePapx: newPapxBteFcLcb
|
|
10094
11245
|
});
|
|
10095
|
-
return cfb.rewrap(newWordDoc, newTableBytes);
|
|
10096
|
-
} catch {
|
|
10097
|
-
|
|
11246
|
+
return { status: "edited", strategy: "piece-append", bytes: cfb.rewrap(newWordDoc, newTableBytes) };
|
|
11247
|
+
} catch (error) {
|
|
11248
|
+
if (error instanceof UnsupportedDocFieldEditError)
|
|
11249
|
+
return rejected("unsupported-features");
|
|
11250
|
+
if (error instanceof DocResourceLimitError)
|
|
11251
|
+
return rejected("resource-limit");
|
|
11252
|
+
if (error instanceof DocCfbRewriteError)
|
|
11253
|
+
return rejected("unsupported-container");
|
|
11254
|
+
return rejected("invalid-document");
|
|
10098
11255
|
}
|
|
10099
11256
|
}
|
|
10100
11257
|
|
|
@@ -10151,6 +11308,7 @@ function makeModel(paragraphs) {
|
|
|
10151
11308
|
async function loadLegacyDoc(input) {
|
|
10152
11309
|
const original = toBytes2(input);
|
|
10153
11310
|
let streams2 = [];
|
|
11311
|
+
let wordDocument = null;
|
|
10154
11312
|
try {
|
|
10155
11313
|
const parsed = parseOle2(
|
|
10156
11314
|
original.buffer.slice(
|
|
@@ -10159,6 +11317,7 @@ async function loadLegacyDoc(input) {
|
|
|
10159
11317
|
)
|
|
10160
11318
|
);
|
|
10161
11319
|
streams2 = parsed.entries.map((entry) => entry.name);
|
|
11320
|
+
wordDocument = readCompoundFileStream(original, ["WordDocument"]) ?? null;
|
|
10162
11321
|
} catch (error) {
|
|
10163
11322
|
throw new LegacyDocError(
|
|
10164
11323
|
`Invalid or truncated OLE compound file: ${error instanceof Error ? error.message : "parse failed"}`
|
|
@@ -10169,9 +11328,13 @@ async function loadLegacyDoc(input) {
|
|
|
10169
11328
|
"This is a password-protected .docx, not a legacy .doc: open it with loadDocument and its password."
|
|
10170
11329
|
);
|
|
10171
11330
|
}
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
11331
|
+
if (wordDocument) {
|
|
11332
|
+
try {
|
|
11333
|
+
readDocFib(wordDocument);
|
|
11334
|
+
} catch (error) {
|
|
11335
|
+
if (error instanceof Error && error.message === "Encrypted or obfuscated Word document")
|
|
11336
|
+
throw new LegacyDocError("Encrypted legacy Word documents are not supported.");
|
|
11337
|
+
}
|
|
10175
11338
|
}
|
|
10176
11339
|
const paragraphs = readOleDocParagraphs(original);
|
|
10177
11340
|
if (!paragraphs) {
|