@stll/anonymize-docx 2.0.2 → 2.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.
- package/dist/index.mjs +249 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -48,8 +48,29 @@ const DOCX_XML_MAX_DEPTH = 256;
|
|
|
48
48
|
const DOCX_MAX_ENTRIES = 4096;
|
|
49
49
|
const DOCX_MAX_TEXT_BLOCKS = 1e5;
|
|
50
50
|
const DOCX_MAX_TEXT_SEGMENTS = 1e6;
|
|
51
|
+
const DOCX_MAX_INLINE_CONTEXT_SCAN_OPS = 2e7;
|
|
51
52
|
const CONTENT_TYPES_PATH = "[Content_Types].xml";
|
|
52
53
|
const ROOT_RELATIONSHIPS_PATH = "_rels/.rels";
|
|
54
|
+
const DOCX_CORE_PROPERTIES_PATH = "docProps/core.xml";
|
|
55
|
+
const DOCX_APP_PROPERTIES_PATH = "docProps/app.xml";
|
|
56
|
+
const DOCX_CUSTOM_PROPERTIES_PATH = "docProps/custom.xml";
|
|
57
|
+
const CUSTOM_XML_DIRECTORY_PREFIX = "customXml/";
|
|
58
|
+
const DOCPROPS_DIRECTORY_PREFIX = "docProps/";
|
|
59
|
+
const METADATA_CONTENT_TYPES = /* @__PURE__ */ new Set([
|
|
60
|
+
"application/vnd.openxmlformats-package.core-properties+xml",
|
|
61
|
+
"application/vnd.openxmlformats-officedocument.extended-properties+xml",
|
|
62
|
+
"application/vnd.openxmlformats-officedocument.custom-properties+xml"
|
|
63
|
+
]);
|
|
64
|
+
const ABSOLUTE_URI_PATTERN = /^[a-z][a-z0-9+.-]*:/iu;
|
|
65
|
+
const KNOWN_METADATA_CONTENT_TYPES = {
|
|
66
|
+
[DOCX_CORE_PROPERTIES_PATH]: "application/vnd.openxmlformats-package.core-properties+xml",
|
|
67
|
+
[DOCX_APP_PROPERTIES_PATH]: "application/vnd.openxmlformats-officedocument.extended-properties+xml",
|
|
68
|
+
[DOCX_CUSTOM_PROPERTIES_PATH]: "application/vnd.openxmlformats-officedocument.custom-properties+xml"
|
|
69
|
+
};
|
|
70
|
+
const GENERIC_XML_CONTENT_TYPE = "application/xml";
|
|
71
|
+
const GENERIC_BINARY_CONTENT_TYPE = "application/octet-stream";
|
|
72
|
+
const RELATIONSHIPS_CONTENT_TYPE = "application/vnd.openxmlformats-package.relationships+xml";
|
|
73
|
+
const PII_RELATIONSHIP_TARGET_SCHEMES = ["mailto:", "tel:"];
|
|
53
74
|
const CONTENT_TYPES_NAMESPACE = "http://schemas.openxmlformats.org/package/2006/content-types";
|
|
54
75
|
const PACKAGE_RELATIONSHIP_NAMESPACES = /* @__PURE__ */ new Set(["http://purl.oclc.org/ooxml/package/relationships", "http://schemas.openxmlformats.org/package/2006/relationships"]);
|
|
55
76
|
const WORDPROCESSING_CONTENT_TYPE_PREFIX = "application/vnd.openxmlformats-officedocument.wordprocessingml.";
|
|
@@ -79,6 +100,9 @@ const assertXmlDepth = (depth) => {
|
|
|
79
100
|
throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX XML must not exceed 256 nested elements`);
|
|
80
101
|
};
|
|
81
102
|
const safeEntryPath = (name) => name.length > 0 && !name.startsWith("/") && !name.includes("\\") && !name.split("/").includes("..") && !name.includes("\0");
|
|
103
|
+
const RELATIONSHIPS_ENTRY_PATTERN = /(?:^|\/)_rels\/[^/]+\.rels$/u;
|
|
104
|
+
const isRelationshipsEntry = (name) => name === ROOT_RELATIONSHIPS_PATH || RELATIONSHIPS_ENTRY_PATTERN.test(name);
|
|
105
|
+
const isCustomXmlEntry = (name) => name.startsWith(CUSTOM_XML_DIRECTORY_PREFIX) && name.endsWith(".xml");
|
|
82
106
|
const archiveFilter = ({ budget, file, includeAllEntries }) => {
|
|
83
107
|
budget.entryCount += 1;
|
|
84
108
|
if (budget.entryCount > DOCX_MAX_ENTRIES) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX archives must contain at most ${DOCX_MAX_ENTRIES} entries`);
|
|
@@ -86,20 +110,24 @@ const archiveFilter = ({ budget, file, includeAllEntries }) => {
|
|
|
86
110
|
if (file.originalSize > 16777216) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`);
|
|
87
111
|
budget.uncompressedBytes += file.originalSize;
|
|
88
112
|
if (budget.uncompressedBytes > 134217728) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`);
|
|
89
|
-
return includeAllEntries || file.name === CONTENT_TYPES_PATH || file.name
|
|
113
|
+
return includeAllEntries || file.name === CONTENT_TYPES_PATH || file.name.startsWith("word/") && file.name.endsWith(".xml") || isRelationshipsEntry(file.name) || file.name.startsWith(DOCPROPS_DIRECTORY_PREFIX) || isCustomXmlEntry(file.name);
|
|
90
114
|
};
|
|
91
|
-
const unzipDocxArchive = (archive, includeAllEntries = false) => {
|
|
115
|
+
const unzipDocxArchive = (archive, includeAllEntries = false, onSkippedEntry) => {
|
|
92
116
|
if (archive.byteLength > 67108864) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded, `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`);
|
|
93
117
|
const budget = {
|
|
94
118
|
entryCount: 0,
|
|
95
119
|
uncompressedBytes: 0
|
|
96
120
|
};
|
|
97
121
|
try {
|
|
98
|
-
return unzipSync(archive, { filter: (file) =>
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
122
|
+
return unzipSync(archive, { filter: (file) => {
|
|
123
|
+
const keep = archiveFilter({
|
|
124
|
+
budget,
|
|
125
|
+
file,
|
|
126
|
+
includeAllEntries
|
|
127
|
+
});
|
|
128
|
+
if (!keep) onSkippedEntry?.(file.name);
|
|
129
|
+
return keep;
|
|
130
|
+
} });
|
|
103
131
|
} catch (error) {
|
|
104
132
|
if (error instanceof DocxExtractionError) throw error;
|
|
105
133
|
throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidArchive, "Input is not a valid bounded DOCX ZIP archive");
|
|
@@ -195,6 +223,97 @@ const parseMainDocumentTarget = (xml) => {
|
|
|
195
223
|
if (target === void 0) throw invalidPackage("DOCX main-document relationship is unavailable");
|
|
196
224
|
return target;
|
|
197
225
|
};
|
|
226
|
+
const hasPiiRelationshipTargetScheme = (target) => {
|
|
227
|
+
const normalized = target.trim().toLowerCase();
|
|
228
|
+
return PII_RELATIONSHIP_TARGET_SCHEMES.some((scheme) => normalized.startsWith(scheme));
|
|
229
|
+
};
|
|
230
|
+
const isExternalRelationshipTarget = (target, targetMode) => {
|
|
231
|
+
const normalized = target.trim();
|
|
232
|
+
return targetMode?.trim().toLowerCase() === "external" || ABSOLUTE_URI_PATTERN.test(normalized) || normalized.startsWith("//");
|
|
233
|
+
};
|
|
234
|
+
const PII_SCHEME_TARGET_REASON = "target uses a PII-bearing external scheme (mailto/tel) that anonymization does not redact";
|
|
235
|
+
const EXTERNAL_TARGET_REASON = "target is external and is not examined or redacted by anonymization";
|
|
236
|
+
const DANGLING_TARGET_REASON = "target does not resolve to a package part and is not examined or redacted by anonymization";
|
|
237
|
+
const relationshipBaseDirectory = (relsPath) => {
|
|
238
|
+
const marker = relsPath.lastIndexOf("_rels/");
|
|
239
|
+
return marker <= 0 ? "" : relsPath.slice(0, marker);
|
|
240
|
+
};
|
|
241
|
+
const decodeOpcTarget = (target) => {
|
|
242
|
+
try {
|
|
243
|
+
return decodeURIComponent(target);
|
|
244
|
+
} catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const normalizeOpcPath = (path) => {
|
|
249
|
+
const segments = [];
|
|
250
|
+
for (const segment of path.split("/")) {
|
|
251
|
+
if (segment === "" || segment === ".") continue;
|
|
252
|
+
if (segment === "..") {
|
|
253
|
+
if (segments.length === 0) return null;
|
|
254
|
+
segments.pop();
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
segments.push(segment);
|
|
258
|
+
}
|
|
259
|
+
return segments.length === 0 ? null : segments.join("/");
|
|
260
|
+
};
|
|
261
|
+
const resolveInternalRelationshipTarget = (target, relsPath) => {
|
|
262
|
+
const decoded = decodeOpcTarget(target.trim());
|
|
263
|
+
if (decoded === null || decoded === "") return null;
|
|
264
|
+
if (decoded.startsWith("/")) return normalizeOpcPath(decoded.slice(1));
|
|
265
|
+
return normalizeOpcPath(`${relationshipBaseDirectory(relsPath)}${decoded}`);
|
|
266
|
+
};
|
|
267
|
+
const parseUncoveredRelationshipTargets = ({ xml, relsPath: path, knownEntryPaths }) => {
|
|
268
|
+
const found = [];
|
|
269
|
+
const parser = new SaxesParser({ xmlns: true });
|
|
270
|
+
let parseError = null;
|
|
271
|
+
let depth = 0;
|
|
272
|
+
parser.on("error", (error) => {
|
|
273
|
+
parseError = error;
|
|
274
|
+
});
|
|
275
|
+
parser.on("doctype", () => {
|
|
276
|
+
throw invalidPackage("DOCX XML must not contain a document type declaration");
|
|
277
|
+
});
|
|
278
|
+
parser.on("opentag", (tag) => {
|
|
279
|
+
assertXmlDepth(depth);
|
|
280
|
+
depth += 1;
|
|
281
|
+
if (tag.local !== "Relationship" || !PACKAGE_RELATIONSHIP_NAMESPACES.has(tag.uri)) return;
|
|
282
|
+
const target = attributeByLocalName(tag, "Target");
|
|
283
|
+
if (target === null) return;
|
|
284
|
+
const targetMode = attributeByLocalName(tag, "TargetMode");
|
|
285
|
+
if (hasPiiRelationshipTargetScheme(target)) {
|
|
286
|
+
found.push({
|
|
287
|
+
relationshipId: attributeByLocalName(tag, "Id"),
|
|
288
|
+
reason: PII_SCHEME_TARGET_REASON
|
|
289
|
+
});
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (isExternalRelationshipTarget(target, targetMode)) {
|
|
293
|
+
found.push({
|
|
294
|
+
relationshipId: attributeByLocalName(tag, "Id"),
|
|
295
|
+
reason: EXTERNAL_TARGET_REASON
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const resolved = resolveInternalRelationshipTarget(target, path);
|
|
300
|
+
if (resolved === null || !knownEntryPaths.has(resolved.toLowerCase())) found.push({
|
|
301
|
+
relationshipId: attributeByLocalName(tag, "Id"),
|
|
302
|
+
reason: DANGLING_TARGET_REASON
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
parser.on("closetag", () => {
|
|
306
|
+
depth -= 1;
|
|
307
|
+
});
|
|
308
|
+
try {
|
|
309
|
+
parser.write(xml).close();
|
|
310
|
+
} catch (error) {
|
|
311
|
+
if (error instanceof DocxExtractionError) throw error;
|
|
312
|
+
parseError = error instanceof Error ? error : /* @__PURE__ */ new Error("invalid XML");
|
|
313
|
+
}
|
|
314
|
+
if (parseError !== null) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidXml, `DOCX relationships are not valid XML: ${path}`);
|
|
315
|
+
return found;
|
|
316
|
+
};
|
|
198
317
|
const classifyPart = ({ contentType, path }) => {
|
|
199
318
|
if (!contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX)) return null;
|
|
200
319
|
const suffix = contentType.slice(63);
|
|
@@ -268,8 +387,10 @@ const inlineContexts = (stack) => {
|
|
|
268
387
|
};
|
|
269
388
|
const appendSegment = (block, budget, value, source, path, stack) => {
|
|
270
389
|
if (value.length === 0) return;
|
|
271
|
-
if (budget.segmentCount >= DOCX_MAX_TEXT_SEGMENTS) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX
|
|
390
|
+
if (budget.segmentCount >= DOCX_MAX_TEXT_SEGMENTS) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX archives must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`);
|
|
272
391
|
budget.segmentCount += 1;
|
|
392
|
+
if (budget.inlineContextScanOps + stack.length > DOCX_MAX_INLINE_CONTEXT_SCAN_OPS) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX archives must not require more than ${DOCX_MAX_INLINE_CONTEXT_SCAN_OPS} aggregate inline-context scan operations`);
|
|
393
|
+
budget.inlineContextScanOps += stack.length;
|
|
273
394
|
const start = block.text.length;
|
|
274
395
|
block.text += value;
|
|
275
396
|
block.segments.push({
|
|
@@ -280,7 +401,7 @@ const appendSegment = (block, budget, value, source, path, stack) => {
|
|
|
280
401
|
xmlPath: path
|
|
281
402
|
});
|
|
282
403
|
};
|
|
283
|
-
const extractPart = (part, xml) => {
|
|
404
|
+
const extractPart = (part, xml, textBudget) => {
|
|
284
405
|
const blocks = [];
|
|
285
406
|
const stack = [];
|
|
286
407
|
const blockStack = [];
|
|
@@ -291,7 +412,6 @@ const extractPart = (part, xml) => {
|
|
|
291
412
|
let unsupportedSymbolCount = 0;
|
|
292
413
|
let unsupportedFieldInstructionCount = 0;
|
|
293
414
|
let unsupportedAlternateContentCount = 0;
|
|
294
|
-
const textBudget = { segmentCount: 0 };
|
|
295
415
|
const parser = new SaxesParser({ xmlns: true });
|
|
296
416
|
parser.on("error", (error) => {
|
|
297
417
|
parseError = error;
|
|
@@ -378,7 +498,10 @@ const extractPart = (part, xml) => {
|
|
|
378
498
|
};
|
|
379
499
|
};
|
|
380
500
|
const extractDocxText = (archive) => {
|
|
381
|
-
const
|
|
501
|
+
const skippedEntryPaths = [];
|
|
502
|
+
const entries = unzipDocxArchive(archive, false, (name) => {
|
|
503
|
+
skippedEntryPaths.push(name);
|
|
504
|
+
});
|
|
382
505
|
const contentTypesBytes = entries[CONTENT_TYPES_PATH];
|
|
383
506
|
if (contentTypesBytes === void 0) throw invalidPackage("DOCX archive is missing [Content_Types].xml");
|
|
384
507
|
const contentTypes = parseContentTypes(decodeXml(contentTypesBytes, CONTENT_TYPES_PATH));
|
|
@@ -395,15 +518,15 @@ const extractDocxText = (archive) => {
|
|
|
395
518
|
let unsupportedSymbolCount = 0;
|
|
396
519
|
let unsupportedFieldInstructionCount = 0;
|
|
397
520
|
let unsupportedAlternateContentCount = 0;
|
|
398
|
-
|
|
521
|
+
const textBudget = {
|
|
522
|
+
segmentCount: 0,
|
|
523
|
+
inlineContextScanOps: 0
|
|
524
|
+
};
|
|
399
525
|
for (const part of supportedParts) {
|
|
400
526
|
const bytes = entries[part.path];
|
|
401
527
|
if (bytes === void 0) throw invalidPackage(`DOCX archive is missing declared part: ${part.path}`);
|
|
402
|
-
const extracted = extractPart(part, decodeXml(bytes, part.path));
|
|
528
|
+
const extracted = extractPart(part, decodeXml(bytes, part.path), textBudget);
|
|
403
529
|
if (blocks.length + extracted.blocks.length > DOCX_MAX_TEXT_BLOCKS) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX archives must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`);
|
|
404
|
-
const extractedSegmentCount = extracted.blocks.reduce((count, block) => count + block.segments.length, 0);
|
|
405
|
-
if (textSegmentCount + extractedSegmentCount > DOCX_MAX_TEXT_SEGMENTS) throw new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded, `DOCX archives must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`);
|
|
406
|
-
textSegmentCount += extractedSegmentCount;
|
|
407
530
|
blocks.push(...extracted.blocks);
|
|
408
531
|
coverageParts.push({
|
|
409
532
|
status: "extracted",
|
|
@@ -416,15 +539,51 @@ const extractDocxText = (archive) => {
|
|
|
416
539
|
unsupportedFieldInstructionCount += extracted.unsupportedFieldInstructionCount;
|
|
417
540
|
unsupportedAlternateContentCount += extracted.unsupportedAlternateContentCount;
|
|
418
541
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
542
|
+
const knownEntryPaths = new Set([...Object.keys(entries), ...skippedEntryPaths].map((name) => name.toLowerCase()));
|
|
543
|
+
for (const [relsPath, relsBytes] of Object.entries(entries)) {
|
|
544
|
+
if (!isRelationshipsEntry(relsPath)) continue;
|
|
545
|
+
const uncoveredTargets = parseUncoveredRelationshipTargets({
|
|
546
|
+
xml: decodeXml(relsBytes, relsPath),
|
|
547
|
+
relsPath,
|
|
548
|
+
knownEntryPaths
|
|
549
|
+
});
|
|
550
|
+
for (const uncovered of uncoveredTargets) coverageParts.push({
|
|
551
|
+
status: "unsupported",
|
|
552
|
+
path: relsPath,
|
|
553
|
+
contentType: RELATIONSHIPS_CONTENT_TYPE,
|
|
554
|
+
reason: uncovered.relationshipId === null ? `Relationship ${uncovered.reason}` : `Relationship "${uncovered.relationshipId}" ${uncovered.reason}`
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
const coveredPaths = /* @__PURE__ */ new Set([CONTENT_TYPES_PATH, ...supportedParts.map((part) => part.path)]);
|
|
558
|
+
const overrideContentTypes = new Map(contentTypes.map((entry) => [entry.path, entry.contentType]));
|
|
559
|
+
const markUnsupported = (path, contentType, reason) => {
|
|
560
|
+
if (coveredPaths.has(path)) return;
|
|
561
|
+
coveredPaths.add(path);
|
|
562
|
+
coverageParts.push({
|
|
563
|
+
status: "unsupported",
|
|
564
|
+
path,
|
|
565
|
+
contentType,
|
|
566
|
+
reason
|
|
567
|
+
});
|
|
568
|
+
};
|
|
569
|
+
for (const path of Object.keys(entries)) if (path.startsWith(DOCPROPS_DIRECTORY_PREFIX)) markUnsupported(path, overrideContentTypes.get(path) ?? KNOWN_METADATA_CONTENT_TYPES[path] ?? GENERIC_XML_CONTENT_TYPE, "Document metadata parts are not extracted or redacted");
|
|
570
|
+
else if (isCustomXmlEntry(path)) markUnsupported(path, overrideContentTypes.get(path) ?? GENERIC_XML_CONTENT_TYPE, "Custom XML parts are not extracted or redacted");
|
|
571
|
+
for (const { contentType, path } of contentTypes) {
|
|
572
|
+
if (contentType === RELATIONSHIPS_CONTENT_TYPE || isRelationshipsEntry(path)) continue;
|
|
573
|
+
if (METADATA_CONTENT_TYPES.has(contentType)) {
|
|
574
|
+
markUnsupported(path, contentType, "Document metadata parts are not extracted or redacted");
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX)) {
|
|
578
|
+
markUnsupported(path, contentType, "WordprocessingML part type is not extracted");
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
markUnsupported(path, contentType, "Package part type is not extracted or redacted");
|
|
582
|
+
}
|
|
583
|
+
for (const path of [...Object.keys(entries), ...skippedEntryPaths]) {
|
|
584
|
+
if (path.endsWith("/") || isRelationshipsEntry(path)) continue;
|
|
585
|
+
markUnsupported(path, overrideContentTypes.get(path) ?? (path.endsWith(".xml") ? GENERIC_XML_CONTENT_TYPE : GENERIC_BINARY_CONTENT_TYPE), "Package part is not examined by anonymization");
|
|
586
|
+
}
|
|
428
587
|
return {
|
|
429
588
|
contractVersion: 1,
|
|
430
589
|
blocks,
|
|
@@ -478,10 +637,43 @@ const isUtf16Boundary = (value, offset) => {
|
|
|
478
637
|
const next = value.charCodeAt(offset);
|
|
479
638
|
return !(previous >= 55296 && previous <= 56319 && next >= 56320 && next <= 57343);
|
|
480
639
|
};
|
|
640
|
+
const escapeXmlText = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
641
|
+
const utf8ByteLength = (value) => {
|
|
642
|
+
let total = 0;
|
|
643
|
+
for (const character of value) {
|
|
644
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
645
|
+
if (codePoint <= 127) total += 1;
|
|
646
|
+
else if (codePoint <= 2047) total += 2;
|
|
647
|
+
else if (codePoint <= 65535) total += 3;
|
|
648
|
+
else total += 4;
|
|
649
|
+
}
|
|
650
|
+
return total;
|
|
651
|
+
};
|
|
652
|
+
const XML_SPACE_INSERTION_BYTES = 21;
|
|
653
|
+
const escapedXmlTextByteLength = (value) => {
|
|
654
|
+
let total = 0;
|
|
655
|
+
for (const character of value) {
|
|
656
|
+
if (character === "&") {
|
|
657
|
+
total += 5;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (character === "<" || character === ">") {
|
|
661
|
+
total += 4;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
665
|
+
if (codePoint <= 127) total += 1;
|
|
666
|
+
else if (codePoint <= 2047) total += 2;
|
|
667
|
+
else if (codePoint <= 65535) total += 3;
|
|
668
|
+
else total += 4;
|
|
669
|
+
}
|
|
670
|
+
return total;
|
|
671
|
+
};
|
|
672
|
+
const replacementByteLength = (replacement) => escapedXmlTextByteLength(replacement.replacement);
|
|
481
673
|
const validateReplacement = (replacement, blockText) => {
|
|
482
674
|
if (!Number.isSafeInteger(replacement.start) || !Number.isSafeInteger(replacement.end) || replacement.start < 0 || replacement.start >= replacement.end || replacement.end > blockText.length || !isUtf16Boundary(blockText, replacement.start) || !isUtf16Boundary(blockText, replacement.end)) throw rewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX replacement spans must be nonempty bounded integer ranges at UTF-16 boundaries");
|
|
483
675
|
if (!isValidXmlText(replacement.replacement)) throw rewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX replacement text must contain only valid XML characters");
|
|
484
|
-
if (
|
|
676
|
+
if (replacementByteLength(replacement) > 16777216) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX replacement text must not exceed ${DOCX_ENTRY_MAX_BYTES} escaped UTF-8 bytes`);
|
|
485
677
|
};
|
|
486
678
|
const coveredTextSegments = (block, replacement) => {
|
|
487
679
|
const segments = block.segments.filter(({ end, start }) => start < replacement.end && end > replacement.start);
|
|
@@ -504,11 +696,13 @@ const planBlockUpdates = (block, rewrite) => {
|
|
|
504
696
|
const originalValues = /* @__PURE__ */ new Map();
|
|
505
697
|
for (const segment of block.segments) {
|
|
506
698
|
if (segment.source !== "text") continue;
|
|
699
|
+
const original = block.text.slice(segment.start, segment.end);
|
|
507
700
|
values.set(pathKey(segment.xmlPath), {
|
|
508
701
|
path: segment.xmlPath,
|
|
509
|
-
value:
|
|
702
|
+
value: original,
|
|
703
|
+
originalByteLength: utf8ByteLength(original)
|
|
510
704
|
});
|
|
511
|
-
originalValues.set(pathKey(segment.xmlPath),
|
|
705
|
+
originalValues.set(pathKey(segment.xmlPath), original);
|
|
512
706
|
}
|
|
513
707
|
for (const replacement of replacements.toReversed()) {
|
|
514
708
|
const segments = coveredTextSegments(block, replacement);
|
|
@@ -533,7 +727,6 @@ const planBlockUpdates = (block, rewrite) => {
|
|
|
533
727
|
}
|
|
534
728
|
return [...values.entries()].filter(([key, update]) => update.value !== originalValues.get(key)).map(([, update]) => update);
|
|
535
729
|
};
|
|
536
|
-
const escapeXmlText = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
537
730
|
const requiresPreservedSpace = (value) => /^\s|\s$/u.test(value);
|
|
538
731
|
const isWordTextTag = (tag) => WORDPROCESSING_NAMESPACES.has(tag.uri) && (tag.local === "t" || tag.local === "delText");
|
|
539
732
|
const hasPreservedSpace = (tag) => Object.values(tag.attributes).some((attribute) => attribute.uri === XML_NAMESPACE && attribute.local === "space" && attribute.value === "preserve");
|
|
@@ -627,7 +820,9 @@ const rewriteDocxText = (archive, rewrites) => {
|
|
|
627
820
|
const blocksByLocation = new Map(extraction.blocks.map((block) => [docxLocationKey(block.location), block]));
|
|
628
821
|
const updatesByPart = /* @__PURE__ */ new Map();
|
|
629
822
|
const rewrittenLocations = /* @__PURE__ */ new Set();
|
|
823
|
+
const replacementBytesByPart = /* @__PURE__ */ new Map();
|
|
630
824
|
let appliedReplacementCount = 0;
|
|
825
|
+
let totalReplacementBytes = 0;
|
|
631
826
|
for (const rewrite of rewrites) {
|
|
632
827
|
const key = docxLocationKey(rewrite.location);
|
|
633
828
|
if (rewrittenLocations.has(key)) throw rewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "Each DOCX block may appear in a rewrite plan only once");
|
|
@@ -636,13 +831,38 @@ const rewriteDocxText = (archive, rewrites) => {
|
|
|
636
831
|
if (block === void 0 || !docxLocationsEqual(block.location, rewrite.location) || block.text !== rewrite.expectedText) throw rewriteError(DOCX_REWRITE_ERROR_CODES.staleExtraction, "DOCX block location or expected text no longer matches");
|
|
637
832
|
if (rewrite.replacements.length === 0) throw rewriteError(DOCX_REWRITE_ERROR_CODES.invalidReplacement, "DOCX block rewrite plans must contain at least one replacement");
|
|
638
833
|
if (appliedReplacementCount + rewrite.replacements.length > DOCX_MAX_REPLACEMENTS) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrites must not contain more than ${DOCX_MAX_REPLACEMENTS} replacements`);
|
|
834
|
+
const rewriteReplacementBytes = rewrite.replacements.reduce((total, replacement) => total + replacementByteLength(replacement), 0);
|
|
835
|
+
totalReplacementBytes += rewriteReplacementBytes;
|
|
836
|
+
if (totalReplacementBytes > 134217728) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrite replacement text must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} aggregate escaped UTF-8 bytes`);
|
|
837
|
+
const partReplacementBytes = (replacementBytesByPart.get(block.location.part.path) ?? 0) + rewriteReplacementBytes;
|
|
838
|
+
replacementBytesByPart.set(block.location.part.path, partReplacementBytes);
|
|
839
|
+
if (partReplacementBytes > 16777216) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewrite replacement text for a single part must not exceed ${DOCX_ENTRY_MAX_BYTES} aggregate escaped UTF-8 bytes`);
|
|
639
840
|
const partUpdates = updatesByPart.get(block.location.part.path) ?? /* @__PURE__ */ new Map();
|
|
640
841
|
for (const update of planBlockUpdates(block, rewrite)) partUpdates.set(pathKey(update.path), update);
|
|
641
842
|
updatesByPart.set(block.location.part.path, partUpdates);
|
|
642
843
|
appliedReplacementCount += rewrite.replacements.length;
|
|
643
844
|
}
|
|
845
|
+
let totalUpdatedNodeBytes = 0;
|
|
846
|
+
for (const updates of updatesByPart.values()) {
|
|
847
|
+
let partUpdatedNodeBytes = 0;
|
|
848
|
+
for (const update of updates.values()) partUpdatedNodeBytes += escapedXmlTextByteLength(update.value);
|
|
849
|
+
if (partUpdatedNodeBytes > 16777216) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewritten text nodes for a single part must not exceed ${DOCX_ENTRY_MAX_BYTES} escaped UTF-8 bytes`);
|
|
850
|
+
totalUpdatedNodeBytes += partUpdatedNodeBytes;
|
|
851
|
+
if (totalUpdatedNodeBytes > 134217728) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `DOCX rewritten text nodes must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} aggregate escaped UTF-8 bytes`);
|
|
852
|
+
}
|
|
644
853
|
const entries = unzipDocxArchive(archive, true);
|
|
645
854
|
if (Object.keys(entries).some((path) => path.toLowerCase().startsWith(SIGNATURE_PART_PREFIX))) throw rewriteError(DOCX_REWRITE_ERROR_CODES.unsupportedReplacement, "Digitally signed DOCX packages must be re-signed before rewriting");
|
|
855
|
+
let projectedTotalBytes = 0;
|
|
856
|
+
for (const [partPath, partBytes] of Object.entries(entries)) {
|
|
857
|
+
let projected = partBytes.byteLength;
|
|
858
|
+
const updates = updatesByPart.get(partPath);
|
|
859
|
+
if (updates !== void 0) {
|
|
860
|
+
for (const update of updates.values()) projected += escapedXmlTextByteLength(update.value) + XML_SPACE_INSERTION_BYTES - update.originalByteLength;
|
|
861
|
+
if (projected > 16777216) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `Rewritten DOCX parts must not exceed ${DOCX_ENTRY_MAX_BYTES} projected bytes`);
|
|
862
|
+
}
|
|
863
|
+
projectedTotalBytes += projected;
|
|
864
|
+
if (projectedTotalBytes > 134217728) throw rewriteError(DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded, `Rewritten DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} projected uncompressed bytes`);
|
|
865
|
+
}
|
|
646
866
|
for (const [partPath, updates] of updatesByPart) {
|
|
647
867
|
const partBytes = entries[partPath];
|
|
648
868
|
if (partBytes === void 0) throw rewriteError(DOCX_REWRITE_ERROR_CODES.staleExtraction, "DOCX source part changed after extraction");
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["WORDPROCESSING_NAMESPACES"],"sources":["../src/types.ts","../src/extract.ts","../src/location.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import { unzipSync, type UnzipFileInfo } from \"fflate\";\nimport { SaxesParser, type SaxesTagNS } from \"saxes\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_PART_TYPES,\n type DocxCoverageItem,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n type DocxPart,\n type DocxPartType,\n type DocxTextBlock,\n type DocxTextSegment,\n type DocxInlineContext,\n} from \"./types\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\nconst DOCX_MAX_ENTRIES = 4096;\nconst DOCX_MAX_TEXT_BLOCKS = 100_000;\nconst DOCX_MAX_TEXT_SEGMENTS = 1_000_000;\n\nconst CONTENT_TYPES_PATH = \"[Content_Types].xml\";\nconst ROOT_RELATIONSHIPS_PATH = \"_rels/.rels\";\nconst CONTENT_TYPES_NAMESPACE =\n \"http://schemas.openxmlformats.org/package/2006/content-types\";\nconst PACKAGE_RELATIONSHIP_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/package/relationships\",\n \"http://schemas.openxmlformats.org/package/2006/relationships\",\n]);\nconst WORDPROCESSING_CONTENT_TYPE_PREFIX =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.\";\nconst SUPPORTED_CONTENT_TYPE_SUFFIXES: Readonly<Record<string, DocxPartType>> =\n {\n \"comments+xml\": DOCX_PART_TYPES.comments,\n \"document.main+xml\": DOCX_PART_TYPES.mainDocument,\n \"endnotes+xml\": DOCX_PART_TYPES.endnotes,\n \"footer+xml\": DOCX_PART_TYPES.footer,\n \"footnotes+xml\": DOCX_PART_TYPES.footnotes,\n \"header+xml\": DOCX_PART_TYPES.header,\n };\nconst WORDPROCESSING_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/wordprocessingml/main\",\n \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n]);\nconst RELATIONSHIP_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/officeDocument/relationships\",\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n]);\nconst OFFICE_DOCUMENT_RELATIONSHIP_TYPES = new Set(\n [...RELATIONSHIP_NAMESPACES].map(\n (namespace) => `${namespace}/officeDocument`,\n ),\n);\nconst MARKUP_COMPATIBILITY_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/markup-compatibility/main\",\n \"http://schemas.openxmlformats.org/markup-compatibility/2006\",\n]);\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\ntype ContentTypePart = {\n path: string;\n contentType: string;\n};\n\ntype ElementFrame = {\n tag: SaxesTagNS;\n path: readonly number[];\n nextChildIndex: number;\n};\n\ntype MutableBlock = {\n text: string;\n segments: DocxTextSegment[];\n location: DocxTextBlock[\"location\"];\n};\n\ntype PartExtraction = {\n blocks: DocxTextBlock[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\ntype PartTextBudget = {\n segmentCount: number;\n};\n\nconst invalidPackage = (message: string): DocxExtractionError =>\n new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidPackage, message);\n\nconst assertXmlDepth = (depth: number): void => {\n if (depth < DOCX_XML_MAX_DEPTH) {\n return;\n }\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX XML must not exceed ${DOCX_XML_MAX_DEPTH} nested elements`,\n );\n};\n\nconst safeEntryPath = (name: string): boolean =>\n name.length > 0 &&\n !name.startsWith(\"/\") &&\n !name.includes(\"\\\\\") &&\n !name.split(\"/\").includes(\"..\") &&\n !name.includes(\"\\0\");\n\ntype ArchiveBudget = {\n entryCount: number;\n uncompressedBytes: number;\n};\n\ntype ArchiveFilterOptions = {\n budget: ArchiveBudget;\n file: UnzipFileInfo;\n includeAllEntries: boolean;\n};\n\nconst archiveFilter = ({\n budget,\n file,\n includeAllEntries,\n}: ArchiveFilterOptions): boolean => {\n budget.entryCount += 1;\n if (budget.entryCount > DOCX_MAX_ENTRIES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must contain at most ${DOCX_MAX_ENTRIES} entries`,\n );\n }\n if (!safeEntryPath(file.name)) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath,\n \"DOCX archive contains an unsafe entry path\",\n );\n }\n if (file.originalSize > DOCX_ENTRY_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`,\n );\n }\n budget.uncompressedBytes += file.originalSize;\n if (budget.uncompressedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`,\n );\n }\n return (\n includeAllEntries ||\n file.name === CONTENT_TYPES_PATH ||\n file.name === ROOT_RELATIONSHIPS_PATH ||\n (file.name.startsWith(\"word/\") && file.name.endsWith(\".xml\"))\n );\n};\n\nexport const unzipDocxArchive = (\n archive: Uint8Array,\n includeAllEntries = false,\n): Record<string, Uint8Array> => {\n if (archive.byteLength > DOCX_ARCHIVE_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded,\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n );\n }\n const budget: ArchiveBudget = { entryCount: 0, uncompressedBytes: 0 };\n try {\n return unzipSync(archive, {\n filter: (file) => archiveFilter({ budget, file, includeAllEntries }),\n });\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidArchive,\n \"Input is not a valid bounded DOCX ZIP archive\",\n );\n }\n};\n\nconst decodeXml = (bytes: Uint8Array, path: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX XML part is not valid UTF-8: ${path}`,\n );\n }\n};\n\nconst attributeByLocalName = (\n tag: SaxesTagNS,\n localName: string,\n namespaces?: ReadonlySet<string>,\n): string | null => {\n for (const attribute of Object.values(tag.attributes)) {\n if (\n attribute.local === localName &&\n (namespaces === undefined || namespaces.has(attribute.uri))\n ) {\n return attribute.value;\n }\n }\n return null;\n};\n\nconst parseContentTypes = (xml: string): ContentTypePart[] => {\n const parts: ContentTypePart[] = [];\n const paths = new Set<string>();\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (tag.local !== \"Override\" || tag.uri !== CONTENT_TYPES_NAMESPACE) {\n return;\n }\n const rawPath = attributeByLocalName(tag, \"PartName\");\n const contentType = attributeByLocalName(tag, \"ContentType\");\n if (rawPath === null || contentType === null) {\n throw invalidPackage(\"DOCX content-type override is incomplete\");\n }\n const path = rawPath.startsWith(\"/\") ? rawPath.slice(1) : rawPath;\n if (!safeEntryPath(path)) {\n throw invalidPackage(\"DOCX content-type override has an unsafe path\");\n }\n if (paths.has(path)) {\n throw invalidPackage(\n \"DOCX content-type overrides must have unique paths\",\n );\n }\n paths.add(path);\n parts.push({ path, contentType });\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n \"DOCX content types are not valid XML\",\n );\n }\n return parts;\n};\n\nconst parseMainDocumentTarget = (xml: string): string => {\n const targets: string[] = [];\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (\n tag.local !== \"Relationship\" ||\n !PACKAGE_RELATIONSHIP_NAMESPACES.has(tag.uri)\n ) {\n return;\n }\n const type = attributeByLocalName(tag, \"Type\");\n if (type === null || !OFFICE_DOCUMENT_RELATIONSHIP_TYPES.has(type)) {\n return;\n }\n const targetMode = attributeByLocalName(tag, \"TargetMode\");\n const rawTarget = attributeByLocalName(tag, \"Target\");\n if (targetMode === \"External\" || rawTarget === null) {\n throw invalidPackage(\"DOCX main-document relationship must be internal\");\n }\n const target = rawTarget.startsWith(\"/\") ? rawTarget.slice(1) : rawTarget;\n if (!safeEntryPath(target) || target.includes(\":\")) {\n throw invalidPackage(\n \"DOCX main-document relationship has an unsafe target\",\n );\n }\n targets.push(target);\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n \"DOCX root relationships are not valid XML\",\n );\n }\n if (targets.length !== 1) {\n throw invalidPackage(\n \"DOCX archive must contain exactly one main-document relationship\",\n );\n }\n const target = targets.at(0);\n if (target === undefined) {\n throw invalidPackage(\"DOCX main-document relationship is unavailable\");\n }\n return target;\n};\n\nconst classifyPart = ({\n contentType,\n path,\n}: ContentTypePart): DocxPart | null => {\n if (!contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX)) {\n return null;\n }\n const suffix = contentType.slice(WORDPROCESSING_CONTENT_TYPE_PREFIX.length);\n const type = SUPPORTED_CONTENT_TYPE_SUFFIXES[suffix];\n return type === undefined ? null : { type, path };\n};\n\nconst isWordTag = (tag: SaxesTagNS, local: string): boolean =>\n tag.local === local && WORDPROCESSING_NAMESPACES.has(tag.uri);\n\nconst frameByLocalName = (\n stack: readonly ElementFrame[],\n local: string,\n): ElementFrame | null => {\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n const frame = stack.at(index);\n if (frame !== undefined && isWordTag(frame.tag, local)) {\n return frame;\n }\n }\n return null;\n};\n\nconst blockLocation = (\n part: DocxPart,\n blockIndex: number,\n paragraphPath: readonly number[],\n stack: readonly ElementFrame[],\n): DocxTextBlock[\"location\"] => {\n const textBox = frameByLocalName(stack, \"txbxContent\");\n if (textBox !== null) {\n return {\n type: \"text-box-paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n textBoxPath: textBox.path,\n };\n }\n const cell = frameByLocalName(stack, \"tc\");\n const row = frameByLocalName(stack, \"tr\");\n const table = frameByLocalName(stack, \"tbl\");\n if (cell !== null && row !== null && table !== null) {\n return {\n type: \"table-cell-paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n tablePath: table.path,\n rowPath: row.path,\n cellPath: cell.path,\n };\n }\n return {\n type: \"paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n };\n};\n\nconst revisionForTag = (\n tag: SaxesTagNS,\n): Extract<DocxInlineContext, { type: \"revision\" }> | null => {\n if (!WORDPROCESSING_NAMESPACES.has(tag.uri)) {\n return null;\n }\n const revisions: Readonly<\n Record<string, \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\">\n > = {\n del: \"deletion\",\n ins: \"insertion\",\n moveFrom: \"move-from\",\n moveTo: \"move-to\",\n };\n const revision = revisions[tag.local];\n return revision === undefined ? null : { type: \"revision\", revision };\n};\n\nconst inlineContexts = (\n stack: readonly ElementFrame[],\n): DocxInlineContext[] => {\n const contexts: DocxInlineContext[] = [];\n for (const { tag } of stack) {\n if (isWordTag(tag, \"hyperlink\")) {\n contexts.push({\n type: \"hyperlink\",\n relationshipId: attributeByLocalName(\n tag,\n \"id\",\n RELATIONSHIP_NAMESPACES,\n ),\n anchor: attributeByLocalName(tag, \"anchor\", WORDPROCESSING_NAMESPACES),\n });\n }\n const revision = revisionForTag(tag);\n if (revision !== null) {\n contexts.push(revision);\n }\n }\n return contexts;\n};\n\nconst appendSegment = (\n block: MutableBlock,\n budget: PartTextBudget,\n value: string,\n source: DocxTextSegment[\"source\"],\n path: readonly number[],\n stack: readonly ElementFrame[],\n): void => {\n if (value.length === 0) {\n return;\n }\n if (budget.segmentCount >= DOCX_MAX_TEXT_SEGMENTS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX parts must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`,\n );\n }\n budget.segmentCount += 1;\n const start = block.text.length;\n block.text += value;\n block.segments.push({\n start,\n end: block.text.length,\n source,\n contexts: inlineContexts(stack),\n xmlPath: path,\n });\n};\n\nconst extractPart = (part: DocxPart, xml: string): PartExtraction => {\n const blocks: DocxTextBlock[] = [];\n const stack: ElementFrame[] = [];\n const blockStack: MutableBlock[] = [];\n let nextBlockIndex = 0;\n let currentText = \"\";\n let currentTextPath: readonly number[] | null = null;\n let parseError: Error | null = null;\n let unsupportedSymbolCount = 0;\n let unsupportedFieldInstructionCount = 0;\n let unsupportedAlternateContentCount = 0;\n const textBudget: PartTextBudget = { segmentCount: 0 };\n\n const parser = new SaxesParser({ xmlns: true });\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(stack.length);\n const parent = stack.at(-1);\n const childIndex = parent?.nextChildIndex ?? 0;\n if (parent !== undefined) {\n parent.nextChildIndex += 1;\n }\n const path = [...(parent?.path ?? []), childIndex];\n if (isWordTag(tag, \"p\")) {\n if (nextBlockIndex >= DOCX_MAX_TEXT_BLOCKS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX parts must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`,\n );\n }\n blockStack.push({\n text: \"\",\n segments: [],\n location: blockLocation(part, nextBlockIndex, path, stack),\n });\n nextBlockIndex += 1;\n }\n stack.push({ tag, path, nextChildIndex: 0 });\n if (isWordTag(tag, \"t\") || isWordTag(tag, \"delText\")) {\n currentText = \"\";\n currentTextPath = path;\n }\n const currentBlock = blockStack.at(-1);\n if (currentBlock !== undefined && isWordTag(tag, \"tab\")) {\n appendSegment(currentBlock, textBudget, \"\\t\", \"tab\", path, stack);\n }\n if (\n currentBlock !== undefined &&\n (isWordTag(tag, \"br\") || isWordTag(tag, \"cr\"))\n ) {\n appendSegment(currentBlock, textBudget, \"\\n\", \"break\", path, stack);\n }\n if (isWordTag(tag, \"sym\")) {\n unsupportedSymbolCount += 1;\n }\n if (isWordTag(tag, \"instrText\") || isWordTag(tag, \"fldSimple\")) {\n unsupportedFieldInstructionCount += 1;\n }\n if (\n tag.local === \"AlternateContent\" &&\n MARKUP_COMPATIBILITY_NAMESPACES.has(tag.uri)\n ) {\n unsupportedAlternateContentCount += 1;\n }\n });\n parser.on(\"text\", (text) => {\n if (currentTextPath !== null) {\n currentText += text;\n }\n });\n parser.on(\"cdata\", (text) => {\n if (currentTextPath !== null) {\n currentText += text;\n }\n });\n parser.on(\"closetag\", (tag) => {\n const frame = stack.at(-1);\n if (frame === undefined || frame.tag !== tag) {\n throw invalidPackage(\"DOCX XML element stack is inconsistent\");\n }\n if (\n currentTextPath !== null &&\n (isWordTag(tag, \"t\") || isWordTag(tag, \"delText\"))\n ) {\n const currentBlock = blockStack.at(-1);\n if (currentBlock === undefined) {\n if (currentText.length > 0) {\n throw invalidPackage(\"DOCX text is outside a paragraph\");\n }\n } else {\n appendSegment(\n currentBlock,\n textBudget,\n currentText,\n \"text\",\n currentTextPath,\n stack,\n );\n }\n currentText = \"\";\n currentTextPath = null;\n }\n if (isWordTag(tag, \"p\")) {\n const completedBlock = blockStack.pop();\n if (completedBlock === undefined) {\n throw invalidPackage(\"DOCX paragraph state is unavailable\");\n }\n blocks.push(completedBlock);\n }\n stack.pop();\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX part is not valid XML: ${part.path}`,\n );\n }\n\n blocks.sort(\n (left, right) => left.location.blockIndex - right.location.blockIndex,\n );\n\n let hyperlinkTextSegmentCount = 0;\n let revisionTextSegmentCount = 0;\n for (const { segments } of blocks) {\n for (const { contexts } of segments) {\n if (contexts.some((context) => context.type === \"hyperlink\")) {\n hyperlinkTextSegmentCount += 1;\n }\n if (contexts.some((context) => context.type === \"revision\")) {\n revisionTextSegmentCount += 1;\n }\n }\n }\n return {\n blocks,\n hyperlinkTextSegmentCount,\n revisionTextSegmentCount,\n unsupportedAlternateContentCount,\n unsupportedSymbolCount,\n unsupportedFieldInstructionCount,\n };\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n const entries = unzipDocxArchive(archive);\n const contentTypesBytes = entries[CONTENT_TYPES_PATH];\n if (contentTypesBytes === undefined) {\n throw invalidPackage(\"DOCX archive is missing [Content_Types].xml\");\n }\n const contentTypes = parseContentTypes(\n decodeXml(contentTypesBytes, CONTENT_TYPES_PATH),\n );\n const rootRelationshipsBytes = entries[ROOT_RELATIONSHIPS_PATH];\n if (rootRelationshipsBytes === undefined) {\n throw invalidPackage(\"DOCX archive is missing _rels/.rels\");\n }\n const mainDocumentTarget = parseMainDocumentTarget(\n decodeXml(rootRelationshipsBytes, ROOT_RELATIONSHIPS_PATH),\n );\n const supportedParts = contentTypes\n .map(classifyPart)\n .filter((part): part is DocxPart => part !== null);\n if (\n supportedParts.filter((part) => part.type === DOCX_PART_TYPES.mainDocument)\n .length !== 1\n ) {\n throw invalidPackage(\"DOCX archive must contain exactly one main document\");\n }\n const mainDocument = supportedParts.find(\n (part) => part.type === DOCX_PART_TYPES.mainDocument,\n );\n if (mainDocument?.path !== mainDocumentTarget) {\n throw invalidPackage(\n \"DOCX main-document relationship and content type do not agree\",\n );\n }\n\n const blocks: DocxTextBlock[] = [];\n const coverageParts: DocxCoverageItem[] = [];\n let hyperlinkTextSegmentCount = 0;\n let revisionTextSegmentCount = 0;\n let unsupportedSymbolCount = 0;\n let unsupportedFieldInstructionCount = 0;\n let unsupportedAlternateContentCount = 0;\n let textSegmentCount = 0;\n for (const part of supportedParts) {\n const bytes = entries[part.path];\n if (bytes === undefined) {\n throw invalidPackage(\n `DOCX archive is missing declared part: ${part.path}`,\n );\n }\n const extracted = extractPart(part, decodeXml(bytes, part.path));\n if (blocks.length + extracted.blocks.length > DOCX_MAX_TEXT_BLOCKS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`,\n );\n }\n const extractedSegmentCount = extracted.blocks.reduce(\n (count, block) => count + block.segments.length,\n 0,\n );\n if (textSegmentCount + extractedSegmentCount > DOCX_MAX_TEXT_SEGMENTS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`,\n );\n }\n textSegmentCount += extractedSegmentCount;\n blocks.push(...extracted.blocks);\n coverageParts.push({\n status: \"extracted\",\n part,\n blockCount: extracted.blocks.length,\n });\n hyperlinkTextSegmentCount += extracted.hyperlinkTextSegmentCount;\n revisionTextSegmentCount += extracted.revisionTextSegmentCount;\n unsupportedSymbolCount += extracted.unsupportedSymbolCount;\n unsupportedFieldInstructionCount +=\n extracted.unsupportedFieldInstructionCount;\n unsupportedAlternateContentCount +=\n extracted.unsupportedAlternateContentCount;\n }\n\n for (const { contentType, path } of contentTypes) {\n if (\n contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX) &&\n classifyPart({ contentType, path }) === null\n ) {\n coverageParts.push({\n status: \"unsupported\",\n path,\n contentType,\n reason: \"WordprocessingML part type is not extracted\",\n });\n }\n }\n\n return {\n contractVersion: DOCX_EXTRACTION_CONTRACT_VERSION,\n blocks,\n coverage: {\n parts: coverageParts,\n hyperlinkTextSegmentCount,\n revisionTextSegmentCount,\n unsupportedAlternateContentCount,\n unsupportedSymbolCount,\n unsupportedFieldInstructionCount,\n },\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { strToU8, zipSync } from \"fflate\";\nimport { SaxesParser, type SaxesTagNS } from \"saxes\";\n\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_ENTRY_MAX_BYTES,\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n extractDocxText,\n unzipDocxArchive,\n} from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport {\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n type DocxTextBlock,\n type DocxTextReplacement,\n type DocxTextSegment,\n} from \"./types\";\n\nconst WORDPROCESSING_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/wordprocessingml/main\",\n \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n]);\nconst XML_NAMESPACE = \"http://www.w3.org/XML/1998/namespace\";\nconst DOCX_MAX_REPLACEMENTS = 1_000_000;\nconst SIGNATURE_PART_PREFIX = \"_xmlsignatures/\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\ntype ElementFrame = {\n path: readonly number[];\n nextChildIndex: number;\n};\n\ntype TextNodeUpdate = {\n path: readonly number[];\n value: string;\n};\n\ntype XmlPatch = {\n start: number;\n end: number;\n value: string;\n};\n\nconst rewriteError = (\n code: DocxRewriteErrorCode,\n message: string,\n): DocxRewriteError => new DocxRewriteError(code, message);\n\nconst pathKey = (path: readonly number[]): string => path.join(\".\");\n\nconst isValidXmlText = (value: string): boolean => {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (\n codePoint === undefined ||\n (codePoint !== 0x09 &&\n codePoint !== 0x0a &&\n codePoint !== 0x0d &&\n (codePoint < 0x20 ||\n (codePoint > 0xd7ff && codePoint < 0xe000) ||\n (codePoint > 0xfffd && codePoint < 0x10_000) ||\n codePoint > 0x10_ffff))\n ) {\n return false;\n }\n }\n return true;\n};\n\nconst isUtf16Boundary = (value: string, offset: number): boolean => {\n if (offset === 0 || offset === value.length) {\n return true;\n }\n const previous = value.charCodeAt(offset - 1);\n const next = value.charCodeAt(offset);\n return !(\n previous >= 0xd800 &&\n previous <= 0xdbff &&\n next >= 0xdc00 &&\n next <= 0xdfff\n );\n};\n\nconst validateReplacement = (\n replacement: DocxTextReplacement,\n blockText: string,\n): void => {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < 0 ||\n replacement.start >= replacement.end ||\n replacement.end > blockText.length ||\n !isUtf16Boundary(blockText, replacement.start) ||\n !isUtf16Boundary(blockText, replacement.end)\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement spans must be nonempty bounded integer ranges at UTF-16 boundaries\",\n );\n }\n if (!isValidXmlText(replacement.replacement)) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement text must contain only valid XML characters\",\n );\n }\n if (strToU8(replacement.replacement).byteLength > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX replacement text must not exceed ${DOCX_ENTRY_MAX_BYTES} UTF-8 bytes`,\n );\n }\n};\n\nconst coveredTextSegments = (\n block: DocxTextBlock,\n replacement: DocxTextReplacement,\n): readonly DocxTextSegment[] => {\n const segments = block.segments.filter(\n ({ end, start }) => start < replacement.end && end > replacement.start,\n );\n let cursor = replacement.start;\n for (const segment of segments) {\n if (\n segment.source !== \"text\" ||\n segment.start > cursor ||\n segment.contexts.some((context) => context.type === \"revision\")\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacements must stay within contiguous non-revision text segments\",\n );\n }\n cursor = Math.min(replacement.end, segment.end);\n }\n if (segments.length === 0 || cursor !== replacement.end) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacements must stay within contiguous non-revision text segments\",\n );\n }\n return segments;\n};\n\nconst planBlockUpdates = (\n block: DocxTextBlock,\n rewrite: DocxBlockRewrite,\n): TextNodeUpdate[] => {\n const replacements = [...rewrite.replacements].sort(\n (left, right) => left.start - right.start,\n );\n for (const [index, replacement] of replacements.entries()) {\n validateReplacement(replacement, block.text);\n const previous = index === 0 ? undefined : replacements.at(index - 1);\n if (previous !== undefined && previous.end > replacement.start) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement spans must not overlap\",\n );\n }\n }\n\n const values = new Map<string, TextNodeUpdate>();\n const originalValues = new Map<string, string>();\n for (const segment of block.segments) {\n if (segment.source !== \"text\") {\n continue;\n }\n values.set(pathKey(segment.xmlPath), {\n path: segment.xmlPath,\n value: block.text.slice(segment.start, segment.end),\n });\n originalValues.set(\n pathKey(segment.xmlPath),\n block.text.slice(segment.start, segment.end),\n );\n }\n\n for (const replacement of replacements.toReversed()) {\n const segments = coveredTextSegments(block, replacement);\n const first = segments.at(0);\n const last = segments.at(-1);\n if (first === undefined || last === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacement text segments are unavailable\",\n );\n }\n const firstUpdate = values.get(pathKey(first.xmlPath));\n const lastUpdate = values.get(pathKey(last.xmlPath));\n if (firstUpdate === undefined || lastUpdate === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacement text nodes are unavailable\",\n );\n }\n const firstStart = replacement.start - first.start;\n const lastEnd = replacement.end - last.start;\n if (first === last) {\n firstUpdate.value =\n firstUpdate.value.slice(0, firstStart) +\n replacement.replacement +\n firstUpdate.value.slice(lastEnd);\n continue;\n }\n firstUpdate.value =\n firstUpdate.value.slice(0, firstStart) + replacement.replacement;\n for (const segment of segments.slice(1, -1)) {\n const update = values.get(pathKey(segment.xmlPath));\n if (update !== undefined) {\n update.value = \"\";\n }\n }\n lastUpdate.value = lastUpdate.value.slice(lastEnd);\n }\n return [...values.entries()]\n .filter(([key, update]) => update.value !== originalValues.get(key))\n .map(([, update]) => update);\n};\n\nconst escapeXmlText = (value: string): string =>\n value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n\nconst requiresPreservedSpace = (value: string): boolean =>\n /^\\s|\\s$/u.test(value);\n\nconst isWordTextTag = (tag: SaxesTagNS): boolean =>\n WORDPROCESSING_NAMESPACES.has(tag.uri) &&\n (tag.local === \"t\" || tag.local === \"delText\");\n\nconst hasPreservedSpace = (tag: SaxesTagNS): boolean =>\n Object.values(tag.attributes).some(\n (attribute) =>\n attribute.uri === XML_NAMESPACE &&\n attribute.local === \"space\" &&\n attribute.value === \"preserve\",\n );\n\ntype FindClosingTagStartOptions = {\n xml: string;\n contentStart: number;\n parserPosition: number;\n};\n\nconst findClosingTagStart = ({\n xml,\n contentStart,\n parserPosition,\n}: FindClosingTagStartOptions): number => {\n for (let index = parserPosition - 1; index >= contentStart; index -= 1) {\n if (xml[index] === \"<\" && xml[index + 1] === \"/\") {\n return index;\n }\n }\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX text-node closing tag changed after extraction\",\n );\n};\n\nconst rewritePartXml = (\n xml: string,\n updates: readonly TextNodeUpdate[],\n): string => {\n const updatesByPath = new Map(\n updates.map((update) => [pathKey(update.path), update]),\n );\n const foundPaths = new Set<string>();\n const patches: XmlPatch[] = [];\n const stack: ElementFrame[] = [];\n let activeText:\n | { key: string; contentStart: number; tag: SaxesTagNS }\n | undefined;\n let parseError: Error | null = null;\n const parser = new SaxesParser({ xmlns: true });\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"opentag\", (tag) => {\n if (stack.length >= DOCX_XML_MAX_DEPTH) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX XML must not exceed ${DOCX_XML_MAX_DEPTH} nested elements`,\n );\n }\n const parent = stack.at(-1);\n const childIndex = parent?.nextChildIndex ?? 0;\n if (parent !== undefined) {\n parent.nextChildIndex += 1;\n }\n const path = [...(parent?.path ?? []), childIndex];\n stack.push({ path, nextChildIndex: 0 });\n const key = pathKey(path);\n if (isWordTextTag(tag) && updatesByPath.has(key)) {\n if (tag.isSelfClosing) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX self-closing text nodes cannot receive replacements\",\n );\n }\n activeText = { key, contentStart: parser.position, tag };\n }\n });\n parser.on(\"closetag\", (tag) => {\n if (activeText?.tag === tag) {\n const update = updatesByPath.get(activeText.key);\n if (update !== undefined) {\n const contentEnd = findClosingTagStart({\n xml,\n contentStart: activeText.contentStart,\n parserPosition: parser.position,\n });\n patches.push({\n start: activeText.contentStart,\n end: contentEnd,\n value: escapeXmlText(update.value),\n });\n if (requiresPreservedSpace(update.value) && !hasPreservedSpace(tag)) {\n patches.push({\n start: activeText.contentStart - 1,\n end: activeText.contentStart - 1,\n value: ' xml:space=\"preserve\"',\n });\n }\n foundPaths.add(activeText.key);\n }\n activeText = undefined;\n }\n stack.pop();\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX source XML changed after extraction\",\n );\n }\n if (foundPaths.size !== updatesByPath.size) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX text-node locations changed after extraction\",\n );\n }\n let rewritten = xml;\n for (const patch of patches.toSorted(\n (left, right) => right.start - left.start,\n )) {\n rewritten =\n rewritten.slice(0, patch.start) +\n patch.value +\n rewritten.slice(patch.end);\n }\n return rewritten;\n};\n\nconst assertArchiveBudgets = (entries: Record<string, Uint8Array>): void => {\n let totalBytes = 0;\n for (const bytes of Object.values(entries)) {\n if (bytes.byteLength > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`,\n );\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`,\n );\n }\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const extraction = extractDocxText(archive);\n if (rewrites.length === 0) {\n return {\n document: archive.slice(),\n rewrittenBlockCount: 0,\n appliedReplacementCount: 0,\n };\n }\n const blocksByLocation = new Map(\n extraction.blocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const updatesByPart = new Map<string, Map<string, TextNodeUpdate>>();\n const rewrittenLocations = new Set<string>();\n let appliedReplacementCount = 0;\n\n for (const rewrite of rewrites) {\n const key = docxLocationKey(rewrite.location);\n if (rewrittenLocations.has(key)) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"Each DOCX block may appear in a rewrite plan only once\",\n );\n }\n rewrittenLocations.add(key);\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, rewrite.location) ||\n block.text !== rewrite.expectedText\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX block location or expected text no longer matches\",\n );\n }\n if (rewrite.replacements.length === 0) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite plans must contain at least one replacement\",\n );\n }\n if (\n appliedReplacementCount + rewrite.replacements.length >\n DOCX_MAX_REPLACEMENTS\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_MAX_REPLACEMENTS} replacements`,\n );\n }\n const partUpdates =\n updatesByPart.get(block.location.part.path) ?? new Map();\n for (const update of planBlockUpdates(block, rewrite)) {\n partUpdates.set(pathKey(update.path), update);\n }\n updatesByPart.set(block.location.part.path, partUpdates);\n appliedReplacementCount += rewrite.replacements.length;\n }\n\n const entries = unzipDocxArchive(archive, true);\n if (\n Object.keys(entries).some((path) =>\n path.toLowerCase().startsWith(SIGNATURE_PART_PREFIX),\n )\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"Digitally signed DOCX packages must be re-signed before rewriting\",\n );\n }\n for (const [partPath, updates] of updatesByPart) {\n const partBytes = entries[partPath];\n if (partBytes === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX source part changed after extraction\",\n );\n }\n const xml = new TextDecoder(\"utf-8\", { fatal: true }).decode(partBytes);\n entries[partPath] = strToU8(rewritePartXml(xml, [...updates.values()]));\n }\n assertArchiveBudgets(entries);\n const document = zipSync(entries);\n if (document.byteLength > DOCX_ARCHIVE_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n );\n }\n return {\n document,\n rewrittenBlockCount: rewrites.length,\n appliedReplacementCount,\n };\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nconst DOCX_RESTORE_MAX_PLACEHOLDER_UTF16 = 512;\nconst DOCX_RESTORE_MAX_CANDIDATES = 1_000_000;\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\nconst encodedSessionNamespace = (sessionId: string): string =>\n sessionId.replaceAll(\"_\", \"%5F\");\n\nconst isOwnedPlaceholderCandidate = (\n value: string,\n encodedSessionId: string,\n): boolean => {\n const inner = value.endsWith(\"]\") ? value.slice(0, -1) : value;\n const countSeparator = inner.lastIndexOf(\"_\");\n if (countSeparator <= 0) {\n return false;\n }\n const prefix = inner.slice(0, countSeparator);\n const namespaceSeparator = prefix.lastIndexOf(\"_\");\n if (namespaceSeparator <= 0) {\n return false;\n }\n return prefix.slice(namespaceSeparator + 1) === encodedSessionId;\n};\n\ntype PlanBlockRestorationOptions = {\n text: string;\n encodedSessionId: string;\n restoreCandidate: (candidate: string) => string;\n budget: { candidateCount: number };\n};\n\nconst planBlockRestoration = ({\n text,\n encodedSessionId,\n restoreCandidate,\n budget,\n}: PlanBlockRestorationOptions): DocxTextReplacement[] => {\n const replacements: DocxTextReplacement[] = [];\n let start: number | undefined;\n for (let cursor = 0; cursor < text.length; cursor += 1) {\n const character = text.at(cursor);\n if (character === \"[\") {\n if (\n start !== undefined &&\n isOwnedPlaceholderCandidate(\n text.slice(start + 1, cursor),\n encodedSessionId,\n )\n ) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an incomplete placeholder for the expected session\",\n );\n }\n start = cursor;\n continue;\n }\n if (character !== \"]\" || start === undefined) {\n continue;\n }\n const candidateEnd = cursor + 1;\n const candidate = text.slice(start, candidateEnd);\n budget.candidateCount += 1;\n if (budget.candidateCount > DOCX_RESTORE_MAX_CANDIDATES) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n `DOCX restoration must not inspect more than ${DOCX_RESTORE_MAX_CANDIDATES} placeholder candidates`,\n );\n }\n const isOwned = isOwnedPlaceholderCandidate(\n candidate.slice(1),\n encodedSessionId,\n );\n if (candidate.length > DOCX_RESTORE_MAX_PLACEHOLDER_UTF16) {\n if (isOwned) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX session placeholder exceeds the maximum length\",\n );\n }\n start = undefined;\n continue;\n }\n if (!isOwned) {\n start = undefined;\n continue;\n }\n const replacement = restoreCandidate(candidate);\n if (replacement !== candidate) {\n replacements.push({ start, end: candidateEnd, replacement });\n } else {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n start = undefined;\n }\n if (\n start !== undefined &&\n isOwnedPlaceholderCandidate(text.slice(start + 1), encodedSessionId)\n ) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an incomplete placeholder for the expected session\",\n );\n }\n return replacements;\n};\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const encodedSessionId = encodedSessionNamespace(sessionId);\n const extraction = extractDocxText(document);\n const rewrites: DocxBlockRewrite[] = [];\n const budget = { candidateCount: 0 };\n let restoredPlaceholderCount = 0;\n for (const block of extraction.blocks) {\n const replacements = planBlockRestoration({\n text: block.text,\n encodedSessionId,\n restoreCandidate,\n budget,\n });\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(extraction.coverage),\n };\n};\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = 1_000_000;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["WORDPROCESSING_NAMESPACES"],"sources":["../src/types.ts","../src/extract.ts","../src/location.ts","../src/rewrite.ts","../src/coverage.ts","../src/restore.ts","../src/anonymize.ts"],"sourcesContent":["import type {\n NativeCallerDetection,\n NativeOperatorConfig,\n NativeSessionBlockRedactionPlan,\n NativeSessionCallerRedactionPlanOptions,\n} from \"@stll/anonymize\";\n\nexport const DOCX_PART_TYPES = {\n comments: \"comments\",\n endnotes: \"endnotes\",\n footer: \"footer\",\n footnotes: \"footnotes\",\n header: \"header\",\n mainDocument: \"main-document\",\n} as const;\n\nexport type DocxPartType =\n (typeof DOCX_PART_TYPES)[keyof typeof DOCX_PART_TYPES];\n\nexport type DocxPart = {\n type: DocxPartType;\n path: string;\n};\n\ntype DocxBaseBlockLocation = {\n part: DocxPart;\n blockIndex: number;\n xmlPath: readonly number[];\n};\n\nexport type DocxBlockLocation =\n | (DocxBaseBlockLocation & {\n type: \"paragraph\";\n })\n | (DocxBaseBlockLocation & {\n type: \"table-cell-paragraph\";\n tablePath: readonly number[];\n rowPath: readonly number[];\n cellPath: readonly number[];\n })\n | (DocxBaseBlockLocation & {\n type: \"text-box-paragraph\";\n textBoxPath: readonly number[];\n });\n\nexport type DocxInlineContext =\n | {\n type: \"hyperlink\";\n relationshipId: string | null;\n anchor: string | null;\n }\n | {\n type: \"revision\";\n revision: \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\";\n };\n\nexport type DocxTextSegment = {\n start: number;\n end: number;\n source: \"break\" | \"tab\" | \"text\";\n contexts: readonly DocxInlineContext[];\n xmlPath: readonly number[];\n};\n\nexport type DocxTextBlock = {\n text: string;\n location: DocxBlockLocation;\n segments: readonly DocxTextSegment[];\n};\n\nexport type DocxCoverageItem =\n | {\n status: \"extracted\";\n part: DocxPart;\n blockCount: number;\n }\n | {\n status: \"unsupported\";\n path: string;\n contentType: string;\n reason: string;\n };\n\nexport type DocxCoverage = {\n parts: readonly DocxCoverageItem[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxExtraction = {\n contractVersion: 1;\n blocks: readonly DocxTextBlock[];\n coverage: DocxCoverage;\n};\n\nexport type DocxTextReplacement = {\n start: number;\n end: number;\n replacement: string;\n};\n\nexport type DocxBlockRewrite = {\n location: DocxBlockLocation;\n expectedText: string;\n replacements: readonly DocxTextReplacement[];\n};\n\nexport type DocxRewriteResult = {\n document: Uint8Array;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n};\n\nexport const DOCX_COVERAGE_MODES = {\n allowPartial: \"allow-partial\",\n requireFull: \"require-full\",\n} as const;\n\nexport type DocxCoverageMode =\n (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n\nexport type DocxCoveragePolicy =\n | { mode: typeof DOCX_COVERAGE_MODES.requireFull }\n | { mode: typeof DOCX_COVERAGE_MODES.allowPartial };\n\nexport type DocxAnonymizationPolicy = {\n coverage: DocxCoveragePolicy;\n operators?: NativeOperatorConfig;\n};\n\nexport type DocxCallerDetection = NativeCallerDetection;\n\nexport type DocxBlockCallerDetections = {\n location: DocxBlockLocation;\n expectedText: string;\n detections: readonly DocxCallerDetection[];\n};\n\nexport type DocxSessionRedactionPlan = {\n blocks: readonly NativeSessionBlockRedactionPlan[];\n commit: () => void;\n};\n\nexport type DocxAnonymizationSession = {\n sessionId: () => string;\n planTextBatchWithCallerDetections: (\n options: NativeSessionCallerRedactionPlanOptions,\n ) => DocxSessionRedactionPlan;\n};\n\nexport type AnonymizeDocxOptions = {\n document: Uint8Array;\n session: DocxAnonymizationSession;\n expectedSessionId: string;\n policy: DocxAnonymizationPolicy;\n callerDetections?: readonly DocxBlockCallerDetections[];\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxCoverageSummary = {\n extractedPartCount: number;\n unsupportedPartCount: number;\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\nexport type DocxWorkflowCoverage =\n | { status: \"full\"; counts: DocxCoverageSummary }\n | { status: \"partial\"; counts: DocxCoverageSummary };\n\nexport type DocxAnonymizationSummary = {\n contractVersion: 1;\n sessionId: string;\n blockCount: number;\n rewrittenBlockCount: number;\n appliedReplacementCount: number;\n entityCount: number;\n callerDetectionCount: number;\n retainedCallerDetectionCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport type DocxAnonymizationResult = {\n document: Uint8Array;\n summary: DocxAnonymizationSummary;\n};\n\nexport const DOCX_ANONYMIZATION_ERROR_CODES = {\n incompleteCoverage: \"incomplete-coverage\",\n invalidCallerDetections: \"invalid-caller-detections\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxAnonymizationErrorCode =\n (typeof DOCX_ANONYMIZATION_ERROR_CODES)[keyof typeof DOCX_ANONYMIZATION_ERROR_CODES];\n\nexport type DocxRestorationSession = {\n sessionId: () => string;\n restoreText: (text: string, observedAtEpochSeconds?: number) => string;\n};\n\nexport type RestoreDocxTextOptions = {\n document: Uint8Array;\n session: DocxRestorationSession;\n expectedSessionId: string;\n observedAtEpochSeconds?: number;\n};\n\nexport type DocxRestorationResult = {\n document: Uint8Array;\n sessionId: string;\n restoredBlockCount: number;\n restoredPlaceholderCount: number;\n coverage: DocxWorkflowCoverage;\n};\n\nexport const DOCX_RESTORATION_ERROR_CODES = {\n invalidPlaceholder: \"invalid-placeholder\",\n invalidSession: \"invalid-session\",\n restorationLimitExceeded: \"restoration-limit-exceeded\",\n sessionMismatch: \"session-mismatch\",\n} as const;\n\nexport type DocxRestorationErrorCode =\n (typeof DOCX_RESTORATION_ERROR_CODES)[keyof typeof DOCX_RESTORATION_ERROR_CODES];\n\nexport const DOCX_REWRITE_ERROR_CODES = {\n invalidReplacement: \"invalid-replacement\",\n rewriteLimitExceeded: \"rewrite-limit-exceeded\",\n staleExtraction: \"stale-extraction\",\n unsupportedReplacement: \"unsupported-replacement\",\n} as const;\n\nexport type DocxRewriteErrorCode =\n (typeof DOCX_REWRITE_ERROR_CODES)[keyof typeof DOCX_REWRITE_ERROR_CODES];\n\nexport const DOCX_EXTRACTION_ERROR_CODES = {\n archiveLimitExceeded: \"archive-limit-exceeded\",\n invalidArchive: \"invalid-archive\",\n invalidPackage: \"invalid-package\",\n invalidXml: \"invalid-xml\",\n unsafeEntryPath: \"unsafe-entry-path\",\n uncompressedLimitExceeded: \"uncompressed-limit-exceeded\",\n} as const;\n\nexport type DocxExtractionErrorCode =\n (typeof DOCX_EXTRACTION_ERROR_CODES)[keyof typeof DOCX_EXTRACTION_ERROR_CODES];\n","import { unzipSync, type UnzipFileInfo } from \"fflate\";\nimport { SaxesParser, type SaxesTagNS } from \"saxes\";\n\nimport {\n DOCX_EXTRACTION_ERROR_CODES,\n DOCX_PART_TYPES,\n type DocxCoverageItem,\n type DocxExtraction,\n type DocxExtractionErrorCode,\n type DocxPart,\n type DocxPartType,\n type DocxTextBlock,\n type DocxTextSegment,\n type DocxInlineContext,\n} from \"./types\";\n\nexport const DOCX_EXTRACTION_CONTRACT_VERSION = 1 as const;\nexport const DOCX_ARCHIVE_MAX_BYTES = 64 * 1024 * 1024;\nexport const DOCX_ENTRY_MAX_BYTES = 16 * 1024 * 1024;\nexport const DOCX_UNCOMPRESSED_MAX_BYTES = 128 * 1024 * 1024;\nexport const DOCX_XML_MAX_DEPTH = 256;\nconst DOCX_MAX_ENTRIES = 4096;\nconst DOCX_MAX_TEXT_BLOCKS = 100_000;\nconst DOCX_MAX_TEXT_SEGMENTS = 1_000_000;\n// Bounds the aggregate cost of inlineContexts() stack scans (segmentCount x\n// stack depth). Each scan is O(depth) regardless of how many segments are\n// produced, so segmentCount and depth budgets alone leave their product\n// unbounded (up to DOCX_MAX_TEXT_SEGMENTS x DOCX_XML_MAX_DEPTH = 256e6 scans\n// from a deep, wide crafted document). This ceiling is far above realistic\n// documents (depth is typically well under 30) but well below the\n// pathological worst case.\nconst DOCX_MAX_INLINE_CONTEXT_SCAN_OPS = 20_000_000;\n\nconst CONTENT_TYPES_PATH = \"[Content_Types].xml\";\nconst ROOT_RELATIONSHIPS_PATH = \"_rels/.rels\";\nconst DOCX_CORE_PROPERTIES_PATH = \"docProps/core.xml\";\nconst DOCX_APP_PROPERTIES_PATH = \"docProps/app.xml\";\nconst DOCX_CUSTOM_PROPERTIES_PATH = \"docProps/custom.xml\";\nconst CUSTOM_XML_DIRECTORY_PREFIX = \"customXml/\";\nconst DOCPROPS_DIRECTORY_PREFIX = \"docProps/\";\n// Metadata/properties content types that carry document PII (dc:creator,\n// cp:lastModifiedBy, custom properties) wherever the part lives — a\n// properties part is not required to sit at the conventional docProps/*\n// path, so coverage must also key on the declared content type.\nconst METADATA_CONTENT_TYPES = new Set([\n \"application/vnd.openxmlformats-package.core-properties+xml\",\n \"application/vnd.openxmlformats-officedocument.extended-properties+xml\",\n \"application/vnd.openxmlformats-officedocument.custom-properties+xml\",\n]);\n// RFC 3986 scheme prefix (\"mailto:\", \"https:\", \"file:\", even \"c:\"). Any\n// target carrying a scheme is addressed outside the package, as is a\n// protocol-relative \"//host/...\" target.\nconst ABSOLUTE_URI_PATTERN = /^[a-z][a-z0-9+.-]*:/iu;\n// Fallback content types for the well-known metadata parts above, used only\n// when [Content_Types].xml does not carry an explicit <Override> for them\n// (e.g. a part relying on a <Default Extension=\"xml\"> rule). These are the\n// content types the OPC/OOXML specs assign to these fixed part names.\nconst KNOWN_METADATA_CONTENT_TYPES: Readonly<Record<string, string>> = {\n [DOCX_CORE_PROPERTIES_PATH]:\n \"application/vnd.openxmlformats-package.core-properties+xml\",\n [DOCX_APP_PROPERTIES_PATH]:\n \"application/vnd.openxmlformats-officedocument.extended-properties+xml\",\n [DOCX_CUSTOM_PROPERTIES_PATH]:\n \"application/vnd.openxmlformats-officedocument.custom-properties+xml\",\n};\nconst GENERIC_XML_CONTENT_TYPE = \"application/xml\";\nconst GENERIC_BINARY_CONTENT_TYPE = \"application/octet-stream\";\nconst RELATIONSHIPS_CONTENT_TYPE =\n \"application/vnd.openxmlformats-package.relationships+xml\";\n// Relationship target URI schemes that can carry PII directly in\n// [Content_Types]-invisible relationship metadata (e.g. hyperlink targets),\n// independent of whatever display text a <w:hyperlink> wraps.\nconst PII_RELATIONSHIP_TARGET_SCHEMES: readonly string[] = [\"mailto:\", \"tel:\"];\nconst CONTENT_TYPES_NAMESPACE =\n \"http://schemas.openxmlformats.org/package/2006/content-types\";\nconst PACKAGE_RELATIONSHIP_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/package/relationships\",\n \"http://schemas.openxmlformats.org/package/2006/relationships\",\n]);\nconst WORDPROCESSING_CONTENT_TYPE_PREFIX =\n \"application/vnd.openxmlformats-officedocument.wordprocessingml.\";\nconst SUPPORTED_CONTENT_TYPE_SUFFIXES: Readonly<Record<string, DocxPartType>> =\n {\n \"comments+xml\": DOCX_PART_TYPES.comments,\n \"document.main+xml\": DOCX_PART_TYPES.mainDocument,\n \"endnotes+xml\": DOCX_PART_TYPES.endnotes,\n \"footer+xml\": DOCX_PART_TYPES.footer,\n \"footnotes+xml\": DOCX_PART_TYPES.footnotes,\n \"header+xml\": DOCX_PART_TYPES.header,\n };\nconst WORDPROCESSING_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/wordprocessingml/main\",\n \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n]);\nconst RELATIONSHIP_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/officeDocument/relationships\",\n \"http://schemas.openxmlformats.org/officeDocument/2006/relationships\",\n]);\nconst OFFICE_DOCUMENT_RELATIONSHIP_TYPES = new Set(\n [...RELATIONSHIP_NAMESPACES].map(\n (namespace) => `${namespace}/officeDocument`,\n ),\n);\nconst MARKUP_COMPATIBILITY_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/markup-compatibility/main\",\n \"http://schemas.openxmlformats.org/markup-compatibility/2006\",\n]);\n\nexport class DocxExtractionError extends Error {\n readonly code: DocxExtractionErrorCode;\n\n constructor(code: DocxExtractionErrorCode, message: string) {\n super(message);\n this.name = \"DocxExtractionError\";\n this.code = code;\n }\n}\n\ntype ContentTypePart = {\n path: string;\n contentType: string;\n};\n\ntype ElementFrame = {\n tag: SaxesTagNS;\n path: readonly number[];\n nextChildIndex: number;\n};\n\ntype MutableBlock = {\n text: string;\n segments: DocxTextSegment[];\n location: DocxTextBlock[\"location\"];\n};\n\ntype PartExtraction = {\n blocks: DocxTextBlock[];\n hyperlinkTextSegmentCount: number;\n revisionTextSegmentCount: number;\n unsupportedAlternateContentCount: number;\n unsupportedSymbolCount: number;\n unsupportedFieldInstructionCount: number;\n};\n\n// Shared across every extracted part of one archive: `extractDocxText`\n// creates a single budget and threads it through each `extractPart` call,\n// so the segment and inline-context-scan ceilings are enforced\n// archive-wide. A per-part budget would let a crafted archive split the\n// work across parts and stay under each per-part cap while still forcing\n// the aggregate worst case.\ntype ArchiveTextBudget = {\n segmentCount: number;\n inlineContextScanOps: number;\n};\n\nconst invalidPackage = (message: string): DocxExtractionError =>\n new DocxExtractionError(DOCX_EXTRACTION_ERROR_CODES.invalidPackage, message);\n\nconst assertXmlDepth = (depth: number): void => {\n if (depth < DOCX_XML_MAX_DEPTH) {\n return;\n }\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX XML must not exceed ${DOCX_XML_MAX_DEPTH} nested elements`,\n );\n};\n\nconst safeEntryPath = (name: string): boolean =>\n name.length > 0 &&\n !name.startsWith(\"/\") &&\n !name.includes(\"\\\\\") &&\n !name.split(\"/\").includes(\"..\") &&\n !name.includes(\"\\0\");\n\nconst RELATIONSHIPS_ENTRY_PATTERN = /(?:^|\\/)_rels\\/[^/]+\\.rels$/u;\n\n// Any OPC relationships part: the package root \"_rels/.rels\" plus every\n// \"<dir>/_rels/<part>.rels\", wherever it lives. All of them can carry\n// PII-bearing external targets (mailto:, tel:), so they are retained and\n// scanned uniformly instead of only the ones below word/.\nconst isRelationshipsEntry = (name: string): boolean =>\n name === ROOT_RELATIONSHIPS_PATH || RELATIONSHIPS_ENTRY_PATTERN.test(name);\n\nconst isCustomXmlEntry = (name: string): boolean =>\n name.startsWith(CUSTOM_XML_DIRECTORY_PREFIX) && name.endsWith(\".xml\");\n\ntype ArchiveBudget = {\n entryCount: number;\n uncompressedBytes: number;\n};\n\ntype ArchiveFilterOptions = {\n budget: ArchiveBudget;\n file: UnzipFileInfo;\n includeAllEntries: boolean;\n};\n\nconst archiveFilter = ({\n budget,\n file,\n includeAllEntries,\n}: ArchiveFilterOptions): boolean => {\n budget.entryCount += 1;\n if (budget.entryCount > DOCX_MAX_ENTRIES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must contain at most ${DOCX_MAX_ENTRIES} entries`,\n );\n }\n if (!safeEntryPath(file.name)) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.unsafeEntryPath,\n \"DOCX archive contains an unsafe entry path\",\n );\n }\n if (file.originalSize > DOCX_ENTRY_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`,\n );\n }\n budget.uncompressedBytes += file.originalSize;\n if (budget.uncompressedBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`,\n );\n }\n return (\n includeAllEntries ||\n file.name === CONTENT_TYPES_PATH ||\n (file.name.startsWith(\"word/\") && file.name.endsWith(\".xml\")) ||\n isRelationshipsEntry(file.name) ||\n // The whole docProps/ directory, not just the conventional core/app/\n // custom filenames: a properties relationship may address a\n // non-conventional part (e.g. docProps/custom2.xml) that must still be\n // flagged as uncovered metadata.\n file.name.startsWith(DOCPROPS_DIRECTORY_PREFIX) ||\n isCustomXmlEntry(file.name)\n );\n};\n\nexport const unzipDocxArchive = (\n archive: Uint8Array,\n includeAllEntries = false,\n onSkippedEntry?: (name: string) => void,\n): Record<string, Uint8Array> => {\n if (archive.byteLength > DOCX_ARCHIVE_MAX_BYTES) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.archiveLimitExceeded,\n `DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n );\n }\n const budget: ArchiveBudget = { entryCount: 0, uncompressedBytes: 0 };\n try {\n return unzipSync(archive, {\n filter: (file) => {\n const keep = archiveFilter({ budget, file, includeAllEntries });\n if (!keep) {\n onSkippedEntry?.(file.name);\n }\n return keep;\n },\n });\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidArchive,\n \"Input is not a valid bounded DOCX ZIP archive\",\n );\n }\n};\n\nconst decodeXml = (bytes: Uint8Array, path: string): string => {\n try {\n return new TextDecoder(\"utf-8\", { fatal: true }).decode(bytes);\n } catch {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX XML part is not valid UTF-8: ${path}`,\n );\n }\n};\n\nconst attributeByLocalName = (\n tag: SaxesTagNS,\n localName: string,\n namespaces?: ReadonlySet<string>,\n): string | null => {\n for (const attribute of Object.values(tag.attributes)) {\n if (\n attribute.local === localName &&\n (namespaces === undefined || namespaces.has(attribute.uri))\n ) {\n return attribute.value;\n }\n }\n return null;\n};\n\nconst parseContentTypes = (xml: string): ContentTypePart[] => {\n const parts: ContentTypePart[] = [];\n const paths = new Set<string>();\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (tag.local !== \"Override\" || tag.uri !== CONTENT_TYPES_NAMESPACE) {\n return;\n }\n const rawPath = attributeByLocalName(tag, \"PartName\");\n const contentType = attributeByLocalName(tag, \"ContentType\");\n if (rawPath === null || contentType === null) {\n throw invalidPackage(\"DOCX content-type override is incomplete\");\n }\n const path = rawPath.startsWith(\"/\") ? rawPath.slice(1) : rawPath;\n if (!safeEntryPath(path)) {\n throw invalidPackage(\"DOCX content-type override has an unsafe path\");\n }\n if (paths.has(path)) {\n throw invalidPackage(\n \"DOCX content-type overrides must have unique paths\",\n );\n }\n paths.add(path);\n parts.push({ path, contentType });\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n \"DOCX content types are not valid XML\",\n );\n }\n return parts;\n};\n\nconst parseMainDocumentTarget = (xml: string): string => {\n const targets: string[] = [];\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (\n tag.local !== \"Relationship\" ||\n !PACKAGE_RELATIONSHIP_NAMESPACES.has(tag.uri)\n ) {\n return;\n }\n const type = attributeByLocalName(tag, \"Type\");\n if (type === null || !OFFICE_DOCUMENT_RELATIONSHIP_TYPES.has(type)) {\n return;\n }\n const targetMode = attributeByLocalName(tag, \"TargetMode\");\n const rawTarget = attributeByLocalName(tag, \"Target\");\n if (targetMode === \"External\" || rawTarget === null) {\n throw invalidPackage(\"DOCX main-document relationship must be internal\");\n }\n const target = rawTarget.startsWith(\"/\") ? rawTarget.slice(1) : rawTarget;\n if (!safeEntryPath(target) || target.includes(\":\")) {\n throw invalidPackage(\n \"DOCX main-document relationship has an unsafe target\",\n );\n }\n targets.push(target);\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n \"DOCX root relationships are not valid XML\",\n );\n }\n if (targets.length !== 1) {\n throw invalidPackage(\n \"DOCX archive must contain exactly one main-document relationship\",\n );\n }\n const target = targets.at(0);\n if (target === undefined) {\n throw invalidPackage(\"DOCX main-document relationship is unavailable\");\n }\n return target;\n};\n\nconst hasPiiRelationshipTargetScheme = (target: string): boolean => {\n const normalized = target.trim().toLowerCase();\n return PII_RELATIONSHIP_TARGET_SCHEMES.some((scheme) =>\n normalized.startsWith(scheme),\n );\n};\n\n// A target addressed outside the package: an explicit TargetMode of\n// \"External\", any target with a URI scheme, or a protocol-relative URL.\n// The attribute alone is not trusted — a crafted archive can carry an\n// absolute URI in a nominally internal relationship.\nconst isExternalRelationshipTarget = (\n target: string,\n targetMode: string | null,\n): boolean => {\n const normalized = target.trim();\n return (\n targetMode?.trim().toLowerCase() === \"external\" ||\n ABSOLUTE_URI_PATTERN.test(normalized) ||\n normalized.startsWith(\"//\")\n );\n};\n\ntype UncoveredRelationshipTarget = {\n relationshipId: string | null;\n reason: string;\n};\n\nconst PII_SCHEME_TARGET_REASON =\n \"target uses a PII-bearing external scheme (mailto/tel) that anonymization does not redact\";\nconst EXTERNAL_TARGET_REASON =\n \"target is external and is not examined or redacted by anonymization\";\nconst DANGLING_TARGET_REASON =\n \"target does not resolve to a package part and is not examined or redacted by anonymization\";\n\n// The OPC base directory internal targets resolve against: the directory\n// that contains the relationships part's _rels folder (\"word/\" for\n// \"word/_rels/document.xml.rels\", \"\" for the package root \"_rels/.rels\").\nconst relationshipBaseDirectory = (relsPath: string): string => {\n const marker = relsPath.lastIndexOf(\"_rels/\");\n return marker <= 0 ? \"\" : relsPath.slice(0, marker);\n};\n\n// Percent-decodes an OPC target segment (\"My%20Doc.xml\" → \"My Doc.xml\").\n// Malformed encoding yields null, which callers treat as unresolvable\n// (fail closed) rather than guessing.\nconst decodeOpcTarget = (target: string): string | null => {\n try {\n return decodeURIComponent(target);\n } catch {\n return null;\n }\n};\n\n// Collapses \".\" and empty segments and applies \"..\" segments; a path that\n// climbs above the package root cannot name a part and yields null.\nconst normalizeOpcPath = (path: string): string | null => {\n const segments: string[] = [];\n for (const segment of path.split(\"/\")) {\n if (segment === \"\" || segment === \".\") {\n continue;\n }\n if (segment === \"..\") {\n if (segments.length === 0) {\n return null;\n }\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n return segments.length === 0 ? null : segments.join(\"/\");\n};\n\n// Resolves an internal relationship target to a package entry name:\n// package-absolute (\"/word/document.xml\") or relative to the relationships\n// part's base directory (\"media/image1.png\" from\n// \"word/_rels/document.xml.rels\" → \"word/media/image1.png\"). Returns null\n// when the target cannot name a part (malformed encoding, escapes the\n// root, or is empty).\nconst resolveInternalRelationshipTarget = (\n target: string,\n relsPath: string,\n): string | null => {\n const decoded = decodeOpcTarget(target.trim());\n if (decoded === null || decoded === \"\") {\n return null;\n }\n if (decoded.startsWith(\"/\")) {\n return normalizeOpcPath(decoded.slice(1));\n }\n return normalizeOpcPath(`${relationshipBaseDirectory(relsPath)}${decoded}`);\n};\n\n// Scans a relationships part for Relationship elements whose Target leaves\n// the package. These targets are never visited by extractPart (which only\n// walks WordprocessingML part XML), so without this check a hyperlink\n// pointing at \"mailto:alice@example.test\" — or at any external URL that\n// embeds PII in its userinfo, path, query, or fragment — with no PII in\n// its visible display text (or an orphaned relationship not referenced by\n// any <w:hyperlink>) produces no text segment and is invisible to\n// coverage. Every external target is uncovered: the rewrite never touches\n// relationship targets, so an external URI of any scheme or shape is an\n// unexamined channel that survives the rewrite verbatim. Internal\n// (in-package) targets are only covered when they resolve to an actual\n// archive entry (which then carries its own coverage entry); a dangling\n// internal target (\"alice@example.test\" with no scheme and no matching\n// part) is a PII channel preserved verbatim in the .rels XML and is\n// flagged too.\ntype ParseUncoveredRelationshipTargetsOptions = {\n xml: string;\n relsPath: string;\n // Lowercased archive entry names (OPC part-name comparison is\n // case-insensitive), including entries the retention filter dropped —\n // those still exist in the package and get inventory coverage entries.\n knownEntryPaths: ReadonlySet<string>;\n};\n\nconst parseUncoveredRelationshipTargets = ({\n xml,\n relsPath: path,\n knownEntryPaths,\n}: ParseUncoveredRelationshipTargetsOptions): UncoveredRelationshipTarget[] => {\n const found: UncoveredRelationshipTarget[] = [];\n const parser = new SaxesParser({ xmlns: true });\n let parseError: Error | null = null;\n let depth = 0;\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(depth);\n depth += 1;\n if (\n tag.local !== \"Relationship\" ||\n !PACKAGE_RELATIONSHIP_NAMESPACES.has(tag.uri)\n ) {\n return;\n }\n const target = attributeByLocalName(tag, \"Target\");\n if (target === null) {\n return;\n }\n const targetMode = attributeByLocalName(tag, \"TargetMode\");\n if (hasPiiRelationshipTargetScheme(target)) {\n found.push({\n relationshipId: attributeByLocalName(tag, \"Id\"),\n reason: PII_SCHEME_TARGET_REASON,\n });\n return;\n }\n if (isExternalRelationshipTarget(target, targetMode)) {\n found.push({\n relationshipId: attributeByLocalName(tag, \"Id\"),\n reason: EXTERNAL_TARGET_REASON,\n });\n return;\n }\n const resolved = resolveInternalRelationshipTarget(target, path);\n if (resolved === null || !knownEntryPaths.has(resolved.toLowerCase())) {\n found.push({\n relationshipId: attributeByLocalName(tag, \"Id\"),\n reason: DANGLING_TARGET_REASON,\n });\n }\n });\n parser.on(\"closetag\", () => {\n depth -= 1;\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX relationships are not valid XML: ${path}`,\n );\n }\n return found;\n};\n\nconst classifyPart = ({\n contentType,\n path,\n}: ContentTypePart): DocxPart | null => {\n if (!contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX)) {\n return null;\n }\n const suffix = contentType.slice(WORDPROCESSING_CONTENT_TYPE_PREFIX.length);\n const type = SUPPORTED_CONTENT_TYPE_SUFFIXES[suffix];\n return type === undefined ? null : { type, path };\n};\n\nconst isWordTag = (tag: SaxesTagNS, local: string): boolean =>\n tag.local === local && WORDPROCESSING_NAMESPACES.has(tag.uri);\n\nconst frameByLocalName = (\n stack: readonly ElementFrame[],\n local: string,\n): ElementFrame | null => {\n for (let index = stack.length - 1; index >= 0; index -= 1) {\n const frame = stack.at(index);\n if (frame !== undefined && isWordTag(frame.tag, local)) {\n return frame;\n }\n }\n return null;\n};\n\nconst blockLocation = (\n part: DocxPart,\n blockIndex: number,\n paragraphPath: readonly number[],\n stack: readonly ElementFrame[],\n): DocxTextBlock[\"location\"] => {\n const textBox = frameByLocalName(stack, \"txbxContent\");\n if (textBox !== null) {\n return {\n type: \"text-box-paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n textBoxPath: textBox.path,\n };\n }\n const cell = frameByLocalName(stack, \"tc\");\n const row = frameByLocalName(stack, \"tr\");\n const table = frameByLocalName(stack, \"tbl\");\n if (cell !== null && row !== null && table !== null) {\n return {\n type: \"table-cell-paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n tablePath: table.path,\n rowPath: row.path,\n cellPath: cell.path,\n };\n }\n return {\n type: \"paragraph\",\n part,\n blockIndex,\n xmlPath: paragraphPath,\n };\n};\n\nconst revisionForTag = (\n tag: SaxesTagNS,\n): Extract<DocxInlineContext, { type: \"revision\" }> | null => {\n if (!WORDPROCESSING_NAMESPACES.has(tag.uri)) {\n return null;\n }\n const revisions: Readonly<\n Record<string, \"deletion\" | \"insertion\" | \"move-from\" | \"move-to\">\n > = {\n del: \"deletion\",\n ins: \"insertion\",\n moveFrom: \"move-from\",\n moveTo: \"move-to\",\n };\n const revision = revisions[tag.local];\n return revision === undefined ? null : { type: \"revision\", revision };\n};\n\nconst inlineContexts = (\n stack: readonly ElementFrame[],\n): DocxInlineContext[] => {\n const contexts: DocxInlineContext[] = [];\n for (const { tag } of stack) {\n if (isWordTag(tag, \"hyperlink\")) {\n contexts.push({\n type: \"hyperlink\",\n relationshipId: attributeByLocalName(\n tag,\n \"id\",\n RELATIONSHIP_NAMESPACES,\n ),\n anchor: attributeByLocalName(tag, \"anchor\", WORDPROCESSING_NAMESPACES),\n });\n }\n const revision = revisionForTag(tag);\n if (revision !== null) {\n contexts.push(revision);\n }\n }\n return contexts;\n};\n\nconst appendSegment = (\n block: MutableBlock,\n budget: ArchiveTextBudget,\n value: string,\n source: DocxTextSegment[\"source\"],\n path: readonly number[],\n stack: readonly ElementFrame[],\n): void => {\n if (value.length === 0) {\n return;\n }\n if (budget.segmentCount >= DOCX_MAX_TEXT_SEGMENTS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not contain more than ${DOCX_MAX_TEXT_SEGMENTS} text segments`,\n );\n }\n budget.segmentCount += 1;\n // inlineContexts() below scans the whole element stack, so its cost is\n // O(stack.length). Bound the aggregate cost (see\n // DOCX_MAX_INLINE_CONTEXT_SCAN_OPS) rather than only the segment count and\n // depth independently, since their product is otherwise unbounded.\n if (\n budget.inlineContextScanOps + stack.length >\n DOCX_MAX_INLINE_CONTEXT_SCAN_OPS\n ) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not require more than ${DOCX_MAX_INLINE_CONTEXT_SCAN_OPS} aggregate inline-context scan operations`,\n );\n }\n budget.inlineContextScanOps += stack.length;\n const start = block.text.length;\n block.text += value;\n block.segments.push({\n start,\n end: block.text.length,\n source,\n contexts: inlineContexts(stack),\n xmlPath: path,\n });\n};\n\nconst extractPart = (\n part: DocxPart,\n xml: string,\n textBudget: ArchiveTextBudget,\n): PartExtraction => {\n const blocks: DocxTextBlock[] = [];\n const stack: ElementFrame[] = [];\n const blockStack: MutableBlock[] = [];\n let nextBlockIndex = 0;\n let currentText = \"\";\n let currentTextPath: readonly number[] | null = null;\n let parseError: Error | null = null;\n let unsupportedSymbolCount = 0;\n let unsupportedFieldInstructionCount = 0;\n let unsupportedAlternateContentCount = 0;\n\n const parser = new SaxesParser({ xmlns: true });\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"doctype\", () => {\n throw invalidPackage(\n \"DOCX XML must not contain a document type declaration\",\n );\n });\n parser.on(\"opentag\", (tag) => {\n assertXmlDepth(stack.length);\n const parent = stack.at(-1);\n const childIndex = parent?.nextChildIndex ?? 0;\n if (parent !== undefined) {\n parent.nextChildIndex += 1;\n }\n const path = [...(parent?.path ?? []), childIndex];\n if (isWordTag(tag, \"p\")) {\n if (nextBlockIndex >= DOCX_MAX_TEXT_BLOCKS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX parts must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`,\n );\n }\n blockStack.push({\n text: \"\",\n segments: [],\n location: blockLocation(part, nextBlockIndex, path, stack),\n });\n nextBlockIndex += 1;\n }\n stack.push({ tag, path, nextChildIndex: 0 });\n if (isWordTag(tag, \"t\") || isWordTag(tag, \"delText\")) {\n currentText = \"\";\n currentTextPath = path;\n }\n const currentBlock = blockStack.at(-1);\n if (currentBlock !== undefined && isWordTag(tag, \"tab\")) {\n appendSegment(currentBlock, textBudget, \"\\t\", \"tab\", path, stack);\n }\n if (\n currentBlock !== undefined &&\n (isWordTag(tag, \"br\") || isWordTag(tag, \"cr\"))\n ) {\n appendSegment(currentBlock, textBudget, \"\\n\", \"break\", path, stack);\n }\n if (isWordTag(tag, \"sym\")) {\n unsupportedSymbolCount += 1;\n }\n if (isWordTag(tag, \"instrText\") || isWordTag(tag, \"fldSimple\")) {\n unsupportedFieldInstructionCount += 1;\n }\n if (\n tag.local === \"AlternateContent\" &&\n MARKUP_COMPATIBILITY_NAMESPACES.has(tag.uri)\n ) {\n unsupportedAlternateContentCount += 1;\n }\n });\n parser.on(\"text\", (text) => {\n if (currentTextPath !== null) {\n currentText += text;\n }\n });\n parser.on(\"cdata\", (text) => {\n if (currentTextPath !== null) {\n currentText += text;\n }\n });\n parser.on(\"closetag\", (tag) => {\n const frame = stack.at(-1);\n if (frame === undefined || frame.tag !== tag) {\n throw invalidPackage(\"DOCX XML element stack is inconsistent\");\n }\n if (\n currentTextPath !== null &&\n (isWordTag(tag, \"t\") || isWordTag(tag, \"delText\"))\n ) {\n const currentBlock = blockStack.at(-1);\n if (currentBlock === undefined) {\n if (currentText.length > 0) {\n throw invalidPackage(\"DOCX text is outside a paragraph\");\n }\n } else {\n appendSegment(\n currentBlock,\n textBudget,\n currentText,\n \"text\",\n currentTextPath,\n stack,\n );\n }\n currentText = \"\";\n currentTextPath = null;\n }\n if (isWordTag(tag, \"p\")) {\n const completedBlock = blockStack.pop();\n if (completedBlock === undefined) {\n throw invalidPackage(\"DOCX paragraph state is unavailable\");\n }\n blocks.push(completedBlock);\n }\n stack.pop();\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxExtractionError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.invalidXml,\n `DOCX part is not valid XML: ${part.path}`,\n );\n }\n\n blocks.sort(\n (left, right) => left.location.blockIndex - right.location.blockIndex,\n );\n\n let hyperlinkTextSegmentCount = 0;\n let revisionTextSegmentCount = 0;\n for (const { segments } of blocks) {\n for (const { contexts } of segments) {\n if (contexts.some((context) => context.type === \"hyperlink\")) {\n hyperlinkTextSegmentCount += 1;\n }\n if (contexts.some((context) => context.type === \"revision\")) {\n revisionTextSegmentCount += 1;\n }\n }\n }\n return {\n blocks,\n hyperlinkTextSegmentCount,\n revisionTextSegmentCount,\n unsupportedAlternateContentCount,\n unsupportedSymbolCount,\n unsupportedFieldInstructionCount,\n };\n};\n\nexport const extractDocxText = (archive: Uint8Array): DocxExtraction => {\n // Entries the retention filter drops are still preserved verbatim by the\n // rewrite, so their names are recorded for the coverage inventory below.\n const skippedEntryPaths: string[] = [];\n const entries = unzipDocxArchive(archive, false, (name) => {\n skippedEntryPaths.push(name);\n });\n const contentTypesBytes = entries[CONTENT_TYPES_PATH];\n if (contentTypesBytes === undefined) {\n throw invalidPackage(\"DOCX archive is missing [Content_Types].xml\");\n }\n const contentTypes = parseContentTypes(\n decodeXml(contentTypesBytes, CONTENT_TYPES_PATH),\n );\n const rootRelationshipsBytes = entries[ROOT_RELATIONSHIPS_PATH];\n if (rootRelationshipsBytes === undefined) {\n throw invalidPackage(\"DOCX archive is missing _rels/.rels\");\n }\n const mainDocumentTarget = parseMainDocumentTarget(\n decodeXml(rootRelationshipsBytes, ROOT_RELATIONSHIPS_PATH),\n );\n const supportedParts = contentTypes\n .map(classifyPart)\n .filter((part): part is DocxPart => part !== null);\n if (\n supportedParts.filter((part) => part.type === DOCX_PART_TYPES.mainDocument)\n .length !== 1\n ) {\n throw invalidPackage(\"DOCX archive must contain exactly one main document\");\n }\n const mainDocument = supportedParts.find(\n (part) => part.type === DOCX_PART_TYPES.mainDocument,\n );\n if (mainDocument?.path !== mainDocumentTarget) {\n throw invalidPackage(\n \"DOCX main-document relationship and content type do not agree\",\n );\n }\n\n const blocks: DocxTextBlock[] = [];\n const coverageParts: DocxCoverageItem[] = [];\n let hyperlinkTextSegmentCount = 0;\n let revisionTextSegmentCount = 0;\n let unsupportedSymbolCount = 0;\n let unsupportedFieldInstructionCount = 0;\n let unsupportedAlternateContentCount = 0;\n // One budget for the whole archive: segment count and inline-context scan\n // ops accumulate across parts inside appendSegment, so splitting the work\n // over many parts cannot dodge the aggregate ceilings.\n const textBudget: ArchiveTextBudget = {\n segmentCount: 0,\n inlineContextScanOps: 0,\n };\n for (const part of supportedParts) {\n const bytes = entries[part.path];\n if (bytes === undefined) {\n throw invalidPackage(\n `DOCX archive is missing declared part: ${part.path}`,\n );\n }\n const extracted = extractPart(\n part,\n decodeXml(bytes, part.path),\n textBudget,\n );\n if (blocks.length + extracted.blocks.length > DOCX_MAX_TEXT_BLOCKS) {\n throw new DocxExtractionError(\n DOCX_EXTRACTION_ERROR_CODES.uncompressedLimitExceeded,\n `DOCX archives must not contain more than ${DOCX_MAX_TEXT_BLOCKS} text blocks`,\n );\n }\n blocks.push(...extracted.blocks);\n coverageParts.push({\n status: \"extracted\",\n part,\n blockCount: extracted.blocks.length,\n });\n hyperlinkTextSegmentCount += extracted.hyperlinkTextSegmentCount;\n revisionTextSegmentCount += extracted.revisionTextSegmentCount;\n unsupportedSymbolCount += extracted.unsupportedSymbolCount;\n unsupportedFieldInstructionCount +=\n extracted.unsupportedFieldInstructionCount;\n unsupportedAlternateContentCount +=\n extracted.unsupportedAlternateContentCount;\n }\n\n // OPC relationships parts are never walked by extractPart, which only\n // parses WordprocessingML. Any external relationship target — a\n // PII-bearing scheme (mailto:, tel:) or an external URL that embeds PII\n // in its userinfo, path, query, or fragment — can therefore carry\n // unredacted PII even when the visible hyperlink display text has no PII\n // of its own, when the relationship is not referenced by any\n // <w:hyperlink> at all, or when it lives outside word/ entirely (e.g. an\n // extra external relationship in the package root \"_rels/.rels\"). Rather\n // than attempting to rewrite relationship targets, fail closed: scan\n // every relationships part in the package and mark every external or\n // dangling target unsupported so `require-full` cannot report \"full\"\n // while such a target survives the rewrite untouched.\n const knownEntryPaths: ReadonlySet<string> = new Set(\n [...Object.keys(entries), ...skippedEntryPaths].map((name) =>\n name.toLowerCase(),\n ),\n );\n for (const [relsPath, relsBytes] of Object.entries(entries)) {\n if (!isRelationshipsEntry(relsPath)) {\n continue;\n }\n const uncoveredTargets = parseUncoveredRelationshipTargets({\n xml: decodeXml(relsBytes, relsPath),\n relsPath,\n knownEntryPaths,\n });\n for (const uncovered of uncoveredTargets) {\n coverageParts.push({\n status: \"unsupported\",\n path: relsPath,\n contentType: RELATIONSHIPS_CONTENT_TYPE,\n reason:\n uncovered.relationshipId === null\n ? `Relationship ${uncovered.reason}`\n : `Relationship \"${uncovered.relationshipId}\" ${uncovered.reason}`,\n });\n }\n }\n\n // ── Coverage inventory ──────────────────────────────────────────────\n // Every archive entry must end up either extracted, structural\n // ([Content_Types].xml and relationships parts, both parsed above), or\n // explicitly marked unsupported. Anything less lets the rewrite\n // preserve a part verbatim while `require-full` reports \"full\".\n const coveredPaths = new Set<string>([\n CONTENT_TYPES_PATH,\n ...supportedParts.map((part) => part.path),\n ]);\n const overrideContentTypes = new Map(\n contentTypes.map((entry) => [entry.path, entry.contentType]),\n );\n const markUnsupported = (\n path: string,\n contentType: string,\n reason: string,\n ): void => {\n if (coveredPaths.has(path)) {\n return;\n }\n coveredPaths.add(path);\n coverageParts.push({ status: \"unsupported\", path, contentType, reason });\n };\n\n // docProps/* (core, extended, custom, and any non-conventional\n // properties part) and customXml/* can carry PII (dc:creator,\n // cp:lastModifiedBy, custom properties, structured custom XML content)\n // but are never walked by extractPart. Redacting arbitrary metadata and\n // custom-XML schemas is out of scope here, so fail closed: mark every\n // present metadata/custom-XML part unsupported.\n for (const path of Object.keys(entries)) {\n if (path.startsWith(DOCPROPS_DIRECTORY_PREFIX)) {\n markUnsupported(\n path,\n overrideContentTypes.get(path) ??\n KNOWN_METADATA_CONTENT_TYPES[path] ??\n GENERIC_XML_CONTENT_TYPE,\n \"Document metadata parts are not extracted or redacted\",\n );\n } else if (isCustomXmlEntry(path)) {\n markUnsupported(\n path,\n overrideContentTypes.get(path) ?? GENERIC_XML_CONTENT_TYPE,\n \"Custom XML parts are not extracted or redacted\",\n );\n }\n }\n\n // Every part declared in [Content_Types].xml that the extractor does not\n // parse is uncovered, whatever its content type: metadata parts at\n // non-conventional paths, non-extracted WordprocessingML part types\n // (styles, settings, ...), and any other declared payload (charts,\n // diagrams, embedded objects, ...) that can carry document text.\n for (const { contentType, path } of contentTypes) {\n if (\n contentType === RELATIONSHIPS_CONTENT_TYPE ||\n isRelationshipsEntry(path)\n ) {\n continue;\n }\n if (METADATA_CONTENT_TYPES.has(contentType)) {\n markUnsupported(\n path,\n contentType,\n \"Document metadata parts are not extracted or redacted\",\n );\n continue;\n }\n if (contentType.startsWith(WORDPROCESSING_CONTENT_TYPE_PREFIX)) {\n markUnsupported(\n path,\n contentType,\n \"WordprocessingML part type is not extracted\",\n );\n continue;\n }\n markUnsupported(\n path,\n contentType,\n \"Package part type is not extracted or redacted\",\n );\n }\n\n // Finally, every remaining archive entry — retained but undeclared, or\n // dropped by the retention filter (media, fonts, embedded binaries,\n // arbitrary extra files) — is preserved verbatim by the rewrite without\n // ever being examined, so it must surface as uncovered too.\n for (const path of [...Object.keys(entries), ...skippedEntryPaths]) {\n if (path.endsWith(\"/\") || isRelationshipsEntry(path)) {\n continue;\n }\n markUnsupported(\n path,\n overrideContentTypes.get(path) ??\n (path.endsWith(\".xml\")\n ? GENERIC_XML_CONTENT_TYPE\n : GENERIC_BINARY_CONTENT_TYPE),\n \"Package part is not examined by anonymization\",\n );\n }\n\n return {\n contractVersion: DOCX_EXTRACTION_CONTRACT_VERSION,\n blocks,\n coverage: {\n parts: coverageParts,\n hyperlinkTextSegmentCount,\n revisionTextSegmentCount,\n unsupportedAlternateContentCount,\n unsupportedSymbolCount,\n unsupportedFieldInstructionCount,\n },\n };\n};\n","import type { DocxBlockLocation } from \"./types\";\n\nconst arraysEqual = (\n left: readonly number[],\n right: readonly number[],\n): boolean =>\n left.length === right.length &&\n left.every((value, index) => value === right.at(index));\n\nexport const docxLocationsEqual = (\n left: DocxBlockLocation,\n right: DocxBlockLocation,\n): boolean => {\n if (\n left.type !== right.type ||\n left.part.type !== right.part.type ||\n left.part.path !== right.part.path ||\n left.blockIndex !== right.blockIndex ||\n !arraysEqual(left.xmlPath, right.xmlPath)\n ) {\n return false;\n }\n if (left.type === \"paragraph\" && right.type === \"paragraph\") {\n return true;\n }\n if (\n left.type === \"table-cell-paragraph\" &&\n right.type === \"table-cell-paragraph\"\n ) {\n return (\n arraysEqual(left.tablePath, right.tablePath) &&\n arraysEqual(left.rowPath, right.rowPath) &&\n arraysEqual(left.cellPath, right.cellPath)\n );\n }\n if (\n left.type === \"text-box-paragraph\" &&\n right.type === \"text-box-paragraph\"\n ) {\n return arraysEqual(left.textBoxPath, right.textBoxPath);\n }\n return false;\n};\n\nexport const docxLocationKey = ({\n blockIndex,\n part,\n}: DocxBlockLocation): string => `${part.path}\\0${blockIndex}`;\n","import { strToU8, zipSync } from \"fflate\";\nimport { SaxesParser, type SaxesTagNS } from \"saxes\";\n\nimport {\n DOCX_ARCHIVE_MAX_BYTES,\n DOCX_ENTRY_MAX_BYTES,\n DOCX_UNCOMPRESSED_MAX_BYTES,\n DOCX_XML_MAX_DEPTH,\n extractDocxText,\n unzipDocxArchive,\n} from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport {\n DOCX_REWRITE_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRewriteErrorCode,\n type DocxRewriteResult,\n type DocxTextBlock,\n type DocxTextReplacement,\n type DocxTextSegment,\n} from \"./types\";\n\nconst WORDPROCESSING_NAMESPACES = new Set([\n \"http://purl.oclc.org/ooxml/wordprocessingml/main\",\n \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\",\n]);\nconst XML_NAMESPACE = \"http://www.w3.org/XML/1998/namespace\";\nconst DOCX_MAX_REPLACEMENTS = 1_000_000;\nconst SIGNATURE_PART_PREFIX = \"_xmlsignatures/\";\n\nexport class DocxRewriteError extends Error {\n readonly code: DocxRewriteErrorCode;\n\n constructor(code: DocxRewriteErrorCode, message: string) {\n super(message);\n this.name = \"DocxRewriteError\";\n this.code = code;\n }\n}\n\ntype ElementFrame = {\n path: readonly number[];\n nextChildIndex: number;\n};\n\ntype TextNodeUpdate = {\n path: readonly number[];\n value: string;\n // UTF-8 byte length of the node's original decoded value. Any XML\n // serialization of a character (raw, entity, character reference, CDATA)\n // occupies at least that character's UTF-8 length in the source part, so\n // this is a safe lower bound on the source bytes the patch removes when\n // projecting the rewritten part's size.\n originalByteLength: number;\n};\n\ntype XmlPatch = {\n start: number;\n end: number;\n value: string;\n};\n\nconst rewriteError = (\n code: DocxRewriteErrorCode,\n message: string,\n): DocxRewriteError => new DocxRewriteError(code, message);\n\nconst pathKey = (path: readonly number[]): string => path.join(\".\");\n\nconst isValidXmlText = (value: string): boolean => {\n for (const character of value) {\n const codePoint = character.codePointAt(0);\n if (\n codePoint === undefined ||\n (codePoint !== 0x09 &&\n codePoint !== 0x0a &&\n codePoint !== 0x0d &&\n (codePoint < 0x20 ||\n (codePoint > 0xd7ff && codePoint < 0xe000) ||\n (codePoint > 0xfffd && codePoint < 0x10_000) ||\n codePoint > 0x10_ffff))\n ) {\n return false;\n }\n }\n return true;\n};\n\nconst isUtf16Boundary = (value: string, offset: number): boolean => {\n if (offset === 0 || offset === value.length) {\n return true;\n }\n const previous = value.charCodeAt(offset - 1);\n const next = value.charCodeAt(offset);\n return !(\n previous >= 0xd800 &&\n previous <= 0xdbff &&\n next >= 0xdc00 &&\n next <= 0xdfff\n );\n};\n\nconst escapeXmlText = (value: string): string =>\n value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\");\n\n// Plain UTF-8 byte length of a string, computed without encoding it.\nconst utf8ByteLength = (value: string): number => {\n let total = 0;\n for (const character of value) {\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint <= 0x7f) {\n total += 1;\n } else if (codePoint <= 0x7ff) {\n total += 2;\n } else if (codePoint <= 0xffff) {\n total += 3;\n } else {\n total += 4;\n }\n }\n return total;\n};\n\n// Worst-case bytes the conditional ' xml:space=\"preserve\"' insertion adds\n// to a rewritten text node.\nconst XML_SPACE_INSERTION_BYTES = ' xml:space=\"preserve\"'.length;\n\n// The UTF-8 byte length `escapeXmlText(value)` would serialize to, computed\n// without materializing the (up to five-fold larger) escaped string, so\n// budget checks cannot themselves cause the allocation they guard against.\nconst escapedXmlTextByteLength = (value: string): number => {\n let total = 0;\n for (const character of value) {\n if (character === \"&\") {\n total += 5; // &\n continue;\n }\n if (character === \"<\" || character === \">\") {\n total += 4; // < / >\n continue;\n }\n const codePoint = character.codePointAt(0) ?? 0;\n if (codePoint <= 0x7f) {\n total += 1;\n } else if (codePoint <= 0x7ff) {\n total += 2;\n } else if (codePoint <= 0xffff) {\n total += 3;\n } else {\n total += 4;\n }\n }\n return total;\n};\n\n// Budget replacements by their escaped size: rewritePartXml expands each\n// \"&\", \"<\", and \">\" through escapeXmlText before materializing the patched\n// XML, so a replacement made of \"&\" grows five-fold between a raw-byte\n// budget check and the rebuild. Counting post-escape bytes keeps the\n// pre-rebuild budgets aligned with what is actually materialized. (The\n// conditional xml:space=\"preserve\" insertion adds a small fixed overhead\n// per rewritten text node; assertArchiveBudgets still bounds the final\n// entries.)\nconst replacementByteLength = (replacement: DocxTextReplacement): number =>\n escapedXmlTextByteLength(replacement.replacement);\n\nconst validateReplacement = (\n replacement: DocxTextReplacement,\n blockText: string,\n): void => {\n if (\n !Number.isSafeInteger(replacement.start) ||\n !Number.isSafeInteger(replacement.end) ||\n replacement.start < 0 ||\n replacement.start >= replacement.end ||\n replacement.end > blockText.length ||\n !isUtf16Boundary(blockText, replacement.start) ||\n !isUtf16Boundary(blockText, replacement.end)\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement spans must be nonempty bounded integer ranges at UTF-16 boundaries\",\n );\n }\n if (!isValidXmlText(replacement.replacement)) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement text must contain only valid XML characters\",\n );\n }\n if (replacementByteLength(replacement) > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX replacement text must not exceed ${DOCX_ENTRY_MAX_BYTES} escaped UTF-8 bytes`,\n );\n }\n};\n\nconst coveredTextSegments = (\n block: DocxTextBlock,\n replacement: DocxTextReplacement,\n): readonly DocxTextSegment[] => {\n const segments = block.segments.filter(\n ({ end, start }) => start < replacement.end && end > replacement.start,\n );\n let cursor = replacement.start;\n for (const segment of segments) {\n if (\n segment.source !== \"text\" ||\n segment.start > cursor ||\n segment.contexts.some((context) => context.type === \"revision\")\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacements must stay within contiguous non-revision text segments\",\n );\n }\n cursor = Math.min(replacement.end, segment.end);\n }\n if (segments.length === 0 || cursor !== replacement.end) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacements must stay within contiguous non-revision text segments\",\n );\n }\n return segments;\n};\n\nconst planBlockUpdates = (\n block: DocxTextBlock,\n rewrite: DocxBlockRewrite,\n): TextNodeUpdate[] => {\n const replacements = [...rewrite.replacements].sort(\n (left, right) => left.start - right.start,\n );\n for (const [index, replacement] of replacements.entries()) {\n validateReplacement(replacement, block.text);\n const previous = index === 0 ? undefined : replacements.at(index - 1);\n if (previous !== undefined && previous.end > replacement.start) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX replacement spans must not overlap\",\n );\n }\n }\n\n const values = new Map<string, TextNodeUpdate>();\n const originalValues = new Map<string, string>();\n for (const segment of block.segments) {\n if (segment.source !== \"text\") {\n continue;\n }\n const original = block.text.slice(segment.start, segment.end);\n values.set(pathKey(segment.xmlPath), {\n path: segment.xmlPath,\n value: original,\n originalByteLength: utf8ByteLength(original),\n });\n originalValues.set(pathKey(segment.xmlPath), original);\n }\n\n for (const replacement of replacements.toReversed()) {\n const segments = coveredTextSegments(block, replacement);\n const first = segments.at(0);\n const last = segments.at(-1);\n if (first === undefined || last === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacement text segments are unavailable\",\n );\n }\n const firstUpdate = values.get(pathKey(first.xmlPath));\n const lastUpdate = values.get(pathKey(last.xmlPath));\n if (firstUpdate === undefined || lastUpdate === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX replacement text nodes are unavailable\",\n );\n }\n const firstStart = replacement.start - first.start;\n const lastEnd = replacement.end - last.start;\n if (first === last) {\n firstUpdate.value =\n firstUpdate.value.slice(0, firstStart) +\n replacement.replacement +\n firstUpdate.value.slice(lastEnd);\n continue;\n }\n firstUpdate.value =\n firstUpdate.value.slice(0, firstStart) + replacement.replacement;\n for (const segment of segments.slice(1, -1)) {\n const update = values.get(pathKey(segment.xmlPath));\n if (update !== undefined) {\n update.value = \"\";\n }\n }\n lastUpdate.value = lastUpdate.value.slice(lastEnd);\n }\n return [...values.entries()]\n .filter(([key, update]) => update.value !== originalValues.get(key))\n .map(([, update]) => update);\n};\n\nconst requiresPreservedSpace = (value: string): boolean =>\n /^\\s|\\s$/u.test(value);\n\nconst isWordTextTag = (tag: SaxesTagNS): boolean =>\n WORDPROCESSING_NAMESPACES.has(tag.uri) &&\n (tag.local === \"t\" || tag.local === \"delText\");\n\nconst hasPreservedSpace = (tag: SaxesTagNS): boolean =>\n Object.values(tag.attributes).some(\n (attribute) =>\n attribute.uri === XML_NAMESPACE &&\n attribute.local === \"space\" &&\n attribute.value === \"preserve\",\n );\n\ntype FindClosingTagStartOptions = {\n xml: string;\n contentStart: number;\n parserPosition: number;\n};\n\nconst findClosingTagStart = ({\n xml,\n contentStart,\n parserPosition,\n}: FindClosingTagStartOptions): number => {\n for (let index = parserPosition - 1; index >= contentStart; index -= 1) {\n if (xml[index] === \"<\" && xml[index + 1] === \"/\") {\n return index;\n }\n }\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX text-node closing tag changed after extraction\",\n );\n};\n\nconst rewritePartXml = (\n xml: string,\n updates: readonly TextNodeUpdate[],\n): string => {\n const updatesByPath = new Map(\n updates.map((update) => [pathKey(update.path), update]),\n );\n const foundPaths = new Set<string>();\n const patches: XmlPatch[] = [];\n const stack: ElementFrame[] = [];\n let activeText:\n | { key: string; contentStart: number; tag: SaxesTagNS }\n | undefined;\n let parseError: Error | null = null;\n const parser = new SaxesParser({ xmlns: true });\n parser.on(\"error\", (error) => {\n parseError = error;\n });\n parser.on(\"opentag\", (tag) => {\n if (stack.length >= DOCX_XML_MAX_DEPTH) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX XML must not exceed ${DOCX_XML_MAX_DEPTH} nested elements`,\n );\n }\n const parent = stack.at(-1);\n const childIndex = parent?.nextChildIndex ?? 0;\n if (parent !== undefined) {\n parent.nextChildIndex += 1;\n }\n const path = [...(parent?.path ?? []), childIndex];\n stack.push({ path, nextChildIndex: 0 });\n const key = pathKey(path);\n if (isWordTextTag(tag) && updatesByPath.has(key)) {\n if (tag.isSelfClosing) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX self-closing text nodes cannot receive replacements\",\n );\n }\n activeText = { key, contentStart: parser.position, tag };\n }\n });\n parser.on(\"closetag\", (tag) => {\n if (activeText?.tag === tag) {\n const update = updatesByPath.get(activeText.key);\n if (update !== undefined) {\n const contentEnd = findClosingTagStart({\n xml,\n contentStart: activeText.contentStart,\n parserPosition: parser.position,\n });\n patches.push({\n start: activeText.contentStart,\n end: contentEnd,\n value: escapeXmlText(update.value),\n });\n if (requiresPreservedSpace(update.value) && !hasPreservedSpace(tag)) {\n patches.push({\n start: activeText.contentStart - 1,\n end: activeText.contentStart - 1,\n value: ' xml:space=\"preserve\"',\n });\n }\n foundPaths.add(activeText.key);\n }\n activeText = undefined;\n }\n stack.pop();\n });\n try {\n parser.write(xml).close();\n } catch (error) {\n if (error instanceof DocxRewriteError) {\n throw error;\n }\n parseError = error instanceof Error ? error : new Error(\"invalid XML\");\n }\n if (parseError !== null) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"DOCX source XML changed after extraction\",\n );\n }\n if (foundPaths.size !== updatesByPath.size) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX text-node locations changed after extraction\",\n );\n }\n let rewritten = xml;\n for (const patch of patches.toSorted(\n (left, right) => right.start - left.start,\n )) {\n rewritten =\n rewritten.slice(0, patch.start) +\n patch.value +\n rewritten.slice(patch.end);\n }\n return rewritten;\n};\n\nconst assertArchiveBudgets = (entries: Record<string, Uint8Array>): void => {\n let totalBytes = 0;\n for (const bytes of Object.values(entries)) {\n if (bytes.byteLength > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX entries must not exceed ${DOCX_ENTRY_MAX_BYTES} bytes`,\n );\n }\n totalBytes += bytes.byteLength;\n }\n if (totalBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} uncompressed bytes`,\n );\n }\n};\n\nexport const rewriteDocxText = (\n archive: Uint8Array,\n rewrites: readonly DocxBlockRewrite[],\n): DocxRewriteResult => {\n const extraction = extractDocxText(archive);\n if (rewrites.length === 0) {\n return {\n document: archive.slice(),\n rewrittenBlockCount: 0,\n appliedReplacementCount: 0,\n };\n }\n const blocksByLocation = new Map(\n extraction.blocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const updatesByPart = new Map<string, Map<string, TextNodeUpdate>>();\n const rewrittenLocations = new Set<string>();\n const replacementBytesByPart = new Map<string, number>();\n let appliedReplacementCount = 0;\n let totalReplacementBytes = 0;\n\n for (const rewrite of rewrites) {\n const key = docxLocationKey(rewrite.location);\n if (rewrittenLocations.has(key)) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"Each DOCX block may appear in a rewrite plan only once\",\n );\n }\n rewrittenLocations.add(key);\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, rewrite.location) ||\n block.text !== rewrite.expectedText\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX block location or expected text no longer matches\",\n );\n }\n if (rewrite.replacements.length === 0) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.invalidReplacement,\n \"DOCX block rewrite plans must contain at least one replacement\",\n );\n }\n if (\n appliedReplacementCount + rewrite.replacements.length >\n DOCX_MAX_REPLACEMENTS\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrites must not contain more than ${DOCX_MAX_REPLACEMENTS} replacements`,\n );\n }\n // Individual replacements are bounded (DOCX_ENTRY_MAX_BYTES) and\n // DOCX_MAX_REPLACEMENTS caps their count, but nothing previously bounded\n // their aggregate byte size. rewritePartXml fully materializes a part's\n // patched XML string before assertArchiveBudgets ever runs, so a large\n // aggregate of otherwise-individually-valid replacements could exhaust\n // memory before the budget check has a chance to reject. Track running\n // totals here, before any XML is built, and fail fast. The totals count\n // escaped bytes (see replacementByteLength), matching what the rebuild\n // materializes.\n const rewriteReplacementBytes = rewrite.replacements.reduce(\n (total, replacement) => total + replacementByteLength(replacement),\n 0,\n );\n totalReplacementBytes += rewriteReplacementBytes;\n if (totalReplacementBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrite replacement text must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} aggregate escaped UTF-8 bytes`,\n );\n }\n const partReplacementBytes =\n (replacementBytesByPart.get(block.location.part.path) ?? 0) +\n rewriteReplacementBytes;\n replacementBytesByPart.set(block.location.part.path, partReplacementBytes);\n if (partReplacementBytes > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewrite replacement text for a single part must not exceed ${DOCX_ENTRY_MAX_BYTES} aggregate escaped UTF-8 bytes`,\n );\n }\n const partUpdates =\n updatesByPart.get(block.location.part.path) ?? new Map();\n for (const update of planBlockUpdates(block, rewrite)) {\n partUpdates.set(pathKey(update.path), update);\n }\n updatesByPart.set(block.location.part.path, partUpdates);\n appliedReplacementCount += rewrite.replacements.length;\n }\n\n // Replacement-byte budgets alone are not enough: rewritePartXml escapes\n // each *entire* updated node value on rebuild, and a text node whose\n // source stores the value cheaply (a CDATA \"&\" run occupies one byte per\n // character on disk but five once escaped) can therefore blow up ~5x\n // from a one-byte replacement. Budget the actual rebuild output — the\n // escaped size of every updated node value — before any XML is built.\n let totalUpdatedNodeBytes = 0;\n for (const updates of updatesByPart.values()) {\n let partUpdatedNodeBytes = 0;\n for (const update of updates.values()) {\n partUpdatedNodeBytes += escapedXmlTextByteLength(update.value);\n }\n if (partUpdatedNodeBytes > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewritten text nodes for a single part must not exceed ${DOCX_ENTRY_MAX_BYTES} escaped UTF-8 bytes`,\n );\n }\n totalUpdatedNodeBytes += partUpdatedNodeBytes;\n if (totalUpdatedNodeBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `DOCX rewritten text nodes must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} aggregate escaped UTF-8 bytes`,\n );\n }\n }\n\n const entries = unzipDocxArchive(archive, true);\n if (\n Object.keys(entries).some((path) =>\n path.toLowerCase().startsWith(SIGNATURE_PART_PREFIX),\n )\n ) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.unsupportedReplacement,\n \"Digitally signed DOCX packages must be re-signed before rewriting\",\n );\n }\n // Project the size of every part as it will exist after patching, before\n // any rewritten XML is materialized: the escaped-node budgets above do\n // not account for a part's unchanged scaffolding, so a near-limit part\n // plus modest rewrites could still materialize well past the entry cap\n // ahead of assertArchiveBudgets. Projection per touched part: original\n // part bytes + escaped updated values + worst-case xml:space insertions\n // - a safe lower bound on the source bytes the patches remove (every XML\n // serialization of a character occupies at least its UTF-8 length).\n // Shrinking rewrites project below the original, so redacting a large\n // part stays possible.\n let projectedTotalBytes = 0;\n for (const [partPath, partBytes] of Object.entries(entries)) {\n let projected = partBytes.byteLength;\n const updates = updatesByPart.get(partPath);\n if (updates !== undefined) {\n for (const update of updates.values()) {\n projected +=\n escapedXmlTextByteLength(update.value) +\n XML_SPACE_INSERTION_BYTES -\n update.originalByteLength;\n }\n if (projected > DOCX_ENTRY_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX parts must not exceed ${DOCX_ENTRY_MAX_BYTES} projected bytes`,\n );\n }\n }\n projectedTotalBytes += projected;\n if (projectedTotalBytes > DOCX_UNCOMPRESSED_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_UNCOMPRESSED_MAX_BYTES} projected uncompressed bytes`,\n );\n }\n }\n for (const [partPath, updates] of updatesByPart) {\n const partBytes = entries[partPath];\n if (partBytes === undefined) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.staleExtraction,\n \"DOCX source part changed after extraction\",\n );\n }\n const xml = new TextDecoder(\"utf-8\", { fatal: true }).decode(partBytes);\n entries[partPath] = strToU8(rewritePartXml(xml, [...updates.values()]));\n }\n assertArchiveBudgets(entries);\n const document = zipSync(entries);\n if (document.byteLength > DOCX_ARCHIVE_MAX_BYTES) {\n throw rewriteError(\n DOCX_REWRITE_ERROR_CODES.rewriteLimitExceeded,\n `Rewritten DOCX archives must not exceed ${DOCX_ARCHIVE_MAX_BYTES} bytes`,\n );\n }\n return {\n document,\n rewrittenBlockCount: rewrites.length,\n appliedReplacementCount,\n };\n};\n","import type { DocxCoverage, DocxWorkflowCoverage } from \"./types\";\n\nconst hasPartialCoverage = (coverage: DocxCoverage): boolean =>\n coverage.parts.some(({ status }) => status === \"unsupported\") ||\n coverage.hyperlinkTextSegmentCount > 0 ||\n coverage.revisionTextSegmentCount > 0 ||\n coverage.unsupportedAlternateContentCount > 0 ||\n coverage.unsupportedSymbolCount > 0 ||\n coverage.unsupportedFieldInstructionCount > 0;\n\nexport const docxWorkflowCoverage = (\n coverage: DocxCoverage,\n): DocxWorkflowCoverage => {\n const counts = {\n extractedPartCount: coverage.parts.filter(\n ({ status }) => status === \"extracted\",\n ).length,\n unsupportedPartCount: coverage.parts.filter(\n ({ status }) => status === \"unsupported\",\n ).length,\n hyperlinkTextSegmentCount: coverage.hyperlinkTextSegmentCount,\n revisionTextSegmentCount: coverage.revisionTextSegmentCount,\n unsupportedAlternateContentCount: coverage.unsupportedAlternateContentCount,\n unsupportedSymbolCount: coverage.unsupportedSymbolCount,\n unsupportedFieldInstructionCount: coverage.unsupportedFieldInstructionCount,\n };\n return hasPartialCoverage(coverage)\n ? { status: \"partial\", counts }\n : { status: \"full\", counts };\n};\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_RESTORATION_ERROR_CODES,\n type DocxBlockRewrite,\n type DocxRestorationErrorCode,\n type DocxRestorationResult,\n type DocxTextReplacement,\n type RestoreDocxTextOptions,\n} from \"./types\";\n\nconst DOCX_RESTORE_MAX_PLACEHOLDER_UTF16 = 512;\nconst DOCX_RESTORE_MAX_CANDIDATES = 1_000_000;\n\nexport class DocxRestorationError extends Error {\n readonly code: DocxRestorationErrorCode;\n\n constructor(code: DocxRestorationErrorCode, message: string) {\n super(message);\n this.name = \"DocxRestorationError\";\n this.code = code;\n }\n}\n\nconst restorationError = (\n code: DocxRestorationErrorCode,\n message: string,\n): DocxRestorationError => new DocxRestorationError(code, message);\n\nconst encodedSessionNamespace = (sessionId: string): string =>\n sessionId.replaceAll(\"_\", \"%5F\");\n\nconst isOwnedPlaceholderCandidate = (\n value: string,\n encodedSessionId: string,\n): boolean => {\n const inner = value.endsWith(\"]\") ? value.slice(0, -1) : value;\n const countSeparator = inner.lastIndexOf(\"_\");\n if (countSeparator <= 0) {\n return false;\n }\n const prefix = inner.slice(0, countSeparator);\n const namespaceSeparator = prefix.lastIndexOf(\"_\");\n if (namespaceSeparator <= 0) {\n return false;\n }\n return prefix.slice(namespaceSeparator + 1) === encodedSessionId;\n};\n\ntype PlanBlockRestorationOptions = {\n text: string;\n encodedSessionId: string;\n restoreCandidate: (candidate: string) => string;\n budget: { candidateCount: number };\n};\n\nconst planBlockRestoration = ({\n text,\n encodedSessionId,\n restoreCandidate,\n budget,\n}: PlanBlockRestorationOptions): DocxTextReplacement[] => {\n const replacements: DocxTextReplacement[] = [];\n let start: number | undefined;\n for (let cursor = 0; cursor < text.length; cursor += 1) {\n const character = text.at(cursor);\n if (character === \"[\") {\n if (\n start !== undefined &&\n isOwnedPlaceholderCandidate(\n text.slice(start + 1, cursor),\n encodedSessionId,\n )\n ) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an incomplete placeholder for the expected session\",\n );\n }\n start = cursor;\n continue;\n }\n if (character !== \"]\" || start === undefined) {\n continue;\n }\n const candidateEnd = cursor + 1;\n const candidate = text.slice(start, candidateEnd);\n budget.candidateCount += 1;\n if (budget.candidateCount > DOCX_RESTORE_MAX_CANDIDATES) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.restorationLimitExceeded,\n `DOCX restoration must not inspect more than ${DOCX_RESTORE_MAX_CANDIDATES} placeholder candidates`,\n );\n }\n const isOwned = isOwnedPlaceholderCandidate(\n candidate.slice(1),\n encodedSessionId,\n );\n if (candidate.length > DOCX_RESTORE_MAX_PLACEHOLDER_UTF16) {\n if (isOwned) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX session placeholder exceeds the maximum length\",\n );\n }\n start = undefined;\n continue;\n }\n if (!isOwned) {\n start = undefined;\n continue;\n }\n const replacement = restoreCandidate(candidate);\n if (replacement !== candidate) {\n replacements.push({ start, end: candidateEnd, replacement });\n } else {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an unknown placeholder for the expected session\",\n );\n }\n start = undefined;\n }\n if (\n start !== undefined &&\n isOwnedPlaceholderCandidate(text.slice(start + 1), encodedSessionId)\n ) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidPlaceholder,\n \"DOCX text contains an incomplete placeholder for the expected session\",\n );\n }\n return replacements;\n};\n\nexport const restoreDocxText = ({\n document,\n session,\n expectedSessionId,\n observedAtEpochSeconds,\n}: RestoreDocxTextOptions): DocxRestorationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.sessionMismatch,\n \"DOCX restoration session does not match the expected session id\",\n );\n }\n\n const assertSessionAvailable = (): void => {\n if (session.restoreText(\"\", observedAtEpochSeconds) !== \"\") {\n throw restorationError(\n DOCX_RESTORATION_ERROR_CODES.invalidSession,\n \"DOCX restoration session must preserve text without placeholders\",\n );\n }\n };\n assertSessionAvailable();\n const restoredCandidates = new Map<string, string>();\n const restoreCandidate = (candidate: string): string => {\n const cached = restoredCandidates.get(candidate);\n if (cached !== undefined) {\n return cached;\n }\n const restored = session.restoreText(candidate, observedAtEpochSeconds);\n restoredCandidates.set(candidate, restored);\n return restored;\n };\n const encodedSessionId = encodedSessionNamespace(sessionId);\n const extraction = extractDocxText(document);\n const rewrites: DocxBlockRewrite[] = [];\n const budget = { candidateCount: 0 };\n let restoredPlaceholderCount = 0;\n for (const block of extraction.blocks) {\n const replacements = planBlockRestoration({\n text: block.text,\n encodedSessionId,\n restoreCandidate,\n budget,\n });\n if (replacements.length === 0) {\n continue;\n }\n restoredPlaceholderCount += replacements.length;\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements,\n });\n }\n assertSessionAvailable();\n const restored = rewriteDocxText(document, rewrites);\n return {\n document: restored.document,\n sessionId,\n restoredBlockCount: restored.rewrittenBlockCount,\n restoredPlaceholderCount,\n coverage: docxWorkflowCoverage(extraction.coverage),\n };\n};\n","import { docxWorkflowCoverage } from \"./coverage\";\nimport { extractDocxText } from \"./extract\";\nimport { docxLocationKey, docxLocationsEqual } from \"./location\";\nimport { rewriteDocxText } from \"./rewrite\";\nimport {\n DOCX_ANONYMIZATION_ERROR_CODES,\n DOCX_COVERAGE_MODES,\n type AnonymizeDocxOptions,\n type DocxAnonymizationErrorCode,\n type DocxAnonymizationResult,\n type DocxBlockCallerDetections,\n type DocxBlockRewrite,\n} from \"./types\";\n\nexport const DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS = 1_000_000;\n\nexport class DocxAnonymizationError extends Error {\n readonly code: DocxAnonymizationErrorCode;\n\n constructor(code: DocxAnonymizationErrorCode, message: string) {\n super(message);\n this.name = \"DocxAnonymizationError\";\n this.code = code;\n }\n}\n\nconst anonymizationError = (\n code: DocxAnonymizationErrorCode,\n message: string,\n): DocxAnonymizationError => new DocxAnonymizationError(code, message);\n\ntype DetectionPlan = {\n detectionsByLocation: ReadonlyMap<string, DocxBlockCallerDetections>;\n callerDetectionCount: number;\n};\n\nconst planCallerDetections = (\n extractionBlocks: ReturnType<typeof extractDocxText>[\"blocks\"],\n inputs: readonly DocxBlockCallerDetections[],\n): DetectionPlan => {\n const blocksByLocation = new Map(\n extractionBlocks.map((block) => [docxLocationKey(block.location), block]),\n );\n const detectionsByLocation = new Map<string, DocxBlockCallerDetections>();\n let callerDetectionCount = 0;\n for (const input of inputs) {\n const key = docxLocationKey(input.location);\n if (detectionsByLocation.has(key)) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"Each DOCX block may have only one caller-detection input\",\n );\n }\n const block = blocksByLocation.get(key);\n if (\n block === undefined ||\n !docxLocationsEqual(block.location, input.location) ||\n block.text !== input.expectedText\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX caller-detection location or expected text no longer matches\",\n );\n }\n if (\n input.detections.length >\n DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS - callerDetectionCount\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n `DOCX workflows must not contain more than ${DOCX_ANONYMIZATION_MAX_CALLER_DETECTIONS} caller detections`,\n );\n }\n detectionsByLocation.set(key, input);\n callerDetectionCount += input.detections.length;\n }\n return { detectionsByLocation, callerDetectionCount };\n};\n\nexport const anonymizeDocx = ({\n document,\n session,\n expectedSessionId,\n policy,\n callerDetections = [],\n observedAtEpochSeconds,\n}: AnonymizeDocxOptions): DocxAnonymizationResult => {\n const sessionId = session.sessionId();\n if (sessionId !== expectedSessionId) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.sessionMismatch,\n \"DOCX anonymization session does not match the expected session\",\n );\n }\n\n const extraction = extractDocxText(document);\n const coverage = docxWorkflowCoverage(extraction.coverage);\n if (\n coverage.status === \"partial\" &&\n policy.coverage.mode === DOCX_COVERAGE_MODES.requireFull\n ) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.incompleteCoverage,\n \"DOCX contains content outside the fully supported anonymization coverage\",\n );\n }\n\n const { detectionsByLocation, callerDetectionCount } = planCallerDetections(\n extraction.blocks,\n callerDetections,\n );\n const plan = session.planTextBatchWithCallerDetections({\n inputs: extraction.blocks.map((block) => ({\n fullText: block.text,\n detections:\n detectionsByLocation.get(docxLocationKey(block.location))?.detections ??\n [],\n })),\n ...(policy.operators === undefined ? {} : { operators: policy.operators }),\n ...(observedAtEpochSeconds === undefined ? {} : { observedAtEpochSeconds }),\n });\n if (plan.blocks.length !== extraction.blocks.length) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan does not match the extracted block count\",\n );\n }\n\n const rewrites: DocxBlockRewrite[] = [];\n let entityCount = 0;\n let retainedCallerDetectionCount = 0;\n for (const [index, block] of extraction.blocks.entries()) {\n const blockPlan = plan.blocks.at(index);\n if (blockPlan === undefined) {\n throw anonymizationError(\n DOCX_ANONYMIZATION_ERROR_CODES.invalidCallerDetections,\n \"DOCX session redaction plan is missing an extracted block\",\n );\n }\n entityCount += blockPlan.entityCount;\n retainedCallerDetectionCount += blockPlan.callerEntityCount;\n if (blockPlan.replacements.length === 0) {\n continue;\n }\n rewrites.push({\n location: block.location,\n expectedText: block.text,\n replacements: blockPlan.replacements,\n });\n }\n\n const rewritten = rewriteDocxText(document, rewrites);\n plan.commit();\n return {\n document: rewritten.document,\n summary: {\n contractVersion: 1,\n sessionId,\n blockCount: extraction.blocks.length,\n rewrittenBlockCount: rewritten.rewrittenBlockCount,\n appliedReplacementCount: rewritten.appliedReplacementCount,\n entityCount,\n callerDetectionCount,\n retainedCallerDetectionCount,\n coverage,\n },\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/anonymize-docx",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Structure-aware DOCX text extraction and rewriting for stella anonymization workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -26,19 +26,19 @@
|
|
|
26
26
|
"license": "Apache-2.0",
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "tsdown",
|
|
29
|
-
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
29
|
+
"typecheck": "bun ../../scripts/tsc-native.ts --noEmit -p tsconfig.json && bun ../../scripts/tsc-native.ts --noEmit -p tsconfig.test.json",
|
|
30
30
|
"test": "bun test",
|
|
31
31
|
"format": "oxfmt ."
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@stll/anonymize": "^2.0
|
|
34
|
+
"@stll/anonymize": "^2.2.0",
|
|
35
35
|
"fflate": "^0.8.3",
|
|
36
36
|
"saxes": "^6.0.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^26.1.1",
|
|
40
40
|
"bun-types": "^1.3.14",
|
|
41
|
-
"tsdown": "^0.22.
|
|
41
|
+
"tsdown": "^0.22.7",
|
|
42
42
|
"typescript": "^6.0.3"
|
|
43
43
|
}
|
|
44
44
|
}
|