kordoc 4.14.2 → 4.14.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +51 -6
  2. package/dist/{-AQKXLFIM.js → -Z4TJ3QGK.js} +13 -11
  3. package/dist/{chunk-QOMNEWCQ.cjs → chunk-5LAZQCUJ.cjs} +305 -31
  4. package/dist/chunk-5LAZQCUJ.cjs.map +1 -0
  5. package/dist/{chunk-7IAQ4HOK.js → chunk-6BWU6SQX.js} +1 -1
  6. package/dist/chunk-6BWU6SQX.js.map +1 -0
  7. package/dist/{chunk-FOJZNMMK.js → chunk-6CUIXJOC.js} +1 -1
  8. package/dist/chunk-6CUIXJOC.js.map +1 -0
  9. package/dist/{chunk-ZWD734EY.js → chunk-7SE4EZ45.js} +2 -2
  10. package/dist/{chunk-TSFS5VKD.js → chunk-D4U2TXDC.js} +332 -141
  11. package/dist/chunk-D4U2TXDC.js.map +1 -0
  12. package/dist/chunk-DPWWXUOG.js +2222 -0
  13. package/dist/chunk-DPWWXUOG.js.map +1 -0
  14. package/dist/{chunk-GDW4YP7K.js → chunk-GLSBEEZJ.js} +2 -2
  15. package/dist/{chunk-JQEFP45L.js → chunk-GUJ4Q73I.js} +2 -2
  16. package/dist/{chunk-ZWOFQRIB.js → chunk-HIJUKZHM.js} +3 -3
  17. package/dist/chunk-IMAAFSAX.cjs +1352 -0
  18. package/dist/chunk-IMAAFSAX.cjs.map +1 -0
  19. package/dist/{chunk-TNCKOL2O.js → chunk-LCW7SEPG.js} +2 -2
  20. package/dist/chunk-LFSQAMSM.js +1352 -0
  21. package/dist/chunk-LFSQAMSM.js.map +1 -0
  22. package/dist/chunk-MG6DJEX4.js +1299 -0
  23. package/dist/chunk-MG6DJEX4.js.map +1 -0
  24. package/dist/{chunk-QC5T6D4J.js → chunk-NLU6LBLY.js} +299 -25
  25. package/dist/chunk-NLU6LBLY.js.map +1 -0
  26. package/dist/{chunk-EQZI77PP.cjs → chunk-QEJUY6SK.cjs} +3 -3
  27. package/dist/{chunk-EQZI77PP.cjs.map → chunk-QEJUY6SK.cjs.map} +1 -1
  28. package/dist/{chunk-VQ5W4MYO.js → chunk-QIF2VXBQ.js} +298 -24
  29. package/dist/chunk-QIF2VXBQ.js.map +1 -0
  30. package/dist/{chunk-UET462HB.js → chunk-TITDJWVZ.js} +2 -2
  31. package/dist/{chunk-247ANM72.cjs → chunk-USHQCF5T.cjs} +870 -147
  32. package/dist/chunk-USHQCF5T.cjs.map +1 -0
  33. package/dist/{chunk-C7FQBSON.js → chunk-V3DX4DUF.js} +2 -2
  34. package/dist/{chunk-DOGPOY4B.js → chunk-VDN7LMPL.js} +2 -2
  35. package/dist/{chunk-EDNJLUSE.js → chunk-VJIL6PIL.js} +2 -2
  36. package/dist/{chunk-L7DMSMAG.cjs → chunk-VWUYVEFN.cjs} +1 -1
  37. package/dist/chunk-VWUYVEFN.cjs.map +1 -0
  38. package/dist/{chunk-HR2XNERB.cjs → chunk-WJHAU73B.cjs} +2 -2
  39. package/dist/{chunk-HR2XNERB.cjs.map → chunk-WJHAU73B.cjs.map} +1 -1
  40. package/dist/{chunk-NS55SM7A.js → chunk-WJJGW5PL.js} +2 -2
  41. package/dist/{chunk-EISO3POT.js → chunk-XVXHXQIO.js} +788 -194
  42. package/dist/chunk-XVXHXQIO.js.map +1 -0
  43. package/dist/{chunk-ILU76UF3.js → chunk-YAKC46HZ.js} +2 -2
  44. package/dist/chunk-YAKC46HZ.js.map +1 -0
  45. package/dist/{chunk-3DUADAY2.js → chunk-YAVHVFRY.js} +851 -128
  46. package/dist/chunk-YAVHVFRY.js.map +1 -0
  47. package/dist/{chunk-A7QC2ZVP.js → chunk-YT3GC62E.js} +851 -128
  48. package/dist/chunk-YT3GC62E.js.map +1 -0
  49. package/dist/chunks-CCMRL4MJ.js +10 -0
  50. package/dist/cli.js +228 -97
  51. package/dist/cli.js.map +1 -1
  52. package/dist/{formula-PJJ6S3Z2.cjs → formula-5LOVHAKE.cjs} +9 -9
  53. package/dist/{formula-PJJ6S3Z2.cjs.map → formula-5LOVHAKE.cjs.map} +1 -1
  54. package/dist/{formula-JGFTXWYT.js → formula-7RANCW5L.js} +3 -3
  55. package/dist/{formula-5S7WWXGH.js → formula-7UYJOVTB.js} +4 -4
  56. package/dist/{image-ocr-WCAYXLOA.js → image-ocr-I6JOCATS.js} +34 -16
  57. package/dist/image-ocr-I6JOCATS.js.map +1 -0
  58. package/dist/{image-ocr-ZTYAGV3W.cjs → image-ocr-NYBJEOH2.cjs} +36 -18
  59. package/dist/image-ocr-NYBJEOH2.cjs.map +1 -0
  60. package/dist/{image-ocr-FLIL7MGN.js → image-ocr-ZV6PDBUQ.js} +32 -14
  61. package/dist/image-ocr-ZV6PDBUQ.js.map +1 -0
  62. package/dist/index.cjs +4048 -1216
  63. package/dist/index.cjs.map +1 -1
  64. package/dist/index.d.cts +66 -28
  65. package/dist/index.d.ts +66 -28
  66. package/dist/index.js +3487 -655
  67. package/dist/index.js.map +1 -1
  68. package/dist/mcp.js +74 -53
  69. package/dist/mcp.js.map +1 -1
  70. package/dist/{model-bundle-FSPZZ2EM.js → model-bundle-2C5IYDLE.js} +5 -5
  71. package/dist/{models-CJPAQK5R.js → models-SEQZHZFT.js} +5 -5
  72. package/dist/{parser-SCUTZAIJ.js → parser-3FNXEP6T.js} +708 -250
  73. package/dist/parser-3FNXEP6T.js.map +1 -0
  74. package/dist/{parser-A4TMPJAM.js → parser-FJBJXRJK.js} +708 -250
  75. package/dist/parser-FJBJXRJK.js.map +1 -0
  76. package/dist/{parser-VCXE4I5A.cjs → parser-HVEKF73B.cjs} +733 -275
  77. package/dist/parser-HVEKF73B.cjs.map +1 -0
  78. package/dist/pdf-ocr-JRDBJ6TA.cjs +14 -0
  79. package/dist/{pdf-ocr-MBZZ57LD.cjs.map → pdf-ocr-JRDBJ6TA.cjs.map} +1 -1
  80. package/dist/pdf-ocr-UUBWBEHL.js +16 -0
  81. package/dist/pdf-ocr-VJSF2SQC.js +13 -0
  82. package/dist/{profile-io-LBM263K7.js → profile-io-RUFZOZO6.js} +3 -3
  83. package/dist/{rasterize-46RG4ZF6.js → rasterize-IOYXR5SI.js} +3 -3
  84. package/dist/redact-NMIKVHMP.js +18 -0
  85. package/dist/redact-doc-QBHNJ2BU.js +1284 -0
  86. package/dist/redact-doc-QBHNJ2BU.js.map +1 -0
  87. package/dist/{render-NGP5T24S.js → render-YZ2TEC3N.js} +7 -7
  88. package/dist/seal-ECV44TUH.js +10 -0
  89. package/dist/{setup-AU4OCHNH.js → setup-MPIEP6XT.js} +3 -3
  90. package/dist/visual-ICMOWUCR.js +19 -0
  91. package/dist/{watch-MKPUCPXM.js → watch-RYO7MN5B.js} +12 -12
  92. package/package.json +2 -2
  93. package/dist/chunk-247ANM72.cjs.map +0 -1
  94. package/dist/chunk-3DUADAY2.js.map +0 -1
  95. package/dist/chunk-422427XJ.cjs +0 -571
  96. package/dist/chunk-422427XJ.cjs.map +0 -1
  97. package/dist/chunk-7IAQ4HOK.js.map +0 -1
  98. package/dist/chunk-A7QC2ZVP.js.map +0 -1
  99. package/dist/chunk-DZIXKL7E.js +0 -145
  100. package/dist/chunk-DZIXKL7E.js.map +0 -1
  101. package/dist/chunk-EISO3POT.js.map +0 -1
  102. package/dist/chunk-EP7KOI2V.js +0 -571
  103. package/dist/chunk-EP7KOI2V.js.map +0 -1
  104. package/dist/chunk-FOJZNMMK.js.map +0 -1
  105. package/dist/chunk-ILU76UF3.js.map +0 -1
  106. package/dist/chunk-KVWV4ULP.js +0 -516
  107. package/dist/chunk-KVWV4ULP.js.map +0 -1
  108. package/dist/chunk-L7DMSMAG.cjs.map +0 -1
  109. package/dist/chunk-QC5T6D4J.js.map +0 -1
  110. package/dist/chunk-QOMNEWCQ.cjs.map +0 -1
  111. package/dist/chunk-TSFS5VKD.js.map +0 -1
  112. package/dist/chunk-VQ5W4MYO.js.map +0 -1
  113. package/dist/chunks-SX77P5CV.js +0 -10
  114. package/dist/image-ocr-FLIL7MGN.js.map +0 -1
  115. package/dist/image-ocr-WCAYXLOA.js.map +0 -1
  116. package/dist/image-ocr-ZTYAGV3W.cjs.map +0 -1
  117. package/dist/parser-A4TMPJAM.js.map +0 -1
  118. package/dist/parser-SCUTZAIJ.js.map +0 -1
  119. package/dist/parser-VCXE4I5A.cjs.map +0 -1
  120. package/dist/pdf-ocr-76SAUXMZ.js +0 -13
  121. package/dist/pdf-ocr-CPU55CVF.js +0 -16
  122. package/dist/pdf-ocr-MBZZ57LD.cjs +0 -14
  123. package/dist/redact-7ILEAUTS.js +0 -12
  124. package/dist/seal-RGUPOLXD.js +0 -10
  125. package/dist/visual-NZLYIYEM.js +0 -19
  126. /package/dist/{-AQKXLFIM.js.map → -Z4TJ3QGK.js.map} +0 -0
  127. /package/dist/{chunk-ZWD734EY.js.map → chunk-7SE4EZ45.js.map} +0 -0
  128. /package/dist/{chunk-GDW4YP7K.js.map → chunk-GLSBEEZJ.js.map} +0 -0
  129. /package/dist/{chunk-JQEFP45L.js.map → chunk-GUJ4Q73I.js.map} +0 -0
  130. /package/dist/{chunk-ZWOFQRIB.js.map → chunk-HIJUKZHM.js.map} +0 -0
  131. /package/dist/{chunk-TNCKOL2O.js.map → chunk-LCW7SEPG.js.map} +0 -0
  132. /package/dist/{chunk-UET462HB.js.map → chunk-TITDJWVZ.js.map} +0 -0
  133. /package/dist/{chunk-C7FQBSON.js.map → chunk-V3DX4DUF.js.map} +0 -0
  134. /package/dist/{chunk-DOGPOY4B.js.map → chunk-VDN7LMPL.js.map} +0 -0
  135. /package/dist/{chunk-EDNJLUSE.js.map → chunk-VJIL6PIL.js.map} +0 -0
  136. /package/dist/{chunk-NS55SM7A.js.map → chunk-WJJGW5PL.js.map} +0 -0
  137. /package/dist/{chunks-SX77P5CV.js.map → chunks-CCMRL4MJ.js.map} +0 -0
  138. /package/dist/{formula-JGFTXWYT.js.map → formula-7RANCW5L.js.map} +0 -0
  139. /package/dist/{formula-5S7WWXGH.js.map → formula-7UYJOVTB.js.map} +0 -0
  140. /package/dist/{model-bundle-FSPZZ2EM.js.map → model-bundle-2C5IYDLE.js.map} +0 -0
  141. /package/dist/{models-CJPAQK5R.js.map → models-SEQZHZFT.js.map} +0 -0
  142. /package/dist/{pdf-ocr-76SAUXMZ.js.map → pdf-ocr-UUBWBEHL.js.map} +0 -0
  143. /package/dist/{pdf-ocr-CPU55CVF.js.map → pdf-ocr-VJSF2SQC.js.map} +0 -0
  144. /package/dist/{profile-io-LBM263K7.js.map → profile-io-RUFZOZO6.js.map} +0 -0
  145. /package/dist/{rasterize-46RG4ZF6.js.map → rasterize-IOYXR5SI.js.map} +0 -0
  146. /package/dist/{redact-7ILEAUTS.js.map → redact-NMIKVHMP.js.map} +0 -0
  147. /package/dist/{render-NGP5T24S.js.map → render-YZ2TEC3N.js.map} +0 -0
  148. /package/dist/{seal-RGUPOLXD.js.map → seal-ECV44TUH.js.map} +0 -0
  149. /package/dist/{setup-AU4OCHNH.js.map → setup-MPIEP6XT.js.map} +0 -0
  150. /package/dist/{visual-NZLYIYEM.js.map → visual-ICMOWUCR.js.map} +0 -0
  151. /package/dist/{watch-MKPUCPXM.js.map → watch-RYO7MN5B.js.map} +0 -0
@@ -0,0 +1,1284 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ compressWithTail,
4
+ parse,
5
+ replaceOleStream,
6
+ wipeFreeSectors
7
+ } from "./chunk-XVXHXQIO.js";
8
+ import {
9
+ allLinesegRemovalSplices,
10
+ findElementEnd,
11
+ patchZipEntries,
12
+ scanSectionXml
13
+ } from "./chunk-HIJUKZHM.js";
14
+ import {
15
+ DEFAULT_REDACT_RULES,
16
+ mdMarkerRanges,
17
+ nameParticleAt,
18
+ normalizeForDetect,
19
+ redactMarkdown,
20
+ redactText
21
+ } from "./chunk-DPWWXUOG.js";
22
+ import "./chunk-VJIL6PIL.js";
23
+ import {
24
+ FLAG_COMPRESSED,
25
+ FLAG_DISTRIBUTION,
26
+ FLAG_DRM,
27
+ FLAG_ENCRYPTED,
28
+ TAG_CTRL_DATA,
29
+ TAG_CTRL_HEADER,
30
+ TAG_PARA_TEXT,
31
+ decompressStream,
32
+ detectFormat,
33
+ detectOle2Format,
34
+ detectZipFormat,
35
+ isExtendedOnlyCtrlChar,
36
+ parseFileHeader,
37
+ readRecords
38
+ } from "./chunk-D4U2TXDC.js";
39
+ import "./chunk-7FCNOKOR.js";
40
+ import "./chunk-6BWU6SQX.js";
41
+ import "./chunk-MOL7MDBG.js";
42
+ import "./chunk-YAKC46HZ.js";
43
+ import "./chunk-NLU6LBLY.js";
44
+ import "./chunk-V3DX4DUF.js";
45
+ import "./chunk-TITDJWVZ.js";
46
+
47
+ // src/redact-scrub.ts
48
+ import { createRequire } from "module";
49
+ import JSZip from "jszip";
50
+ import { deflateSync, inflateSync } from "zlib";
51
+ var require2 = createRequire(import.meta.url);
52
+ var CFB = require2("cfb");
53
+ function cannotContainPii(text) {
54
+ if (text.includes("@") || text.includes("\uFF20")) return false;
55
+ let digits = 0;
56
+ for (let i = 0; i < text.length; i++) {
57
+ const c = text.charCodeAt(i);
58
+ if (c >= 48 && c <= 57 || c >= 65296 && c <= 65305) {
59
+ if (++digits >= 4) return false;
60
+ }
61
+ }
62
+ return true;
63
+ }
64
+ var LIT_KEY = 6;
65
+ var LIT_KEY_SHORT = 2;
66
+ var literalIndex = /* @__PURE__ */ new WeakMap();
67
+ function literalBuckets(lits) {
68
+ let m = literalIndex.get(lits);
69
+ if (!m) {
70
+ m = { long: /* @__PURE__ */ new Map(), short: /* @__PURE__ */ new Map() };
71
+ for (const l of [...lits].sort((a, b) => b.norm.length - a.norm.length)) {
72
+ const short = l.norm.length < LIT_KEY;
73
+ const k = l.norm.slice(0, short ? LIT_KEY_SHORT : LIT_KEY);
74
+ const map = short ? m.short : m.long;
75
+ const list = map.get(k);
76
+ if (list) list.push(l);
77
+ else map.set(k, [l]);
78
+ }
79
+ literalIndex.set(lits, m);
80
+ }
81
+ return m;
82
+ }
83
+ var isAlnum = (c) => c !== void 0 && /[0-9A-Za-z]/.test(c);
84
+ var isHangul = (c) => c !== void 0 && c >= "\uAC00" && c <= "\uD7A3";
85
+ function literalBoundaryOk(norm, i, l) {
86
+ const end = i + l.norm.length;
87
+ if (isAlnum(norm[end - 1]) && isAlnum(norm[end])) return false;
88
+ if (isHangul(l.norm[0]) && isHangul(norm[i - 1])) return false;
89
+ return !isHangul(l.norm[l.norm.length - 1]) || !isHangul(norm[end]) || nameParticleAt(norm, end);
90
+ }
91
+ function findLiterals(norm, lits) {
92
+ const out = [];
93
+ if (lits.length === 0) return out;
94
+ const { long, short } = literalBuckets(lits);
95
+ const hit = (cand, i) => cand?.find((l) => norm.startsWith(l.norm, i) && literalBoundaryOk(norm, i, l));
96
+ for (let i = 0; i + LIT_KEY_SHORT <= norm.length; i++) {
97
+ if (isAlnum(norm[i - 1]) && isAlnum(norm[i])) continue;
98
+ const lit = (long.size > 0 ? hit(long.get(norm.slice(i, i + LIT_KEY)), i) : void 0) ?? (short.size > 0 ? hit(short.get(norm.slice(i, i + LIT_KEY_SHORT)), i) : void 0);
99
+ if (!lit) continue;
100
+ out.push({ index: i, lit });
101
+ i += lit.norm.length - 1;
102
+ }
103
+ return out;
104
+ }
105
+ var TEXT_RULES = /* @__PURE__ */ new Set(["name", "address"]);
106
+ function binaryCtx(ctx) {
107
+ return ctx.rules.some((r) => TEXT_RULES.has(r)) ? { ...ctx, rules: ctx.rules.filter((r) => !TEXT_RULES.has(r)) } : ctx;
108
+ }
109
+ function findPii(text, ctx) {
110
+ const textRules = ctx.rules.some((r) => TEXT_RULES.has(r));
111
+ const hits = textRules ? redactText(text, { rules: ctx.rules, maskChar: ctx.maskChar }).hits : text.length < 5 || cannotContainPii(text) ? [] : redactText(text, { rules: ctx.rules, maskChar: ctx.maskChar }).hits;
112
+ if (ctx.literals.length === 0) return hits;
113
+ for (const { index: i, lit } of findLiterals(normalizeForDetect(text), ctx.literals)) {
114
+ const end = i + lit.norm.length;
115
+ if (hits.some((h) => i < h.index + h.length && end > h.index)) continue;
116
+ let masked = "";
117
+ for (let k = 0; k < lit.norm.length; k++) masked += lit.masked[k] !== lit.value[k] ? ctx.maskChar : text[i + k];
118
+ hits.push({ rule: lit.rule, masked, index: i, length: lit.norm.length });
119
+ }
120
+ const view = parserView(text);
121
+ if (view) {
122
+ for (const { index: i, lit } of findLiterals(normalizeForDetect(view.text), ctx.literals)) {
123
+ const start = view.pos[i];
124
+ const end = view.pos[i + lit.norm.length - 1] + 1;
125
+ if (hits.some((h) => start < h.index + h.length && end > h.index)) continue;
126
+ const chars = text.slice(start, end).split("");
127
+ for (let k = 0; k < lit.norm.length; k++) if (lit.masked[k] !== lit.value[k]) chars[view.pos[i + k] - start] = ctx.maskChar;
128
+ hits.push({ rule: lit.rule, masked: chars.join(""), index: start, length: end - start });
129
+ }
130
+ }
131
+ return hits.sort((a, b) => a.index - b.index);
132
+ }
133
+ function parserView(text) {
134
+ if (!text.includes(" ")) return null;
135
+ let squeeze = false;
136
+ if (text.length <= 400) {
137
+ const tokens = text.replace(/ {2,}/g, " ").trim().split(" ");
138
+ const hangul1 = (t) => t.length === 1 && /[\uAC00-\uD7AF\u3131-\u318E]/.test(t);
139
+ squeeze = tokens.join(" ").length <= 30 && tokens.length >= 3 && tokens.filter(hangul1).length / tokens.length >= 0.7 && !tokens.every((t) => !hangul1(t) || /[년월일시분초]/.test(t));
140
+ }
141
+ if (!squeeze && !text.includes(" ")) return null;
142
+ const pos = [];
143
+ for (let i = 0; i < text.length; i++) if (text[i] !== " " || !squeeze && text[i - 1] !== " ") pos.push(i);
144
+ return { text: pos.map((i) => text[i]).join(""), pos };
145
+ }
146
+ function literalsFromMarkdown(markdown, hits) {
147
+ const out = /* @__PURE__ */ new Map();
148
+ const lineMarkers = /* @__PURE__ */ new Map();
149
+ for (const h of hits) {
150
+ const rawValue = markdown.slice(h.index, h.index + h.length);
151
+ const lineStart = markdown.lastIndexOf("\n", h.index - 1) + 1;
152
+ let ranges = lineMarkers.get(lineStart);
153
+ if (!ranges) {
154
+ const lineEnd = markdown.indexOf("\n", h.index);
155
+ ranges = mdMarkerRanges(markdown.slice(lineStart, lineEnd < 0 ? void 0 : lineEnd));
156
+ lineMarkers.set(lineStart, ranges);
157
+ }
158
+ const drop = /* @__PURE__ */ new Set();
159
+ for (const [ms, me] of ranges) {
160
+ for (let p = ms + lineStart; p < me + lineStart; p++) if (p >= h.index && p < h.index + h.length) drop.add(p - h.index);
161
+ }
162
+ const value = rawValue.split("").filter((_, k) => !drop.has(k)).join("");
163
+ const masked = h.masked.split("").filter((_, k) => !drop.has(k)).join("");
164
+ const norm = normalizeForDetect(value);
165
+ const tooShort = TEXT_RULES.has(h.rule) ? norm.length < 2 : norm.replace(/[^0-9A-Za-z]/g, "").length < 6;
166
+ if (tooShort || out.has(norm)) continue;
167
+ out.set(norm, { norm, value, masked, rule: h.rule });
168
+ }
169
+ return [...out.values()];
170
+ }
171
+ var CRC_TABLE = (() => {
172
+ const t = new Uint32Array(256);
173
+ for (let n = 0; n < 256; n++) {
174
+ let c = n;
175
+ for (let k = 0; k < 8; k++) c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
176
+ t[n] = c >>> 0;
177
+ }
178
+ return t;
179
+ })();
180
+ function crc32(data) {
181
+ let crc = 4294967295;
182
+ for (let i = 0; i < data.length; i++) crc = CRC_TABLE[(crc ^ data[i]) & 255] ^ crc >>> 8;
183
+ return (crc ^ 4294967295) >>> 0;
184
+ }
185
+ function pngChunk(type, data) {
186
+ const head = Buffer.alloc(8);
187
+ head.writeUInt32BE(data.length, 0);
188
+ head.write(type, 4, "ascii");
189
+ const crc = Buffer.alloc(4);
190
+ crc.writeUInt32BE(crc32(Buffer.concat([head.subarray(4), data])), 0);
191
+ return Buffer.concat([head, data, crc]);
192
+ }
193
+ function blankPng(width, height) {
194
+ const ihdr = Buffer.alloc(13);
195
+ ihdr.writeUInt32BE(width, 0);
196
+ ihdr.writeUInt32BE(height, 4);
197
+ ihdr[8] = 8;
198
+ ihdr[9] = 0;
199
+ const row = Buffer.alloc(width + 1, 255);
200
+ row[0] = 0;
201
+ const raw = Buffer.alloc(row.length * height);
202
+ for (let y = 0; y < height; y++) row.copy(raw, y * row.length);
203
+ return Buffer.concat([
204
+ Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
205
+ pngChunk("IHDR", ihdr),
206
+ pngChunk("IDAT", deflateSync(raw)),
207
+ pngChunk("IEND", new Uint8Array(0))
208
+ ]);
209
+ }
210
+ function blankGif(width, height) {
211
+ const b = Buffer.from([
212
+ 71,
213
+ 73,
214
+ 70,
215
+ 56,
216
+ 57,
217
+ 97,
218
+ 0,
219
+ 0,
220
+ 0,
221
+ 0,
222
+ 128,
223
+ 0,
224
+ 0,
225
+ // GIF89a, 화면 크기, 전역 팔레트 2색
226
+ 255,
227
+ 255,
228
+ 255,
229
+ 255,
230
+ 255,
231
+ 255,
232
+ 44,
233
+ 0,
234
+ 0,
235
+ 0,
236
+ 0,
237
+ 1,
238
+ 0,
239
+ 1,
240
+ 0,
241
+ 0,
242
+ // 이미지 1×1 @(0,0)
243
+ 2,
244
+ 2,
245
+ 68,
246
+ 1,
247
+ 0,
248
+ 59
249
+ // LZW: clear·0·end, 트레일러
250
+ ]);
251
+ b.writeUInt16LE(Math.min(width, 65535), 6);
252
+ b.writeUInt16LE(Math.min(height, 65535), 8);
253
+ return b;
254
+ }
255
+ function blankPreviewImage(img) {
256
+ const b = Buffer.from(img.buffer, img.byteOffset, img.byteLength);
257
+ if (b.length >= 24 && b.readUInt32BE(0) === 2303741511) {
258
+ const w = b.readUInt32BE(16);
259
+ const h = b.readUInt32BE(20);
260
+ if (w > 0 && h > 0 && w <= 8192 && h <= 8192) return blankPng(w, h);
261
+ return blankPng(1, 1);
262
+ }
263
+ if (b.length >= 10 && b.toString("latin1", 0, 3) === "GIF") return blankGif(b.readUInt16LE(6), b.readUInt16LE(8));
264
+ if (b.length >= 26 && b.toString("latin1", 0, 2) === "BM") {
265
+ const out = Buffer.from(b);
266
+ const pixelOffset = out.readUInt32LE(10);
267
+ if (pixelOffset > 0 && pixelOffset < out.length) out.fill(255, pixelOffset);
268
+ return out;
269
+ }
270
+ return null;
271
+ }
272
+ var isTextUnit = (u) => u >= 32 && u < 127 || u >= 160 && u <= 55295 || u >= 57344 && u <= 65533 || u === 9;
273
+ function isBindingPath(s) {
274
+ const t = s.trim();
275
+ return /^\.?\/[^\s]*\//.test(t) || /^[^\s]*\[\d+\][^\s]*\//.test(t) || /[A-Za-z]\.Code@code\.[A-Z]/.test(t);
276
+ }
277
+ function scrubUtf16Runs(buf, ctx, mode, onHit, skipBinding = false) {
278
+ let changed = false;
279
+ const mc = ctx.maskChar.charCodeAt(0);
280
+ const minRun = ctx.literals.some((l) => l.norm.length < 6) ? 2 : 6;
281
+ const literalOnly = { ...ctx, rules: [] };
282
+ const binCtx = binaryCtx(ctx);
283
+ for (let align = 0; align < 2; align++) {
284
+ let runStart = -1;
285
+ let text = "";
286
+ const flush = () => {
287
+ if (runStart >= 0 && text.length >= minRun && !(skipBinding && isBindingPath(text))) {
288
+ for (const h of findPii(text, text.length >= 6 ? binCtx : literalOnly)) {
289
+ onHit(h);
290
+ if (mode !== "mask") continue;
291
+ const orig = text.slice(h.index, h.index + h.length);
292
+ for (let k = 0; k < h.length; k++) {
293
+ if (h.masked[k] !== orig[k]) {
294
+ buf.writeUInt16LE(mc, runStart + (h.index + k) * 2);
295
+ changed = true;
296
+ }
297
+ }
298
+ }
299
+ }
300
+ runStart = -1;
301
+ text = "";
302
+ };
303
+ for (let off = align; off + 1 < buf.length; off += 2) {
304
+ const u = buf.readUInt16LE(off);
305
+ if (isTextUnit(u)) {
306
+ if (runStart < 0) runStart = off;
307
+ text += String.fromCharCode(u);
308
+ } else flush();
309
+ }
310
+ flush();
311
+ }
312
+ return changed;
313
+ }
314
+ function scrubAsciiRuns(buf, ctx, mode, onHit) {
315
+ let changed = false;
316
+ let runStart = -1;
317
+ const byteCtx = { ...binaryCtx(ctx), maskChar: "*" };
318
+ const flush = (end) => {
319
+ if (runStart >= 0 && end - runStart >= 6) {
320
+ const text = buf.toString("latin1", runStart, end);
321
+ for (const h of findPii(text, byteCtx)) {
322
+ onHit({ ...h, masked: h.masked.split("*").join(ctx.maskChar) });
323
+ if (mode !== "mask") continue;
324
+ for (let k = 0; k < h.length; k++) {
325
+ if (h.masked[k] !== text[h.index + k]) {
326
+ buf[runStart + h.index + k] = 42;
327
+ changed = true;
328
+ }
329
+ }
330
+ }
331
+ }
332
+ runStart = -1;
333
+ };
334
+ for (let i = 0; i < buf.length; i++) {
335
+ const c = buf[i];
336
+ if (c >= 32 && c < 127) {
337
+ if (runStart < 0) runStart = i;
338
+ } else flush(i);
339
+ }
340
+ flush(buf.length);
341
+ return changed;
342
+ }
343
+ function looksUtf16le(b) {
344
+ if (b.length < 2 || b.length % 2 !== 0) return false;
345
+ let text = 0;
346
+ let asciiHi0 = 0;
347
+ const n = b.length / 2;
348
+ for (let i = 0; i < b.length; i += 2) {
349
+ const u = b[i] | b[i + 1] << 8;
350
+ if (isTextUnit(u) || u === 10 || u === 13 || u === 65279) text++;
351
+ if (b[i + 1] === 0 && b[i] !== 0) asciiHi0++;
352
+ }
353
+ return text / n >= 0.95 && asciiHi0 > 0;
354
+ }
355
+ function utf8WithOffsets(b, start, end) {
356
+ const text = [];
357
+ const at = [];
358
+ let i = start;
359
+ while (i < end) {
360
+ const c = b[i];
361
+ const len = c < 128 ? 1 : c >= 240 ? 4 : c >= 224 ? 3 : c >= 192 ? 2 : 1;
362
+ const ch = len === 1 && c >= 128 ? "\uFFFD" : b.toString("utf8", i, Math.min(end, i + len));
363
+ for (let k = 0; k < ch.length; k++) {
364
+ text.push(ch[k]);
365
+ at.push(i);
366
+ }
367
+ i += len;
368
+ }
369
+ return { text: text.join(""), at };
370
+ }
371
+ function scrubTextBytes(b, start, end, ctx, mode, onHit) {
372
+ const { text, at } = utf8WithOffsets(b, start, end);
373
+ let changed = false;
374
+ for (const h of findPii(text, { ...ctx, maskChar: "*" })) {
375
+ onHit({ ...h, masked: h.masked.split("*").join(ctx.maskChar) });
376
+ if (mode !== "mask") continue;
377
+ for (let k = 0; k < h.length; k++) {
378
+ const pos = at[h.index + k];
379
+ if (h.masked[k] !== text[h.index + k] && b[pos] < 128) {
380
+ b[pos] = 42;
381
+ changed = true;
382
+ }
383
+ }
384
+ }
385
+ return changed;
386
+ }
387
+ var XMP_HEADER = "http://ns.adobe.com/xap/1.0/\0";
388
+ function scrubImageMeta(buf, ctx, mode, onHit) {
389
+ let changed = false;
390
+ if (buf.length > 4 && buf[0] === 255 && buf[1] === 216) {
391
+ for (let o = 2; o + 4 <= buf.length; ) {
392
+ if (buf[o] !== 255) break;
393
+ const m = buf[o + 1];
394
+ if (m === 216 || m === 1 || m >= 208 && m <= 215) {
395
+ o += 2;
396
+ continue;
397
+ }
398
+ const len = buf.readUInt16BE(o + 2);
399
+ const segStart = o + 4;
400
+ const segEnd = Math.min(buf.length, o + 2 + len);
401
+ if (m === 225 && buf.toString("latin1", segStart, segStart + XMP_HEADER.length) === XMP_HEADER) {
402
+ if (scrubTextBytes(buf, segStart + XMP_HEADER.length, segEnd, ctx, mode, onHit)) changed = true;
403
+ } else if (m === 254) {
404
+ if (scrubTextBytes(buf, segStart, segEnd, ctx, mode, onHit)) changed = true;
405
+ }
406
+ if (m === 218) break;
407
+ o += 2 + len;
408
+ }
409
+ } else if (buf.length > 8 && buf.readUInt32BE(0) === 2303741511) {
410
+ for (let o = 8; o + 12 <= buf.length; ) {
411
+ const len = buf.readUInt32BE(o);
412
+ const type = buf.toString("latin1", o + 4, o + 8);
413
+ const dStart = o + 8;
414
+ const dEnd = Math.min(buf.length, dStart + len);
415
+ let chunkChanged = false;
416
+ if (type === "tEXt" || type === "iTXt") {
417
+ const k = buf.indexOf(0, dStart);
418
+ let textStart = k + 1;
419
+ if (type === "iTXt" && k >= 0) {
420
+ const compressed = buf[k + 1] === 1;
421
+ const lang = buf.indexOf(0, k + 3);
422
+ const trans = lang >= 0 ? buf.indexOf(0, lang + 1) : -1;
423
+ textStart = trans + 1;
424
+ if (compressed) {
425
+ if (mode === "check") {
426
+ try {
427
+ for (const h of findPii(inflateSync(buf.subarray(textStart, dEnd)).toString("utf8"), ctx)) onHit(h);
428
+ } catch {
429
+ }
430
+ }
431
+ textStart = -1;
432
+ }
433
+ }
434
+ if (k >= 0 && textStart > 0 && textStart <= dEnd) chunkChanged = scrubTextBytes(buf, textStart, dEnd, ctx, mode, onHit);
435
+ } else if (type === "zTXt" && mode === "check") {
436
+ const k = buf.indexOf(0, dStart);
437
+ try {
438
+ for (const h of findPii(inflateSync(buf.subarray(k + 2, dEnd)).toString("latin1"), ctx)) onHit(h);
439
+ } catch {
440
+ }
441
+ }
442
+ if (chunkChanged) {
443
+ changed = true;
444
+ buf.writeUInt32BE(crc32(buf.subarray(o + 4, dEnd)), dEnd);
445
+ }
446
+ if (type === "IEND") break;
447
+ o += 12 + len;
448
+ }
449
+ }
450
+ return changed;
451
+ }
452
+ var OLE_SIG = Buffer.from("d0cf11e0a1b11ae1", "hex");
453
+ function isOleBin(b) {
454
+ const i = b.indexOf(OLE_SIG);
455
+ return i >= 0 && i < 16;
456
+ }
457
+ function isMetaImage(b) {
458
+ return b.length > 3 && b[0] === 255 && b[1] === 216 && b[2] === 255 || b.length > 8 && b.readUInt32BE(0) === 2303741511;
459
+ }
460
+ function isEmf(b) {
461
+ return b.length > 44 && b.readUInt32LE(0) === 1 && b.readUInt32LE(40) === 1179469088;
462
+ }
463
+ var decodeXmlEntities = (s) => s.replace(/&#x([0-9a-fA-F]+);/g, (e, h) => {
464
+ const c = parseInt(h, 16);
465
+ return c <= 1114111 ? String.fromCodePoint(c) : e;
466
+ }).replace(/&#(\d+);/g, (e, d) => {
467
+ const c = Number(d);
468
+ return c <= 1114111 ? String.fromCodePoint(c) : e;
469
+ }).replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
470
+ function checkXmlText(xml, ctx, onHit) {
471
+ for (const h of findPii(decodeXmlEntities(xml.replace(/(?:<[^>]*>)+/g, " ")), ctx)) onHit(h);
472
+ for (const m of xml.matchAll(/=\s*"([^"]{5,})"/g)) for (const h of findPii(decodeXmlEntities(m[1]), ctx)) onHit(h);
473
+ }
474
+ async function checkEmbeddedOle(data, ctx, onHit) {
475
+ let inner;
476
+ try {
477
+ inner = CFB.parse(data.subarray(data.indexOf(OLE_SIG)));
478
+ } catch {
479
+ scrubUtf16Runs(Buffer.from(data), ctx, "check", onHit);
480
+ return;
481
+ }
482
+ for (let i = 0; i < inner.FullPaths.length; i++) {
483
+ const e = inner.FileIndex[i];
484
+ if (e.type !== 2 || !e.content) continue;
485
+ const c = Buffer.from(e.content);
486
+ if (c.length >= 4 && c.readUInt32LE(0) === 67324752) {
487
+ let zip = null;
488
+ try {
489
+ zip = await JSZip.loadAsync(c);
490
+ } catch {
491
+ }
492
+ if (!zip) {
493
+ scrubUtf16Runs(c, ctx, "check", onHit);
494
+ continue;
495
+ }
496
+ for (const [name, z] of Object.entries(zip.files)) {
497
+ if (z.dir) continue;
498
+ const b = Buffer.from(await z.async("uint8array"));
499
+ if (/\.(xml|rels|vml)$/i.test(name)) checkXmlText(b.toString("utf8"), ctx, onHit);
500
+ else scrubUtf16Runs(b, ctx, "check", onHit);
501
+ }
502
+ } else if (/^(?:\xef\xbb\xbf)?\s*<(?:\?xml|[A-Za-z])/.test(c.toString("latin1", 0, 64))) {
503
+ checkXmlText(c.toString("utf8"), ctx, onHit);
504
+ } else {
505
+ scrubUtf16Runs(c, ctx, "check", onHit);
506
+ if (/(?:^|\/)(?:Workbook|Book)$/.test(inner.FullPaths[i])) scrubAsciiRuns(c, ctx, "check", onHit);
507
+ }
508
+ }
509
+ }
510
+
511
+ // src/redact-hwpx.ts
512
+ import JSZip2 from "jszip";
513
+ var WS_TAGS = /* @__PURE__ */ new Set(["tab", "lineBreak", "br", "fwSpace", "hwSpace", "nbSpace"]);
514
+ var GAP_SEPARATORS = /* @__PURE__ */ new Set([...WS_TAGS, "deleteBegin", "deleteEnd", "insertBegin", "insertEnd"]);
515
+ var FLOW_OBJECTS = /* @__PURE__ */ new Set([
516
+ "tbl",
517
+ "pic",
518
+ "rect",
519
+ "ellipse",
520
+ "line",
521
+ "arc",
522
+ "polygon",
523
+ "curve",
524
+ "connectLine",
525
+ "container",
526
+ "ole",
527
+ "chart",
528
+ "textart",
529
+ "video",
530
+ "equation",
531
+ "footNote",
532
+ "endNote",
533
+ "header",
534
+ "footer",
535
+ "hiddenComment",
536
+ "memo",
537
+ "memogroup"
538
+ ]);
539
+ var T_INNER_TRANSPARENT = /* @__PURE__ */ new Set(["markpenBegin", "markpenEnd", "hyphen", "titleMark", "indexmark"]);
540
+ var ENTITY_RE = /&(?:#x[0-9a-fA-F]+|#\d+|[a-zA-Z]+);/y;
541
+ function decodeEntity(ent) {
542
+ switch (ent) {
543
+ case "&lt;":
544
+ return "<";
545
+ case "&gt;":
546
+ return ">";
547
+ case "&amp;":
548
+ return "&";
549
+ case "&quot;":
550
+ return '"';
551
+ case "&apos;":
552
+ return "'";
553
+ }
554
+ const m = /^&#(x?)([0-9a-fA-F]+);$/.exec(ent);
555
+ if (m) {
556
+ const code = parseInt(m[2], m[1] ? 16 : 10);
557
+ if (code >= 0 && code <= 1114111) return String.fromCodePoint(code);
558
+ }
559
+ return ent;
560
+ }
561
+ function appendDecoded(xml, start, end, out) {
562
+ let i = start;
563
+ while (i < end) {
564
+ if (xml[i] === "&") {
565
+ ENTITY_RE.lastIndex = i;
566
+ const m = ENTITY_RE.exec(xml);
567
+ if (m && i + m[0].length <= end) {
568
+ const dec = decodeEntity(m[0]);
569
+ for (let k = 0; k < dec.length; k++) {
570
+ out.text += dec[k];
571
+ out.pos.push(k === 0 && dec.length === 1 ? [i, i + m[0].length] : null);
572
+ }
573
+ i += m[0].length;
574
+ continue;
575
+ }
576
+ }
577
+ out.text += xml[i];
578
+ out.pos.push([i, i + 1]);
579
+ i++;
580
+ }
581
+ }
582
+ function appendRaw(xml, start, end, out) {
583
+ for (let i = start; i < end; i++) {
584
+ out.text += xml[i];
585
+ out.pos.push([i, i + 1]);
586
+ }
587
+ }
588
+ var localName = (qname) => qname.slice(qname.indexOf(":") + 1);
589
+ function appendTContent(xml, start, end, out) {
590
+ const tokRe = /<!--[\s\S]*?-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\?[\s\S]*?\?>|<\/?([^\s/>!?]+)[^>]*>/g;
591
+ tokRe.lastIndex = start;
592
+ let cursor = start;
593
+ let m;
594
+ while ((m = tokRe.exec(xml)) !== null && m.index < end) {
595
+ appendDecoded(xml, cursor, m.index, out);
596
+ if (m[1] !== void 0) appendRaw(xml, m.index + 9, m.index + 9 + m[1].length, out);
597
+ else if (m[2] !== void 0 && !T_INNER_TRANSPARENT.has(localName(m[2]))) {
598
+ out.text += " ";
599
+ out.pos.push(null);
600
+ }
601
+ cursor = m.index + m[0].length;
602
+ }
603
+ appendDecoded(xml, cursor, end, out);
604
+ }
605
+ function gapSeparates(gap) {
606
+ const tagRe = /<(\/?)([^\s/>!?]+)(?:"[^"]*"|'[^']*'|[^>"'])*>/g;
607
+ let m;
608
+ while ((m = tagRe.exec(gap)) !== null) {
609
+ const l = localName(m[2]);
610
+ if (m[1] === "" && FLOW_OBJECTS.has(l) && !m[0].endsWith("/>")) {
611
+ if (l === "equation" || l === "footNote" || l === "endNote") return true;
612
+ const end = findElementEnd(gap, m.index);
613
+ if (end < 0) return true;
614
+ const pos = l === "tbl" ? /<(?:[\w]+:)?pos\b[^>]*>/.exec(gap.slice(m.index, end))?.[0] : void 0;
615
+ if (pos && /\btreatAsChar="1"/.test(pos)) return true;
616
+ tagRe.lastIndex = end;
617
+ continue;
618
+ }
619
+ if (GAP_SEPARATORS.has(l)) return true;
620
+ }
621
+ return false;
622
+ }
623
+ function paragraphText(xml, para) {
624
+ const out = { text: "", pos: [] };
625
+ let prevEnd = -1;
626
+ for (const t of para.tRanges) {
627
+ if (prevEnd >= 0 && gapSeparates(xml.slice(prevEnd, t.contentStart))) {
628
+ out.text += " ";
629
+ out.pos.push(null);
630
+ }
631
+ if (!t.selfClosing) appendTContent(xml, t.contentStart, t.contentEnd, out);
632
+ prevEnd = t.contentEnd;
633
+ }
634
+ return out;
635
+ }
636
+ function allParagraphs(scan) {
637
+ const out = [...scan.bodyParagraphs, ...scan.excludedParagraphs];
638
+ const walk = (t, depth) => {
639
+ if (depth > 32) return;
640
+ for (const row of t.rows) for (const cell of row) {
641
+ out.push(...cell.paragraphs);
642
+ for (const n of cell.tables) walk(n, depth + 1);
643
+ }
644
+ };
645
+ for (const t of [...scan.tables, ...scan.orphanTables]) walk(t, 0);
646
+ return out;
647
+ }
648
+ var CONTAINER_WHERE = {
649
+ header: "header",
650
+ footer: "footer",
651
+ footNote: "footnote",
652
+ endNote: "endnote",
653
+ fn: "footnote",
654
+ en: "endnote",
655
+ caption: "caption",
656
+ memo: "memo",
657
+ memogroup: "memo",
658
+ hiddenComment: "hidden"
659
+ };
660
+ function containerRanges(xml) {
661
+ const out = [];
662
+ for (const m of xml.matchAll(/<((?:[\w]+:)?(header|footer|footNote|endNote|caption|memogroup|memo|hiddenComment))[\s>]/g)) {
663
+ const end = findElementEnd(xml, m.index);
664
+ if (end > 0) out.push({ start: m.index, end, where: CONTAINER_WHERE[m[2]] ?? "other" });
665
+ }
666
+ return out;
667
+ }
668
+ function paragraphWhere(para, ranges) {
669
+ if (para.kind === "body") return "body";
670
+ if (para.kind === "draw") return "textbox";
671
+ if (para.kind === "cell") return para.inTextbox ? "textbox" : "table";
672
+ let best;
673
+ for (const r of ranges) {
674
+ if (r.start < para.start && para.start < r.end && (!best || r.start > best.start)) best = r;
675
+ }
676
+ return best?.where ?? "other";
677
+ }
678
+ function maskMapped(m, ctx, onHit, splices) {
679
+ for (const h of findPii(m.text, ctx)) {
680
+ onHit(h);
681
+ if (!splices) continue;
682
+ let run = null;
683
+ for (let k = 0; k < h.length; k++) {
684
+ const p = m.pos[h.index + k];
685
+ if (!p || h.masked[k] === m.text[h.index + k]) {
686
+ run = null;
687
+ continue;
688
+ }
689
+ if (run && run.end === p[0]) {
690
+ run.end = p[1];
691
+ run.replacement += ctx.maskChar;
692
+ } else {
693
+ run = { start: p[0], end: p[1], replacement: ctx.maskChar };
694
+ splices.push(run);
695
+ }
696
+ }
697
+ }
698
+ }
699
+ function applySplicesLinear(src, splices) {
700
+ const sorted = [...splices].sort((a, b) => a.start - b.start || a.end - b.end);
701
+ const parts = [];
702
+ let cursor = 0;
703
+ for (const s of sorted) {
704
+ if (s.start < cursor) throw new Error("\uB9C8\uC2A4\uD0B9 splice \uBC94\uC704 \uACB9\uCE68 \u2014 \uB0B4\uBD80 \uC624\uB958");
705
+ parts.push(src.slice(cursor, s.start), s.replacement);
706
+ cursor = s.end;
707
+ }
708
+ parts.push(src.slice(cursor));
709
+ return parts.join("");
710
+ }
711
+ var STRUCTURAL_ELEMENTS = /* @__PURE__ */ new Set(["lineseg", "linesegarray"]);
712
+ function scrubLooseText(xml, owned, ctx, partWhere, report, splices) {
713
+ const tokenRe = /<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)\]\]>|<\?([\s\S]*?)\?>|<!((?:"[^"]*"|'[^']*'|[^>"'])*)>|<(\/?)([^\s/>!?]+)((?:"[^"]*"|'[^']*'|[^>"'])*?)(\/?)>|([^<]+)/g;
714
+ const stack = [];
715
+ const shortLits = ctx.literals.some((l) => l.norm.length < 6);
716
+ const minText = shortLits ? 2 : 5;
717
+ let oi = 0;
718
+ let pending = null;
719
+ let pendingWhere = "other";
720
+ const whereOf = (fallback) => {
721
+ if (partWhere) return partWhere;
722
+ for (let i = stack.length - 1; i >= 0; i--) {
723
+ const l = stack[i];
724
+ if (l === "stringParam" || l === "fieldBegin" || l === "parameters") return "field";
725
+ if (l === "shapeComment") return "attribute";
726
+ if (CONTAINER_WHERE[l]) return CONTAINER_WHERE[l];
727
+ }
728
+ return fallback;
729
+ };
730
+ const flush = () => {
731
+ if (pending && pending.text.length >= minText && !isBindingPath(pending.text)) {
732
+ const where = pendingWhere;
733
+ maskMapped(pending, ctx, (h) => report(h, where), splices);
734
+ }
735
+ pending = null;
736
+ };
737
+ const addText = (fill) => {
738
+ if (!pending) {
739
+ pending = { text: "", pos: [] };
740
+ pendingWhere = whereOf("other");
741
+ }
742
+ fill(pending);
743
+ };
744
+ const scanInner = (start, text) => {
745
+ const mapped = { text: "", pos: [] };
746
+ appendRaw(xml, start, start + text.length, mapped);
747
+ maskMapped(mapped, ctx, (h) => report(h, partWhere ?? "other"), splices);
748
+ };
749
+ const isOwned = (at) => {
750
+ while (oi < owned.length && owned[oi][1] <= at) oi++;
751
+ return oi < owned.length && owned[oi][0] <= at && at < owned[oi][1];
752
+ };
753
+ for (const m of xml.matchAll(tokenRe)) {
754
+ const at = m.index;
755
+ if (m[9] !== void 0 || m[2] !== void 0) {
756
+ if (isOwned(at)) {
757
+ flush();
758
+ continue;
759
+ }
760
+ if (m[9] !== void 0) addText((out) => appendDecoded(xml, at, at + m[9].length, out));
761
+ else addText((out) => appendRaw(xml, at + 9, at + 9 + m[2].length, out));
762
+ continue;
763
+ }
764
+ if (m[1] !== void 0) {
765
+ scanInner(at + 4, m[1]);
766
+ continue;
767
+ }
768
+ if (m[3] !== void 0) {
769
+ scanInner(at + 2, m[3]);
770
+ continue;
771
+ }
772
+ if (m[4] !== void 0) {
773
+ flush();
774
+ scanInner(at + 2, m[4]);
775
+ continue;
776
+ }
777
+ if (m[6] === void 0) continue;
778
+ flush();
779
+ const local = localName(m[6]);
780
+ if (m[5] === "/") {
781
+ const i = stack.lastIndexOf(local);
782
+ if (i >= 0) stack.length = i;
783
+ continue;
784
+ }
785
+ const attrs = m[7] ?? "";
786
+ if (attrs.includes("=") && !STRUCTURAL_ELEMENTS.has(local)) {
787
+ const attrBase = at + 1 + m[6].length;
788
+ for (const a of attrs.matchAll(/([^\s=]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) {
789
+ const val = a[2] ?? a[3] ?? "";
790
+ if (val.length < minText || !shortLits && !/[0-9A-Za-z]/.test(val) || isBindingPath(val)) continue;
791
+ const valStart = attrBase + a.index + a[0].length - val.length - 1;
792
+ const mapped = { text: "", pos: [] };
793
+ appendDecoded(xml, valStart, valStart + val.length, mapped);
794
+ maskMapped(mapped, ctx, (h) => report(h, partWhere ?? "attribute"), splices);
795
+ }
796
+ }
797
+ if (m[8] !== "/") stack.push(local);
798
+ }
799
+ flush();
800
+ }
801
+ function scrubXml(xml, part, ctx, mode, hits) {
802
+ const splices = mode === "mask" ? [] : null;
803
+ const partWhere = /content\.hpf$/i.test(part) ? "metadata" : null;
804
+ const owned = [];
805
+ if (/<(?:[\w]+:)?p[\s>]/.test(xml) && /<(?:[\w]+:)?t[\s>/]/.test(xml)) {
806
+ const scan = scanSectionXml(xml, 0);
807
+ const ranges = containerRanges(xml);
808
+ for (const para of allParagraphs(scan)) {
809
+ for (const t of para.tRanges) owned.push([t.contentStart, t.contentEnd]);
810
+ const where = paragraphWhere(para, ranges);
811
+ maskMapped(paragraphText(xml, para), ctx, (h) => hits.push({ rule: h.rule, masked: h.masked, part, where }), splices);
812
+ }
813
+ owned.sort((a, b) => a[0] - b[0]);
814
+ }
815
+ scrubLooseText(xml, owned, ctx, partWhere, (h, where) => hits.push({ rule: h.rule, masked: h.masked, part, where }), splices);
816
+ if (!splices || splices.length === 0) return null;
817
+ if (xml.includes("linesegarray")) splices.push(...allLinesegRemovalSplices(xml));
818
+ return applySplicesLinear(xml, splices);
819
+ }
820
+ function scrubPlainText(text, part, ctx, mode, hits) {
821
+ const where = /^Preview\//i.test(part) ? "preview" : "other";
822
+ let changed = false;
823
+ const lines = text.split("\n").map((line) => {
824
+ const found = findPii(line, ctx);
825
+ if (found.length === 0) return line;
826
+ let out = "";
827
+ let cursor = 0;
828
+ for (const h of found) {
829
+ hits.push({ rule: h.rule, masked: h.masked, part, where });
830
+ out += line.slice(cursor, h.index) + h.masked;
831
+ cursor = h.index + h.length;
832
+ }
833
+ changed = true;
834
+ return out + line.slice(cursor);
835
+ });
836
+ return mode === "mask" && changed ? lines.join("\n") : null;
837
+ }
838
+ var isXmlName = (n) => /\.(xml|hpf|rdf)$/i.test(n);
839
+ var isTextName = (n) => /\.(txt|js|json|csv|html?)$/i.test(n) || /^Scripts\//i.test(n);
840
+ var isPreviewImage = (n) => /^Preview\/PrvImage\./i.test(n);
841
+ var nameList = (names) => names.length <= 5 ? names.join(", ") : `${names.slice(0, 5).join(", ")} \uC678 ${names.length - 5}\uAC1C`;
842
+ function literalLeftovers(xml, part, ctx, hits) {
843
+ if (ctx.literals.length === 0) return;
844
+ const stripped = normalizeForDetect(xml.replace(/<[^>]*>/g, "").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&"));
845
+ for (const { lit } of findLiterals(stripped, ctx.literals)) {
846
+ if (!hits.some((x) => x.part === part && x.masked === lit.masked)) hits.push({ rule: lit.rule, masked: lit.masked, part, where: "other" });
847
+ }
848
+ }
849
+ async function scrubHwpx(original, ctx, mode) {
850
+ const zip = await JSZip2.loadAsync(original);
851
+ const hits = [];
852
+ const warnings = [];
853
+ const unscanned = [];
854
+ const replacements = /* @__PURE__ */ new Map();
855
+ const encoder = new TextEncoder();
856
+ const previews = [];
857
+ const pictures = [];
858
+ const embedded = [];
859
+ const decode = (bytes) => {
860
+ if (bytes.length >= 2 && bytes[0] === 255 && bytes[1] === 254 || bytes.includes(0) && looksUtf16le(bytes)) {
861
+ return { text: bytes.toString("utf16le"), encode: (x) => Buffer.from(x, "utf16le") };
862
+ }
863
+ return bytes.includes(0) ? null : { text: bytes.toString("utf8"), encode: (x) => encoder.encode(x) };
864
+ };
865
+ for (const [name, entry] of Object.entries(zip.files)) {
866
+ if (entry.dir || name === "mimetype") continue;
867
+ if (isPreviewImage(name)) {
868
+ previews.push(name);
869
+ continue;
870
+ }
871
+ const bytes = Buffer.from(await entry.async("uint8array"));
872
+ if (isXmlName(name) || /^BinData\/.*\.svg$/i.test(name)) {
873
+ const dec2 = decode(bytes);
874
+ if (!dec2) {
875
+ unscanned.push(name);
876
+ continue;
877
+ }
878
+ const next = scrubXml(dec2.text, name, ctx, mode, hits);
879
+ if (next !== null) replacements.set(name, dec2.encode(next));
880
+ if (mode === "check") literalLeftovers(dec2.text, name, ctx, hits);
881
+ continue;
882
+ }
883
+ if (/^BinData\//i.test(name)) {
884
+ const report = (where) => (h) => {
885
+ hits.push({ rule: h.rule, masked: h.masked, part: name, where });
886
+ };
887
+ if (isOleBin(bytes) || isEmf(bytes)) {
888
+ embedded.push(name);
889
+ if (mode === "check") {
890
+ if (isOleBin(bytes)) await checkEmbeddedOle(bytes, ctx, report("other"));
891
+ else scrubUtf16Runs(bytes, ctx, "check", report("other"));
892
+ }
893
+ } else {
894
+ pictures.push(name);
895
+ if (isMetaImage(bytes) && scrubImageMeta(bytes, ctx, mode, report("metadata"))) replacements.set(name, bytes);
896
+ }
897
+ continue;
898
+ }
899
+ const dec = decode(bytes);
900
+ if (dec) {
901
+ const next = scrubPlainText(dec.text, name, ctx, mode, hits);
902
+ if (next !== null) replacements.set(name, dec.encode(next));
903
+ } else if (isTextName(name)) {
904
+ unscanned.push(name);
905
+ } else {
906
+ warnings.push(`\uC54C \uC218 \uC5C6\uB294 \uBC14\uC774\uB108\uB9AC \uC5D4\uD2B8\uB9AC ${name} \uB294 \uAC80\uC0AC\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4`);
907
+ }
908
+ }
909
+ if (mode === "mask") {
910
+ if (hits.length > 0 || ctx.literals.length > 0) {
911
+ for (const name of previews) {
912
+ const blank = blankPreviewImage(await zip.file(name).async("uint8array"));
913
+ if (blank) replacements.set(name, blank);
914
+ else warnings.push(`${name}: \uD615\uC2DD\uC744 \uBAB0\uB77C \uBBF8\uB9AC\uBCF4\uAE30 \uC774\uBBF8\uC9C0\uB97C \uC9C0\uC6B0\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uCCAB \uCABD \uB80C\uB354\uC5D0 PII \uAC00 \uBCF4\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4`);
915
+ }
916
+ if (previews.length > 0) warnings.push(`\uBBF8\uB9AC\uBCF4\uAE30 \uC774\uBBF8\uC9C0(${previews.join(", ")})\uB97C \uAC19\uC740 \uD06C\uAE30\uC758 \uD770 \uC774\uBBF8\uC9C0\uB85C \uBC14\uAFE8\uC2B5\uB2C8\uB2E4 \u2014 \uD55C\uCEF4\uC5D0\uC11C \uC800\uC7A5\uD558\uBA74 \uB2E4\uC2DC \uB9CC\uB4E4\uC5B4\uC9D1\uB2C8\uB2E4`);
917
+ }
918
+ if (pictures.length > 0) warnings.push(`\uC0BD\uC785 \uADF8\uB9BC ${pictures.length}\uAC1C(${nameList(pictures)})\uB294 \uD654\uC18C \uC18D \uAE00\uC790(\uC2A4\uCE94 \uBB38\uC11C \uB4F1)\uB97C \uD0D0\uC9C0\uD558\uC9C0 \uBABB\uD569\uB2C8\uB2E4 \u2014 JPEG\xB7PNG \uBA54\uD0C0\uB370\uC774\uD130\uB9CC \uAC80\uC0AC\uD588\uC2B5\uB2C8\uB2E4`);
919
+ if (embedded.length > 0) warnings.push(`\uC0BD\uC785 \uAC1C\uCCB4 ${embedded.length}\uAC1C(${nameList(embedded)} \u2014 OLE\xB7EMF)\uB294 \uAC00\uB9AC\uC9C0 \uBABB\uD569\uB2C8\uB2E4 \u2014 \uC548\uC758 \uAE00\uC790\uC5D0 PII \uAC00 \uC788\uC73C\uBA74 \uC7AC\uAC80\uC0AC \uC794\uC874\uC73C\uB85C \uBCF4\uACE0\uB429\uB2C8\uB2E4`);
920
+ if (unscanned.length > 0) warnings.push(`\uC778\uCF54\uB529\uC744 \uBAB0\uB77C \uAC80\uC0AC\uD558\uC9C0 \uBABB\uD55C \uAE00\uC790 \uC5D4\uD2B8\uB9AC: ${unscanned.join(", ")}`);
921
+ }
922
+ const data = mode === "mask" && replacements.size > 0 ? patchZipEntries(original, replacements) : original;
923
+ return { data, hits, warnings, unscanned };
924
+ }
925
+
926
+ // src/redact-hwp5.ts
927
+ import { createRequire as createRequire2 } from "module";
928
+ import { deflateRawSync, inflateRawSync, constants as zlibConstants } from "zlib";
929
+ var require3 = createRequire2(import.meta.url);
930
+ var CFB2 = require3("cfb");
931
+ var CTRL_WHERE = {
932
+ "tbl ": "table",
933
+ head: "header",
934
+ foot: "footer",
935
+ "fn ": "footnote",
936
+ "en ": "endnote",
937
+ "gso ": "textbox",
938
+ tcmt: "hidden"
939
+ };
940
+ var BREAKING_CTRLS = /* @__PURE__ */ new Set(["eqed", "atno", "fn ", "en "]);
941
+ function ctrlName(data) {
942
+ if (data.length < 4) return "";
943
+ const id = data.readUInt32LE(0);
944
+ return String.fromCharCode(id >>> 24, id >>> 16 & 255, id >>> 8 & 255, id & 255);
945
+ }
946
+ function decodeParaText(data) {
947
+ let text = "";
948
+ const pos = [];
949
+ let i = 0;
950
+ const sep = (c = " ") => {
951
+ text += c;
952
+ pos.push(-1);
953
+ };
954
+ while (i + 1 < data.length) {
955
+ const ch = data.readUInt16LE(i);
956
+ if (ch >= 32) {
957
+ text += String.fromCharCode(ch);
958
+ pos.push(i);
959
+ i += 2;
960
+ continue;
961
+ }
962
+ if (ch === 13) {
963
+ sep("\n");
964
+ i += 2;
965
+ continue;
966
+ }
967
+ if (ch === 10) {
968
+ if (i + 18 <= data.length && data.readUInt16LE(i + 2) === 11) {
969
+ sep();
970
+ i += 18;
971
+ continue;
972
+ }
973
+ sep("\n");
974
+ i += 2;
975
+ continue;
976
+ }
977
+ const extended = isExtendedOnlyCtrlChar(ch);
978
+ if ((extended || ch >= 4 && ch <= 9 || ch === 19 || ch === 20) && i + 16 <= data.length) {
979
+ if (ch === 9 || extended && BREAKING_CTRLS.has(ctrlName(data.subarray(i + 2, i + 6)))) sep();
980
+ i += 16;
981
+ continue;
982
+ }
983
+ if (ch === 30 || ch === 31) sep();
984
+ i += 2;
985
+ }
986
+ return { text, pos };
987
+ }
988
+ function scrubParaText(data, ctx, mode, where, report) {
989
+ const { text, pos } = decodeParaText(data);
990
+ let changed = false;
991
+ const mc = ctx.maskChar.charCodeAt(0);
992
+ for (const h of findPii(text, ctx)) {
993
+ report(h.rule, h.masked, where);
994
+ if (mode !== "mask") continue;
995
+ for (let k = 0; k < h.length; k++) {
996
+ const p = pos[h.index + k];
997
+ if (p >= 0 && h.masked[k] !== text[h.index + k]) {
998
+ data.writeUInt16LE(mc, p);
999
+ changed = true;
1000
+ }
1001
+ }
1002
+ }
1003
+ return changed;
1004
+ }
1005
+ function scrubRecords(data, ctx, mode, report) {
1006
+ let changed = false;
1007
+ const ctrlStack = [];
1008
+ for (const rec of readRecords(data)) {
1009
+ while (ctrlStack.length > 0 && ctrlStack[ctrlStack.length - 1].level >= rec.level) ctrlStack.pop();
1010
+ if (rec.tagId === TAG_PARA_TEXT) {
1011
+ let where2 = "body";
1012
+ for (let i = ctrlStack.length - 1; i >= 0; i--) {
1013
+ const w = CTRL_WHERE[ctrlStack[i].id];
1014
+ if (w) {
1015
+ where2 = w;
1016
+ break;
1017
+ }
1018
+ }
1019
+ if (scrubParaText(rec.data, ctx, mode, where2, report)) changed = true;
1020
+ continue;
1021
+ }
1022
+ let where = "other";
1023
+ if (rec.tagId === TAG_CTRL_HEADER) {
1024
+ const id = ctrlName(rec.data);
1025
+ ctrlStack.push({ level: rec.level, id });
1026
+ where = id.startsWith("%") ? "field" : "attribute";
1027
+ } else if (rec.tagId === TAG_CTRL_DATA) where = "field";
1028
+ if (scrubUtf16Runs(rec.data, ctx, mode, (h) => report(h.rule, h.masked, where), true)) changed = true;
1029
+ }
1030
+ return changed;
1031
+ }
1032
+ function scrubRaw(data, ctx, mode, where, report, ascii) {
1033
+ const a = scrubUtf16Runs(data, ctx, mode, (h) => report(h.rule, h.masked, where));
1034
+ const b = ascii && scrubAsciiRuns(data, ctx, mode, (h) => report(h.rule, h.masked, where));
1035
+ return a || b;
1036
+ }
1037
+ function tryInflate(data) {
1038
+ try {
1039
+ return decompressStream(data);
1040
+ } catch {
1041
+ return null;
1042
+ }
1043
+ }
1044
+ function hasHancomTail(stream, raw) {
1045
+ return stream.length >= 8 && stream.readUInt32LE(stream.length - 4) === raw.length >>> 0 && stream.readUInt32LE(stream.length - 8) === crc32(raw);
1046
+ }
1047
+ var recompress = (raw, tail) => tail ? compressWithTail(raw) : deflateRawSync(raw);
1048
+ var BLOCK = 64;
1049
+ function changedSpans(a, b) {
1050
+ const spans = [];
1051
+ const n = Math.min(a.length, b.length);
1052
+ let start = -1;
1053
+ for (let o = 0; o < n; o += BLOCK) {
1054
+ const e = Math.min(n, o + BLOCK);
1055
+ const diff = a.compare(b, o, e, o, e) !== 0;
1056
+ if (diff && start < 0) start = o;
1057
+ else if (!diff && start >= 0) {
1058
+ spans.push([start, o]);
1059
+ start = -1;
1060
+ }
1061
+ }
1062
+ if (start >= 0) spans.push([start, n]);
1063
+ return spans;
1064
+ }
1065
+ function mayStartDeflate(b, o) {
1066
+ const type = b[o] >> 1 & 3;
1067
+ if (type === 3) return false;
1068
+ if (type === 0) return o + 5 <= b.length && (b.readUInt16LE(o + 1) ^ b.readUInt16LE(o + 3)) === 65535;
1069
+ return true;
1070
+ }
1071
+ function scanSlack(before, after, ctx, onHit) {
1072
+ let bytes = 0;
1073
+ for (const [s, e] of changedSpans(before, after)) {
1074
+ bytes += e - s;
1075
+ const region = Buffer.from(before.subarray(s, e));
1076
+ scrubUtf16Runs(region, ctx, "check", onHit);
1077
+ for (let o = 0; o + 8 <= region.length; o += BLOCK) {
1078
+ if (!mayStartDeflate(region, o)) continue;
1079
+ let out;
1080
+ try {
1081
+ out = inflateRawSync(region.subarray(o), { finishFlush: zlibConstants.Z_SYNC_FLUSH, maxOutputLength: 64 * 1024 * 1024 });
1082
+ } catch {
1083
+ continue;
1084
+ }
1085
+ if (out.length >= 16) scrubUtf16Runs(out, ctx, "check", onHit);
1086
+ }
1087
+ }
1088
+ return bytes;
1089
+ }
1090
+ var nameList2 = (names) => names.length <= 5 ? names.join(", ") : `${names.slice(0, 5).join(", ")} \uC678 ${names.length - 5}\uAC1C`;
1091
+ async function scrubHwp5(original, ctx, mode) {
1092
+ const file = Buffer.from(original.buffer, original.byteOffset, original.byteLength);
1093
+ const cfb = CFB2.parse(file);
1094
+ const headerEntry = CFB2.find(cfb, "/FileHeader");
1095
+ if (!headerEntry?.content) throw new Error("HWP FileHeader \uC2A4\uD2B8\uB9BC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4");
1096
+ const header = parseFileHeader(Buffer.from(headerEntry.content));
1097
+ if (header.flags & (FLAG_ENCRYPTED | FLAG_DISTRIBUTION | FLAG_DRM)) {
1098
+ throw new Error("\uBC30\uD3EC\uC6A9\xB7\uC554\uD638\xB7DRM \uBB38\uC11C\uB294 \uBCF8\uBB38\uC774 \uC554\uD638\uD654\uB3FC \uC788\uC5B4 \uB9C8\uC2A4\uD0B9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4");
1099
+ }
1100
+ const compressed = (header.flags & FLAG_COMPRESSED) !== 0;
1101
+ const hits = [];
1102
+ const warnings = [];
1103
+ const replaced = /* @__PURE__ */ new Map();
1104
+ const pictures = [];
1105
+ const embedded = [];
1106
+ let previewPath = null;
1107
+ for (let idx = 0; idx < cfb.FullPaths.length; idx++) {
1108
+ const entry = cfb.FileIndex[idx];
1109
+ if (entry.type !== 2 || !entry.content) continue;
1110
+ const path = cfb.FullPaths[idx].replace(/^[^/]*\//, "");
1111
+ const content = Buffer.from(entry.content);
1112
+ const part = path.replace(/[\x00-\x1f]/g, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`);
1113
+ const report = (rule, masked, where) => hits.push({ rule, masked, part, where });
1114
+ if (path === "FileHeader") continue;
1115
+ if (path === "PrvImage") {
1116
+ previewPath = path;
1117
+ continue;
1118
+ }
1119
+ let next = null;
1120
+ if (path.startsWith("BinData/")) {
1121
+ const inflated = tryInflate(content);
1122
+ const data2 = inflated ?? Buffer.from(content);
1123
+ const tail = inflated ? hasHancomTail(content, inflated) : false;
1124
+ if (isOleBin(data2) || isEmf(data2)) {
1125
+ embedded.push(part);
1126
+ if (mode === "check") {
1127
+ if (isOleBin(data2)) await checkEmbeddedOle(data2, ctx, (h) => report(h.rule, h.masked, "other"));
1128
+ else scrubUtf16Runs(data2, ctx, "check", (h) => report(h.rule, h.masked, "other"));
1129
+ }
1130
+ } else {
1131
+ pictures.push(part);
1132
+ if (isMetaImage(data2) && scrubImageMeta(data2, ctx, mode, (h) => report(h.rule, h.masked, "metadata"))) next = inflated ? recompress(data2, tail) : data2;
1133
+ }
1134
+ } else if (/^BodyText\/Section\d+$/.test(path) || path === "DocInfo") {
1135
+ const data2 = compressed ? decompressStream(content) : Buffer.from(content);
1136
+ const tail = compressed && hasHancomTail(content, data2);
1137
+ if (scrubRecords(data2, ctx, mode, report)) next = compressed ? recompress(data2, tail) : data2;
1138
+ } else if (path === "PrvText") {
1139
+ const data2 = Buffer.from(content);
1140
+ if (scrubUtf16Runs(data2, ctx, mode, (h) => report(h.rule, h.masked, "preview"))) next = data2;
1141
+ } else if (path.startsWith("")) {
1142
+ const data2 = Buffer.from(content);
1143
+ if (scrubRaw(data2, ctx, mode, "metadata", report, true)) next = data2;
1144
+ } else {
1145
+ const inflated = compressed ? tryInflate(content) : null;
1146
+ const data2 = inflated ?? Buffer.from(content);
1147
+ const tail = inflated ? hasHancomTail(content, inflated) : false;
1148
+ if (scrubRaw(data2, ctx, mode, "other", report, path.startsWith("Scripts/"))) next = inflated ? recompress(data2, tail) : data2;
1149
+ if (path.startsWith("DocHistory/")) warnings.push(`\uBB38\uC11C \uC774\uB825(${path})\uC774 \uB4E4\uC5B4 \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uC774\uC804 \uD310\uC758 \uAE00\uC790\uB294 \uBB38\uC790\uC5F4 \uC870\uAC01 \uB2E8\uC704\uB85C\uB9CC \uAC80\uC0AC\uD588\uC2B5\uB2C8\uB2E4`);
1150
+ }
1151
+ if (next) replaced.set(path, next);
1152
+ }
1153
+ let data = original;
1154
+ if (mode === "mask") {
1155
+ if (previewPath && (hits.length > 0 || ctx.literals.length > 0)) {
1156
+ const img = CFB2.find(cfb, "/PrvImage")?.content;
1157
+ const blank = img ? blankPreviewImage(Buffer.from(img)) : null;
1158
+ if (blank) {
1159
+ replaced.set(previewPath, blank);
1160
+ warnings.push("\uBBF8\uB9AC\uBCF4\uAE30 \uC774\uBBF8\uC9C0(PrvImage)\uB97C \uAC19\uC740 \uD06C\uAE30\uC758 \uD770 \uC774\uBBF8\uC9C0\uB85C \uBC14\uAFE8\uC2B5\uB2C8\uB2E4 \u2014 \uD55C\uCEF4\uC5D0\uC11C \uC800\uC7A5\uD558\uBA74 \uB2E4\uC2DC \uB9CC\uB4E4\uC5B4\uC9D1\uB2C8\uB2E4");
1161
+ } else warnings.push("\uBBF8\uB9AC\uBCF4\uAE30 \uC774\uBBF8\uC9C0(PrvImage) \uD615\uC2DD\uC744 \uBAB0\uB77C \uC9C0\uC6B0\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 \u2014 \uCCAB \uCABD \uB80C\uB354\uC5D0 PII \uAC00 \uBCF4\uC77C \uC218 \uC788\uC2B5\uB2C8\uB2E4");
1162
+ }
1163
+ if (pictures.length > 0) warnings.push(`\uC0BD\uC785 \uADF8\uB9BC ${pictures.length}\uAC1C(${nameList2(pictures)})\uB294 \uD654\uC18C \uC18D \uAE00\uC790(\uC2A4\uCE94 \uBB38\uC11C \uB4F1)\uB97C \uD0D0\uC9C0\uD558\uC9C0 \uBABB\uD569\uB2C8\uB2E4 \u2014 JPEG\xB7PNG \uBA54\uD0C0\uB370\uC774\uD130\uB9CC \uAC80\uC0AC\uD588\uC2B5\uB2C8\uB2E4`);
1164
+ if (embedded.length > 0) warnings.push(`\uC0BD\uC785 \uAC1C\uCCB4 ${embedded.length}\uAC1C(${nameList2(embedded)} \u2014 OLE\xB7EMF)\uB294 \uAC00\uB9AC\uC9C0 \uBABB\uD569\uB2C8\uB2E4 \u2014 \uC548\uC758 \uAE00\uC790\uC5D0 PII \uAC00 \uC788\uC73C\uBA74 \uC7AC\uAC80\uC0AC \uC794\uC874\uC73C\uB85C \uBCF4\uACE0\uB429\uB2C8\uB2E4`);
1165
+ let out = Buffer.from(file);
1166
+ for (const [path, content] of replaced) out = replaceOleStream(out, path, content);
1167
+ try {
1168
+ const wiped = wipeFreeSectors(out);
1169
+ const before = hits.length;
1170
+ const seen = /* @__PURE__ */ new Set();
1171
+ const bytes = scanSlack(out, wiped, ctx, (h) => {
1172
+ const key = `${h.rule}\0${h.masked}`;
1173
+ if (seen.has(key)) return;
1174
+ seen.add(key);
1175
+ hits.push({ rule: h.rule, masked: h.masked, part: "(\uBBF8\uD560\uB2F9 \uC139\uD130)", where: "slack" });
1176
+ });
1177
+ if (bytes > 0) {
1178
+ const found = hits.length - before;
1179
+ warnings.push(`\uBBF8\uD560\uB2F9 \uC601\uC5ED(\uD55C\uCEF4\uC774 \uB0A8\uAE34 \uC61B \uC2A4\uD2B8\uB9BC \uC0AC\uBCF8 \uB4F1) ${Math.ceil(bytes / 1024)}KB\uB97C 0\uC73C\uB85C \uBE44\uC6E0\uC2B5\uB2C8\uB2E4${found > 0 ? ` \u2014 \uADF8 \uC548\uC5D0 \uAC1C\uC778\uC815\uBCF4 ${found}\uAC74\uC774 \uC788\uC5C8\uC2B5\uB2C8\uB2E4` : ""}`);
1180
+ }
1181
+ out = wiped;
1182
+ } catch (err) {
1183
+ warnings.push(`\uBBF8\uD560\uB2F9 \uC139\uD130\uB97C \uBE44\uC6B0\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4 (${err instanceof Error ? err.message : String(err)}) \u2014 \uC61B \uC2A4\uD2B8\uB9BC \uC0AC\uBCF8\uC774 \uB0A8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4`);
1184
+ }
1185
+ if (!out.equals(file)) data = new Uint8Array(out);
1186
+ } else {
1187
+ const add = (part, where) => (h) => {
1188
+ if (!hits.some((x) => x.masked === h.masked && x.rule === h.rule)) hits.push({ rule: h.rule, masked: h.masked, part, where });
1189
+ };
1190
+ scrubUtf16Runs(Buffer.from(file), ctx, "check", add("(OLE \uC6D0\uC2DC \uBC14\uC774\uD2B8)", "other"));
1191
+ try {
1192
+ scanSlack(file, wipeFreeSectors(Buffer.from(file)), ctx, add("(\uBBF8\uD560\uB2F9 \uC139\uD130)", "slack"));
1193
+ } catch {
1194
+ }
1195
+ }
1196
+ return { data, hits, warnings, unscanned: [] };
1197
+ }
1198
+
1199
+ // src/redact-doc.ts
1200
+ async function preciseFormat(buffer) {
1201
+ let format = detectFormat(buffer);
1202
+ if (format === "hwpx") {
1203
+ const z = await detectZipFormat(buffer);
1204
+ if (z !== "unknown") format = z;
1205
+ } else if (format === "hwp") {
1206
+ const o = detectOle2Format(buffer);
1207
+ if (o !== "unknown") format = o;
1208
+ }
1209
+ return format;
1210
+ }
1211
+ async function redactDocument(input, options) {
1212
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
1213
+ const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
1214
+ const rules = options?.rules ?? DEFAULT_REDACT_RULES;
1215
+ const maskChar = options?.maskChar ?? "\u25CF";
1216
+ const format = await preciseFormat(buffer);
1217
+ const parsed = await parse(buffer, options?.filePath ? { filePath: options.filePath } : void 0);
1218
+ if (!parsed.success) throw new Error(`\uD30C\uC2F1 \uC2E4\uD328: ${parsed.error}`);
1219
+ const md = redactMarkdown(parsed.markdown, { rules, maskChar });
1220
+ const ctx = { rules, maskChar, literals: literalsFromMarkdown(parsed.markdown, md.hits) };
1221
+ const lit = maskLiterals(md.text, ctx);
1222
+ const base = { format, markdown: lit.text, markdownHits: [...md.hits, ...lit.hits].sort((a, b) => a.index - b.index) };
1223
+ if (format === "hwpx" || format === "hwp") {
1224
+ const scrub = format === "hwpx" ? scrubHwpx : scrubHwp5;
1225
+ const r = await scrub(bytes, ctx, "mask");
1226
+ const check = await scrub(r.data, ctx, "check");
1227
+ const residual = [...check.hits];
1228
+ const unscanned = [...r.unscanned];
1229
+ if (r.data === bytes) residual.push(...markdownLeftovers(parsed.markdown, ctx));
1230
+ else {
1231
+ const re = await parse(r.data.buffer.slice(r.data.byteOffset, r.data.byteOffset + r.data.byteLength));
1232
+ if (re.success) residual.push(...markdownLeftovers(re.markdown, ctx));
1233
+ else unscanned.push(`\uACB0\uACFC \uD30C\uC77C \uC7AC\uD30C\uC2F1 \uC2E4\uD328 (${re.error}) \u2014 \uACB0\uACFC \uBCF8\uBB38\uC744 \uAC80\uC99D\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4`);
1234
+ }
1235
+ return {
1236
+ ...base,
1237
+ data: options?.dryRun ? void 0 : r.data,
1238
+ changed: r.data !== bytes,
1239
+ fileHits: r.hits,
1240
+ residual,
1241
+ unscanned,
1242
+ warnings: r.warnings
1243
+ };
1244
+ }
1245
+ const warnings = [
1246
+ `${format} \uC6D0\uBCF8 \uD30C\uC77C\uC740 \uC218\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uB9CC \uB9CC\uB4ED\uB2C8\uB2E4. \uC6D0\uBCF8\uC758 \uD14D\uC2A4\uD2B8 \uB808\uC774\uC5B4\xB7\uC774\uBBF8\uC9C0\xB7\uBA54\uD0C0\uB370\uC774\uD130\uC5D0\uB294 PII \uAC00 \uADF8\uB300\uB85C \uB0A8\uC2B5\uB2C8\uB2E4.`
1247
+ ];
1248
+ if (format === "pdf") warnings.push("PDF \uB97C \uACF5\uAC1C\uD558\uB824\uBA74 PDF \uD3B8\uC9D1\uAE30\uC758 \uAC00\uB9BC(redaction) \uAE30\uB2A5\uC73C\uB85C \uD14D\uC2A4\uD2B8\uB97C \uC2E4\uC81C\uB85C \uC9C0\uC6B0\uAC70\uB098, \uB9C8\uC2A4\uD0B9\uB41C \uB9C8\uD06C\uB2E4\uC6B4\uC73C\uB85C \uC0C8 \uBB38\uC11C\uB97C \uB9CC\uB4DC\uC138\uC694.");
1249
+ return { ...base, changed: false, fileHits: [], residual: markdownLeftovers(lit.text, ctx), unscanned: [], warnings };
1250
+ }
1251
+ function maskLiterals(markdown, ctx) {
1252
+ if (ctx.literals.length === 0) return { text: markdown, hits: [] };
1253
+ const hits = [];
1254
+ let offset = 0;
1255
+ const lines = markdown.split("\n").map((line) => {
1256
+ const at = offset;
1257
+ offset += line.length + 1;
1258
+ if (line.includes("data:image/")) return line;
1259
+ const found = findLiterals(normalizeForDetect(line), ctx.literals);
1260
+ if (found.length === 0) return line;
1261
+ let out = "";
1262
+ let cursor = 0;
1263
+ for (const { index, lit } of found) {
1264
+ let masked = "";
1265
+ for (let k = 0; k < lit.norm.length; k++) masked += lit.masked[k] !== lit.value[k] ? ctx.maskChar : line[index + k];
1266
+ out += line.slice(cursor, index) + masked;
1267
+ cursor = index + lit.norm.length;
1268
+ hits.push({ rule: lit.rule, masked, index: at + index, length: lit.norm.length });
1269
+ }
1270
+ return out + line.slice(cursor);
1271
+ });
1272
+ return { text: lines.join("\n"), hits };
1273
+ }
1274
+ function markdownLeftovers(markdown, ctx) {
1275
+ const out = redactMarkdown(markdown, { rules: ctx.rules, maskChar: ctx.maskChar }).hits.map((h) => ({ rule: h.rule, masked: h.masked, part: "(\uACB0\uACFC \uBCF8\uBB38 \uC7AC\uD30C\uC2F1)", where: "body" }));
1276
+ for (const { lit } of findLiterals(normalizeForDetect(markdown), ctx.literals)) {
1277
+ if (!out.some((h) => h.masked === lit.masked)) out.push({ rule: lit.rule, masked: lit.masked, part: "(\uACB0\uACFC \uBCF8\uBB38 \uC7AC\uD30C\uC2F1)", where: "body" });
1278
+ }
1279
+ return out;
1280
+ }
1281
+ export {
1282
+ redactDocument
1283
+ };
1284
+ //# sourceMappingURL=redact-doc-QBHNJ2BU.js.map