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