@patrickdk77/office-defuse 0.1.1 → 0.2.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.
Files changed (75) hide show
  1. package/README.md +197 -42
  2. package/dist/cfb-writer.d.ts +25 -0
  3. package/dist/cfb-writer.js +314 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +357 -0
  6. package/dist/contained.d.ts +46 -0
  7. package/dist/contained.js +152 -0
  8. package/dist/context.d.ts +107 -12
  9. package/dist/context.js +101 -24
  10. package/dist/engine.d.ts +12 -1
  11. package/dist/engine.js +276 -31
  12. package/dist/errors.d.ts +17 -0
  13. package/dist/errors.js +27 -1
  14. package/dist/fields.d.ts +20 -13
  15. package/dist/fields.js +65 -23
  16. package/dist/findings.d.ts +2 -1
  17. package/dist/findings.js +17 -1
  18. package/dist/flat.d.ts +33 -0
  19. package/dist/flat.js +486 -0
  20. package/dist/formats.d.ts +7 -0
  21. package/dist/formats.js +5 -1
  22. package/dist/formulas.d.ts +27 -0
  23. package/dist/formulas.js +157 -1
  24. package/dist/index.d.ts +3 -2
  25. package/dist/index.js +8 -1
  26. package/dist/index.mjs +1 -1
  27. package/dist/io.d.ts +62 -1
  28. package/dist/io.js +245 -1
  29. package/dist/links.d.ts +27 -0
  30. package/dist/links.js +106 -1
  31. package/dist/msg.d.ts +3 -2
  32. package/dist/msg.js +172 -18
  33. package/dist/objects.d.ts +8 -5
  34. package/dist/objects.js +90 -20
  35. package/dist/odf.d.ts +65 -1
  36. package/dist/odf.js +181 -64
  37. package/dist/offcrypto.d.ts +24 -1
  38. package/dist/offcrypto.js +308 -69
  39. package/dist/ole.js +17 -5
  40. package/dist/oleobj.d.ts +11 -0
  41. package/dist/oleobj.js +20 -3
  42. package/dist/ooxml.d.ts +81 -0
  43. package/dist/ooxml.js +342 -81
  44. package/dist/plan.d.ts +104 -0
  45. package/dist/plan.js +73 -0
  46. package/dist/plugins.d.ts +8 -0
  47. package/dist/plugins.js +60 -0
  48. package/dist/ppt.js +45 -5
  49. package/dist/rewrite-cfb.d.ts +14 -0
  50. package/dist/rewrite-cfb.js +52 -0
  51. package/dist/rewrite-rtf.d.ts +9 -0
  52. package/dist/rewrite-rtf.js +45 -0
  53. package/dist/rewrite-xml.d.ts +36 -0
  54. package/dist/rewrite-xml.js +341 -0
  55. package/dist/rewrite-zip.d.ts +8 -0
  56. package/dist/rewrite-zip.js +498 -0
  57. package/dist/rtf.js +214 -41
  58. package/dist/rtfcomp.d.ts +7 -0
  59. package/dist/rtfcomp.js +80 -0
  60. package/dist/score.js +1 -1
  61. package/dist/sniff.d.ts +5 -0
  62. package/dist/sniff.js +38 -0
  63. package/dist/types.d.ts +43 -6
  64. package/dist/types.js +14 -0
  65. package/dist/vsd.d.ts +13 -0
  66. package/dist/vsd.js +689 -0
  67. package/dist/word.js +51 -17
  68. package/dist/xls.js +38 -4
  69. package/dist/xml.d.ts +30 -0
  70. package/dist/xml.js +45 -23
  71. package/dist/zip-writer.d.ts +4 -0
  72. package/dist/zip-writer.js +20 -7
  73. package/dist/zip.d.ts +9 -2
  74. package/dist/zip.js +61 -4
  75. package/package.json +4 -1
@@ -0,0 +1,498 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.writePackage = writePackage;
37
+ const stream = __importStar(require("node:stream"));
38
+ const zlib = __importStar(require("node:zlib"));
39
+ const crc32_1 = require("./crc32");
40
+ const errors_1 = require("./errors");
41
+ const io_1 = require("./io");
42
+ const links_1 = require("./links");
43
+ const odf_1 = require("./odf");
44
+ const ooxml_1 = require("./ooxml");
45
+ const rewrite_xml_1 = require("./rewrite-xml");
46
+ const xml_1 = require("./xml");
47
+ const zip_1 = require("./zip");
48
+ const zip_writer_1 = require("./zip-writer");
49
+ const VML_OFFICE_NS = 'urn:schemas-microsoft-com:office:office';
50
+ const MACRO_RELATIONSHIPS = new Set(['http://schemas.microsoft.com/office/2006/relationships/xlmacrosheet', 'http://schemas.microsoft.com/office/2006/relationships/xlintlmacrosheet']);
51
+ /**
52
+ * SpreadsheetML elements whose schema wants at least one child, which go once their last child went. An external
53
+ * link part whose link went goes whole.
54
+ */
55
+ const LISTS = new Set(['oleObjects', 'controls', 'hyperlinks', 'externalReferences', 'externalLink']);
56
+ async function* bytesOf(b) {
57
+ yield b;
58
+ }
59
+ const nothing = async () => undefined;
60
+ /** Deflates what `chunks` yields. The compressed bytes wait in a spill sink for their size and CRC. */
61
+ async function deflatedEntry(run, e, chunks) {
62
+ let crc = 0;
63
+ let size = 0;
64
+ const counted = (async function* () {
65
+ for await (const c of chunks) {
66
+ (0, errors_1.checkTime)(run.deadline);
67
+ crc = (0, crc32_1.crc32)(c, crc);
68
+ size += c.length;
69
+ yield c;
70
+ }
71
+ })();
72
+ const deflated = new io_1.SpillSink(run.temp, run.threshold);
73
+ try {
74
+ const src = stream.Readable.from(counted);
75
+ const deflater = zlib.createDeflateRaw();
76
+ src.on('error', err => deflater.destroy(err));
77
+ src.pipe(deflater);
78
+ for await (const c of deflater)
79
+ await deflated.write(c);
80
+ await deflated.close();
81
+ }
82
+ catch (err) {
83
+ await deflated.dispose();
84
+ throw err;
85
+ }
86
+ const info = { name: e.name, crc, size, dosTime: e.dosTime, dosDate: e.dosDate };
87
+ if (size === 0) {
88
+ await deflated.dispose();
89
+ return { info: { ...info, method: 0, compressedSize: 0 }, data: () => bytesOf(new Uint8Array(0)), dispose: nothing };
90
+ }
91
+ return { info: { ...info, method: 8, compressedSize: deflated.length }, data: () => deflated.read(), dispose: () => deflated.dispose() };
92
+ }
93
+ /** An entry whose compressed bytes are copied as they are. The run inflated them once and checked them against their CRC. */
94
+ function copiedEntry(run, zip, e) {
95
+ return {
96
+ info: { name: e.name, method: e.method, crc: e.crc, compressedSize: e.compressedSize, size: e.size, dosTime: e.dosTime, dosDate: e.dosDate },
97
+ data: () => zip.raw(e),
98
+ dispose: nothing,
99
+ again: () => deflatedEntry(run, e, zip.read(e)),
100
+ };
101
+ }
102
+ /** An entry made from bytes in memory, deflated, with the time ZipWriter gives new entries. */
103
+ function bytesEntry(name, data) {
104
+ const body = zlib.deflateRawSync(data);
105
+ return { info: { name, method: 8, crc: (0, crc32_1.crc32)(data), compressedSize: body.length, size: data.length }, data: () => bytesOf(body), dispose: nothing };
106
+ }
107
+ /** PDF readers take a header that starts in the first 1024 bytes of a file. */
108
+ const PDF_WINDOW = 1024;
109
+ const PDF_HEADER = Buffer.from('%PDF-', 'latin1');
110
+ /** The bytes a header that starts in the window reaches. */
111
+ const PDF_SPAN = PDF_WINDOW + PDF_HEADER.length - 1;
112
+ /** The most entries that wait, so a package of many such entries cannot keep them all. */
113
+ const MAX_WAITING = 16;
114
+ /**
115
+ * Writes entries so that no PDF header starts in the first 1024 bytes of the archive. Entries that go, and entries
116
+ * rewritten to another size, move what follows, so a stored PDF, a name or a local header from deep in the upload
117
+ * can land where PDF readers look. A copied entry that would put a PDF header there is deflated again. Any other
118
+ * entry that would waits until later entries have filled the window, up to 16 of them. What still lands there fails
119
+ * the output check, which rejects the file.
120
+ */
121
+ class PdfWindow {
122
+ writer;
123
+ head = Buffer.alloc(0);
124
+ waiting = [];
125
+ constructor(sink) {
126
+ this.writer = new zip_writer_1.ZipWriter({
127
+ write: async (b) => {
128
+ if (this.head.length < PDF_SPAN)
129
+ this.head = Buffer.concat([this.head, b.subarray(0, PDF_SPAN - this.head.length)]);
130
+ await sink.write(b);
131
+ },
132
+ close: () => sink.close(),
133
+ });
134
+ }
135
+ /** True when writing `r` next keeps a PDF header out of the window. */
136
+ async clear(r) {
137
+ if (this.head.length >= PDF_SPAN)
138
+ return true;
139
+ const parts = [this.head, Buffer.from(this.writer.localHeader(r.info))];
140
+ let got = parts[0].length + parts[1].length;
141
+ if (got < PDF_SPAN) {
142
+ for await (const c of r.data()) {
143
+ parts.push(Buffer.from(c));
144
+ got += c.length;
145
+ if (got >= PDF_SPAN)
146
+ break;
147
+ }
148
+ }
149
+ return !Buffer.concat(parts).subarray(0, PDF_SPAN).includes(PDF_HEADER);
150
+ }
151
+ async write(r) {
152
+ try {
153
+ await this.writer.copy(r.info, r.data());
154
+ }
155
+ finally {
156
+ await r.dispose();
157
+ }
158
+ }
159
+ async add(r) {
160
+ let ready = r;
161
+ if (!(await this.clear(ready)) && ready.again) {
162
+ const other = await ready.again();
163
+ await ready.dispose();
164
+ ready = other;
165
+ }
166
+ if (this.waiting.length < MAX_WAITING && !(await this.clear(ready))) {
167
+ this.waiting.push(ready);
168
+ return;
169
+ }
170
+ await this.write(ready);
171
+ while (this.waiting.length && this.head.length >= PDF_SPAN)
172
+ await this.write(this.waiting.shift());
173
+ }
174
+ /** Writes the entries still waiting, each time the first that keeps the window clear, or the first when none does. */
175
+ async end() {
176
+ while (this.waiting.length) {
177
+ let i = 0;
178
+ while (i < this.waiting.length && !(await this.clear(this.waiting[i])))
179
+ i++;
180
+ const [r] = this.waiting.splice(i < this.waiting.length ? i : 0, 1);
181
+ await this.write(r);
182
+ }
183
+ }
184
+ /** Frees what the waiting entries hold, after a failure. */
185
+ async dispose() {
186
+ for (const r of this.waiting.splice(0))
187
+ await r.dispose();
188
+ }
189
+ }
190
+ /** The parts the relationships reach from the package, past parts that go and out of no macro sheet that is emptied. */
191
+ function reach(graph, drops, emptied) {
192
+ const key = (p) => (0, zip_1.nameKey)(p.entry.name);
193
+ const reached = new Set();
194
+ const queue = [];
195
+ for (const r of graph.roots) {
196
+ if (drops.has(key(r)) || reached.has(r))
197
+ continue;
198
+ reached.add(r);
199
+ queue.push(r);
200
+ }
201
+ for (let p = queue.pop(); p; p = queue.pop()) {
202
+ if (emptied.has(key(p)))
203
+ continue;
204
+ for (const t of p.targets) {
205
+ if (drops.has(key(t)) || reached.has(t))
206
+ continue;
207
+ reached.add(t);
208
+ queue.push(t);
209
+ }
210
+ }
211
+ return reached;
212
+ }
213
+ const escapeAttribute = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/"/g, '&quot;');
214
+ /**
215
+ * Writes an OOXML package without what the plan removes. A part goes with its own relationships, every relationship
216
+ * that targets it, its content type override, every element that names one of those relationships, and the parts
217
+ * only it reached. A macro sheet becomes an empty worksheet. Parts nothing changes keep their compressed bytes.
218
+ */
219
+ async function writeOoxml(run, zip, plan, graph, format, limits, sink) {
220
+ const drops = new Set(plan.drop);
221
+ const sheets = plan.sheets;
222
+ const before = reach(graph, new Set(), new Set());
223
+ const relsEdits = new Map();
224
+ const partEditsByName = new Map();
225
+ const strict = graph.strict;
226
+ const worksheetRelationship = strict ? 'http://purl.oclc.org/ooxml/officeDocument/relationships/worksheet' : 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet';
227
+ const binarySheet = (k) => !(0, ooxml_1.isXmlType)(graph.parts.get(k)?.contentType) && format.format === 'xlsb';
228
+ const sheetType = (k) => (binarySheet(k) ? 'application/vnd.ms-excel.worksheet' : 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml');
229
+ // Removing an element can empty a part of its root, and that part then goes too, so this runs until nothing more goes.
230
+ for (;;) {
231
+ const after = reach(graph, drops, sheets);
232
+ for (const p of before)
233
+ if (!after.has(p))
234
+ drops.add((0, zip_1.nameKey)(p.entry.name));
235
+ for (const [source, rels] of graph.relsOf)
236
+ if (source !== '' && (drops.has(source) || sheets.has(source)))
237
+ drops.add((0, zip_1.nameKey)(rels.name));
238
+ const removedIds = new Map();
239
+ relsEdits.clear();
240
+ for (const [source, rels] of graph.relsOf) {
241
+ if (drops.has((0, zip_1.nameKey)(rels.name)))
242
+ continue;
243
+ const ids = plan.rels.get(rels.name) ?? new Set();
244
+ const removed = new Set();
245
+ const rules = {
246
+ element(el) {
247
+ if (el.depth !== 2 || el.local !== 'Relationship' || el.ns.toLowerCase() !== ooxml_1.RELS_NS)
248
+ return undefined;
249
+ const id = (0, xml_1.attr)(el, '', 'Id') ?? '';
250
+ const target = (0, xml_1.attr)(el, '', 'Target');
251
+ if (ids.has(id)) {
252
+ removed.add(id);
253
+ return { remove: true };
254
+ }
255
+ if (target === undefined)
256
+ return undefined;
257
+ const external = ((0, xml_1.attr)(el, '', 'TargetMode') ?? 'Internal').trim().toLowerCase() !== 'internal' || (0, links_1.isAbsoluteTarget)(target);
258
+ if (external)
259
+ return undefined;
260
+ const k = (0, ooxml_1.resolveTarget)(rels.name, target);
261
+ if (drops.has(k)) {
262
+ removed.add(id);
263
+ return { remove: true };
264
+ }
265
+ const type = ((0, xml_1.attr)(el, '', 'Type') ?? '').trim().toLowerCase();
266
+ if (sheets.has(k) && MACRO_RELATIONSHIPS.has(type))
267
+ return { set: new Map([['Type', worksheetRelationship]]) };
268
+ return undefined;
269
+ },
270
+ };
271
+ const r = await (0, rewrite_xml_1.partEdits)(zip.read(rels), rels.name, limits, false, plan.parts.get(rels.name), rules);
272
+ if (r.edits.length)
273
+ relsEdits.set(rels.name, r.edits);
274
+ if (removed.size)
275
+ removedIds.set(source, removed);
276
+ }
277
+ let more = false;
278
+ partEditsByName.clear();
279
+ // An XPS part names the parts it uses in its markup, so every XPS part is read again once a part goes.
280
+ const xpsDrops = graph.xps && drops.size > 0;
281
+ for (const [k, p] of graph.parts) {
282
+ const removed = removedIds.get(k);
283
+ const own = plan.parts.get(p.entry.name);
284
+ if (drops.has(k) || sheets.has(k) || plan.replace.has(k) || !(0, ooxml_1.isXmlType)(p.contentType) || (!removed && !own && !xpsDrops))
285
+ continue;
286
+ const rules = {
287
+ element(el) {
288
+ // An XPS element that names a part that went goes with it.
289
+ if (xpsDrops && ooxml_1.XPS_NS.has(el.ns.toLowerCase())) {
290
+ for (const [, uri] of (0, ooxml_1.xpsResources)(el))
291
+ if (!(0, links_1.isAbsoluteTarget)(uri) && drops.has((0, ooxml_1.resolvePartRef)(p.entry.name, uri)))
292
+ return { remove: true };
293
+ }
294
+ if (!removed)
295
+ return undefined;
296
+ for (const a of el.attributes) {
297
+ const ns = a.ns.toLowerCase();
298
+ if ((ooxml_1.R_NS.has(ns) || (ns === VML_OFFICE_NS && a.local === 'relid')) && removed.has(a.value)) {
299
+ // A hyperlink's text stays. Any other element that names a relationship that went goes with it.
300
+ return ooxml_1.WORD_NS.has(el.ns.toLowerCase()) && el.local === 'hyperlink' ? { unwrap: true } : { remove: true };
301
+ }
302
+ }
303
+ return undefined;
304
+ },
305
+ // An XPS property element such as Path.Fill holds one value, and goes once that value went.
306
+ collapses: el => (ooxml_1.SHEET_NS.has(el.ns.toLowerCase()) && LISTS.has(el.local)) || (graph.xps && ooxml_1.XPS_NS.has(el.ns.toLowerCase()) && el.local.includes('.')),
307
+ word: el => ooxml_1.WORD_NS.has(el.ns.toLowerCase()),
308
+ };
309
+ const r = await (0, rewrite_xml_1.partEdits)(zip.read(p.entry), p.entry.name, limits, p.contentType === ooxml_1.TYPE.vml, own, rules);
310
+ if (r.rootRemoved) {
311
+ drops.add(k);
312
+ more = true;
313
+ }
314
+ else if (r.edits.length)
315
+ partEditsByName.set(p.entry.name, r.edits);
316
+ }
317
+ if (!more)
318
+ break;
319
+ }
320
+ // The content types of parts that went, and of macro sheets that became worksheets.
321
+ const overridden = new Set();
322
+ let prefix = '';
323
+ const typesRules = {
324
+ element(el) {
325
+ if (el.ns.toLowerCase() !== ooxml_1.CONTENT_TYPES_NS)
326
+ return undefined;
327
+ if (el.depth === 1)
328
+ prefix = el.qname.includes(':') ? el.qname.slice(0, el.qname.indexOf(':') + 1) : '';
329
+ if (el.local !== 'Override')
330
+ return undefined;
331
+ const name = (0, xml_1.attr)(el, '', 'PartName');
332
+ if (name === undefined)
333
+ return undefined;
334
+ const k = (0, zip_1.nameKey)(name.replace(/^\//, ''));
335
+ if (drops.has(k))
336
+ return { remove: true };
337
+ if (!sheets.has(k))
338
+ return undefined;
339
+ overridden.add(k);
340
+ return { set: new Map([['ContentType', sheetType(k)]]) };
341
+ },
342
+ append: () => [...sheets]
343
+ .filter(k => !overridden.has(k) && !drops.has(k))
344
+ .map(k => `<${prefix}Override PartName="/${escapeAttribute(graph.parts.get(k)?.entry.name ?? k)}" ContentType="${sheetType(k)}"/>`)
345
+ .join(''),
346
+ };
347
+ const types = await (0, rewrite_xml_1.partEdits)(zip.read(graph.contentTypes), graph.contentTypes.name, limits, false, plan.parts.get(graph.contentTypes.name), typesRules);
348
+ const window = new PdfWindow(sink);
349
+ try {
350
+ for (const e of zip.entries) {
351
+ (0, errors_1.checkTime)(run.deadline);
352
+ const k = (0, zip_1.nameKey)(e.name);
353
+ if (drops.has(k))
354
+ continue;
355
+ const replaced = plan.replace.get(k);
356
+ const edits = e === graph.contentTypes ? types.edits : (relsEdits.get(e.name) ?? partEditsByName.get(e.name));
357
+ if (replaced)
358
+ await window.add(await deflatedEntry(run, e, replaced.read()));
359
+ else if (sheets.has(k))
360
+ await window.add(await deflatedEntry(run, e, bytesOf(emptySheet(binarySheet(k), strict))));
361
+ else if (edits?.length)
362
+ await window.add(await deflatedEntry(run, e, (0, rewrite_xml_1.splice)(zip.read(e), edits)));
363
+ else
364
+ await window.add(copiedEntry(run, zip, e));
365
+ }
366
+ await window.end();
367
+ }
368
+ finally {
369
+ await window.dispose();
370
+ }
371
+ await window.writer.finish();
372
+ }
373
+ /** An empty worksheet, as XML or as the records of a binary workbook. */
374
+ function emptySheet(binary, strict) {
375
+ // BrtBeginSheet, BrtBeginSheetData, BrtEndSheetData and BrtEndSheet, each with no data.
376
+ if (binary)
377
+ return Uint8Array.of(0x81, 0x01, 0x00, 0x91, 0x01, 0x00, 0x92, 0x01, 0x00, 0x82, 0x01, 0x00);
378
+ const ns = strict ? 'http://purl.oclc.org/ooxml/spreadsheetml/main' : 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
379
+ return Buffer.from(`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<worksheet xmlns="${ns}"><sheetData/></worksheet>`, 'utf8');
380
+ }
381
+ /** A relative path resolved against a folder of the package, as a key. "." and ".." segments are read. */
382
+ function resolveIn(folder, href) {
383
+ const out = [];
384
+ for (const seg of `${folder}${href.trim()}`.split('/')) {
385
+ if (seg === '..')
386
+ out.pop();
387
+ else if (seg !== '.' && seg !== '')
388
+ out.push(seg);
389
+ }
390
+ return (0, zip_1.nameKey)(out.join('/'));
391
+ }
392
+ /**
393
+ * Writes an OpenDocument package without what the plan removes, with the mimetype entry first, stored and without an
394
+ * extra field. An entry goes with its manifest entry and every element whose link names it. A package without a
395
+ * manifest gets one that lists its entries.
396
+ */
397
+ async function writeOdf(run, zip, plan, format, limits, sink) {
398
+ const layout = plan.odf;
399
+ const drops = new Set(plan.drop);
400
+ const folders = plan.folders;
401
+ const gone = (k) => drops.has(k) || [...folders].some(f => k.startsWith(f) || `${k}/` === f);
402
+ for (const e of zip.entries)
403
+ if (gone((0, zip_1.nameKey)(e.name)))
404
+ drops.add((0, zip_1.nameKey)(e.name));
405
+ const xmlEntries = zip.entries.filter(e => !e.directory && e !== layout?.mimetype && e !== layout?.manifest && /\.xml$/i.test(e.name) && !(0, odf_1.folderOf)(e.name));
406
+ const edits = new Map();
407
+ for (;;) {
408
+ let more = false;
409
+ edits.clear();
410
+ for (const e of xmlEntries) {
411
+ const k = (0, zip_1.nameKey)(e.name);
412
+ const own = plan.parts.get(e.name);
413
+ if (drops.has(k) || plan.replace.has(k) || (!own && !drops.size))
414
+ continue;
415
+ const folder = e.name.includes('/') ? e.name.slice(0, e.name.lastIndexOf('/') + 1) : '';
416
+ const rules = {
417
+ element(el) {
418
+ const written = (0, odf_1.hrefOf)(el);
419
+ // LibreOffice's vnd.sun.star.Package: URLs name an entry too.
420
+ const href = written === undefined ? undefined : (/^\s*vnd\.sun\.star\.package:(.*)$/is.exec(written)?.[1] ?? written);
421
+ if (href === undefined || !(0, links_1.isInsidePackage)(href))
422
+ return undefined;
423
+ // A link inside a sub-document may name its entry from the package root or from the sub-document's folder.
424
+ return gone((0, odf_1.packagePath)(href)) || gone(resolveIn(folder, href)) ? { remove: true } : undefined;
425
+ },
426
+ };
427
+ const r = await (0, rewrite_xml_1.partEdits)(zip.read(e), e.name, limits, false, own, rules);
428
+ if (r.rootRemoved) {
429
+ drops.add(k);
430
+ more = true;
431
+ }
432
+ else if (r.edits.length)
433
+ edits.set(e.name, r.edits);
434
+ }
435
+ if (!more)
436
+ break;
437
+ }
438
+ const manifest = layout?.manifest;
439
+ if (manifest && drops.size) {
440
+ const rules = {
441
+ element(el) {
442
+ if ((0, odf_1.odfNamespace)(el.ns) !== odf_1.NS.manifest || el.local !== 'file-entry')
443
+ return undefined;
444
+ const path = el.attributes.find(a => a.local === 'full-path' && (0, odf_1.odfNamespace)(a.ns) === odf_1.NS.manifest)?.value;
445
+ return path !== undefined && path !== '/' && gone((0, odf_1.packagePath)(path)) ? { remove: true } : undefined;
446
+ },
447
+ };
448
+ const r = await (0, rewrite_xml_1.partEdits)(zip.read(manifest), manifest.name, limits, false, plan.parts.get(manifest.name), rules);
449
+ if (r.edits.length)
450
+ edits.set(manifest.name, r.edits);
451
+ }
452
+ else if (manifest && plan.parts.has(manifest.name)) {
453
+ const r = await (0, rewrite_xml_1.partEdits)(zip.read(manifest), manifest.name, limits, false, plan.parts.get(manifest.name), { element: () => undefined });
454
+ if (r.edits.length)
455
+ edits.set(manifest.name, r.edits);
456
+ }
457
+ const window = new PdfWindow(sink);
458
+ const writer = window.writer;
459
+ await writer.add('mimetype', Buffer.from(layout?.mediaType || format.mimeType, 'latin1'), { compress: false, dosTime: layout?.mimetype?.dosTime, dosDate: layout?.mimetype?.dosDate });
460
+ const kept = [];
461
+ try {
462
+ for (const e of zip.entries) {
463
+ (0, errors_1.checkTime)(run.deadline);
464
+ const k = (0, zip_1.nameKey)(e.name);
465
+ if (e === layout?.mimetype || drops.has(k))
466
+ continue;
467
+ kept.push(e);
468
+ const replaced = plan.replace.get(k);
469
+ const own = edits.get(e.name);
470
+ if (replaced)
471
+ await window.add(await deflatedEntry(run, e, replaced.read()));
472
+ else if (own?.length)
473
+ await window.add(await deflatedEntry(run, e, (0, rewrite_xml_1.splice)(zip.read(e), own)));
474
+ else
475
+ await window.add(copiedEntry(run, zip, e));
476
+ }
477
+ if (!manifest) {
478
+ const entries = kept
479
+ .filter(e => !e.directory)
480
+ .map(e => `<manifest:file-entry manifest:full-path="${escapeAttribute(e.name)}" manifest:media-type="${/\.xml$/i.test(e.name) ? 'text/xml' : ''}"/>`)
481
+ .join('');
482
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<manifest:manifest xmlns:manifest="${odf_1.NS.manifest}"><manifest:file-entry manifest:full-path="/" manifest:media-type="${escapeAttribute(layout?.mediaType || format.mimeType)}"/>${entries}</manifest:manifest>`;
483
+ await window.add(bytesEntry('META-INF/manifest.xml', Buffer.from(xml, 'utf8')));
484
+ }
485
+ await window.end();
486
+ }
487
+ finally {
488
+ await window.dispose();
489
+ }
490
+ await writer.finish();
491
+ }
492
+ /** Writes the rewritten package to `sink`, which the caller closes. */
493
+ async function writePackage(run, zip, plan, format, limits, sink) {
494
+ if (plan.graph)
495
+ await writeOoxml(run, zip, plan, plan.graph, format, limits, sink);
496
+ else
497
+ await writeOdf(run, zip, plan, format, limits, sink);
498
+ }