@ryuzaki13/react-foundation-lib 2.18.1 → 2.19.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.
@@ -0,0 +1,8 @@
1
+ import { type FileSignatureCheckOptions, type FileSignatureCheckResult } from "./fileSignatureTypes";
2
+ /**
3
+ * Частичная pure-проверка fixed headers: нет FileReader, Node API, декодирования,
4
+ * распаковки или чтения всего файла. Любой match относится только к header.
5
+ * Общий ZIP/OLE marker не подтверждает Office/ODF/XFL; allowlist принадлежит caller.
6
+ */
7
+ export declare function checkFileSignature({ extension, bytes }: FileSignatureCheckOptions): FileSignatureCheckResult;
8
+ //# sourceMappingURL=checkFileSignature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkFileSignature.d.ts","sourceRoot":"","sources":["../../src/file/checkFileSignature.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAmErG;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,yBAAyB,GAAG,wBAAwB,CAuB5G"}
@@ -0,0 +1,20 @@
1
+ /** Приватный словарь результатов; значения сохраняют публичный string union. */
2
+ export declare const FILE_SIGNATURE_RESULT: {
3
+ readonly MATCH: "match";
4
+ readonly MISMATCH: "mismatch";
5
+ readonly UNSUPPORTED: "unsupported";
6
+ };
7
+ /** Только dispatch поддержанных проверок; это не предметный allowlist caller. */
8
+ export declare const FILE_SIGNATURE_EXTENSION: {
9
+ readonly PNG: "png";
10
+ readonly JPG: "jpg";
11
+ readonly JPEG: "jpeg";
12
+ readonly BMP: "bmp";
13
+ readonly PSD: "psd";
14
+ readonly RTF: "rtf";
15
+ readonly RAR: "rar";
16
+ readonly ZIP: "zip";
17
+ readonly SEVEN_ZIP: "7z";
18
+ readonly PDF: "pdf";
19
+ };
20
+ //# sourceMappingURL=fileSignatureConstants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSignatureConstants.d.ts","sourceRoot":"","sources":["../../src/file/fileSignatureConstants.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,eAAO,MAAM,qBAAqB;;;;CAIxB,CAAC;AAEX,iFAAiF;AACjF,eAAO,MAAM,wBAAwB;;;;;;;;;;;CAW3B,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type FILE_SIGNATURE_EXTENSION, type FILE_SIGNATURE_RESULT } from "./fileSignatureConstants";
2
+ /** Bytes принадлежат caller; расширение передаётся без точки и имени файла. */
3
+ export type FileSignatureCheckOptions = Readonly<{
4
+ extension: string;
5
+ bytes: Uint8Array;
6
+ }>;
7
+ /**
8
+ * match подтверждает только поддержанный header, не целостность или безопасность
9
+ * файла. unsupported означает отсутствие надёжного решения этой проверкой;
10
+ * допустимость такого файла определяет вызывающая policy, а не foundation.
11
+ */
12
+ export type FileSignatureCheckResult = (typeof FILE_SIGNATURE_RESULT)[keyof typeof FILE_SIGNATURE_RESULT];
13
+ /** Внутренний archive dispatch; public index не публикует этот тип. */
14
+ export type ArchiveFileSignatureExtension = typeof FILE_SIGNATURE_EXTENSION.RAR | typeof FILE_SIGNATURE_EXTENSION.ZIP | typeof FILE_SIGNATURE_EXTENSION.SEVEN_ZIP;
15
+ //# sourceMappingURL=fileSignatureTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileSignatureTypes.d.ts","sourceRoot":"","sources":["../../src/file/fileSignatureTypes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAErG,+EAA+E;AAC/E,MAAM,MAAM,yBAAyB,GAAG,QAAQ,CAAC;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,CAAC;CAClB,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,OAAO,qBAAqB,CAAC,CAAC;AAE1G,uEAAuE;AACvE,MAAM,MAAM,6BAA6B,GACxC,OAAO,wBAAwB,CAAC,GAAG,GAAG,OAAO,wBAAwB,CAAC,GAAG,GAAG,OAAO,wBAAwB,CAAC,SAAS,CAAC"}
@@ -1,3 +1,5 @@
1
+ export { checkFileSignature } from "./checkFileSignature";
2
+ export { type FileSignatureCheckOptions, type FileSignatureCheckResult } from "./fileSignatureTypes";
1
3
  export * from "./contentDisposition";
2
4
  export * from "./image";
3
5
  export * from "./mimeType";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,KAAK,yBAAyB,EAAE,KAAK,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AACrG,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
@@ -1,50 +1,219 @@
1
+ //#region src/file/fileSignatureConstants.ts
2
+ var e = {
3
+ MATCH: "match",
4
+ MISMATCH: "mismatch",
5
+ UNSUPPORTED: "unsupported"
6
+ }, t = {
7
+ PNG: "png",
8
+ JPG: "jpg",
9
+ JPEG: "jpeg",
10
+ BMP: "bmp",
11
+ PSD: "psd",
12
+ RTF: "rtf",
13
+ RAR: "rar",
14
+ ZIP: "zip",
15
+ SEVEN_ZIP: "7z",
16
+ PDF: "pdf"
17
+ };
18
+ //#endregion
19
+ //#region src/file/signature/hasSignatureBytes.ts
20
+ function n(e, t, n = 0) {
21
+ return n + t.length > e.length ? !1 : t.every((t, r) => e[n + r] === t);
22
+ }
23
+ function r(e, t) {
24
+ return e.length < t.length && e.every((e, n) => e === t[n]);
25
+ }
26
+ //#endregion
27
+ //#region src/file/signature/checkArchiveSignature.ts
28
+ var i = [
29
+ 82,
30
+ 97,
31
+ 114,
32
+ 33
33
+ ], a = [26, 7], o = 0, s = 1, c = 0, l = [80, 75], u = [3, 4], d = [5, 6], f = 30, ee = 22, te = [55, 122], p = [
34
+ 55,
35
+ 122,
36
+ 188,
37
+ 175,
38
+ 39,
39
+ 28
40
+ ], ne = 2, re = 0, m = 32;
41
+ function ie(t) {
42
+ if (r(t, i)) return e.MISMATCH;
43
+ if (!n(t, i)) return e.UNSUPPORTED;
44
+ let l = i.length + a.length;
45
+ return t.length <= l || !n(t, a, i.length) ? e.MISMATCH : t[l] === o ? e.MATCH : t[l] === s ? t[l + 1] === c ? e.MATCH : e.MISMATCH : e.UNSUPPORTED;
46
+ }
47
+ function ae(t) {
48
+ if (r(t, l)) return e.MISMATCH;
49
+ if (!n(t, l)) return e.UNSUPPORTED;
50
+ let i = l.length;
51
+ return t.length <= i ? e.MISMATCH : t[i] === u[0] ? t.length >= f && n(t, u, i) ? e.MATCH : e.MISMATCH : t[i] === d[0] ? t.length >= ee && n(t, d, i) ? e.MATCH : e.MISMATCH : e.UNSUPPORTED;
52
+ }
53
+ function h(t) {
54
+ return r(t, p) ? e.MISMATCH : n(t, p) ? t.length < p.length + ne ? e.MISMATCH : t[p.length] === re ? t.length >= m ? e.MATCH : e.MISMATCH : e.UNSUPPORTED : n(t, te) ? e.MISMATCH : e.UNSUPPORTED;
55
+ }
56
+ function oe(e, n) {
57
+ return e === t.RAR ? ie(n) : e === t.ZIP ? ae(n) : h(n);
58
+ }
59
+ //#endregion
60
+ //#region src/file/signature/checkPdfSignature.ts
61
+ var g = [
62
+ 37,
63
+ 80,
64
+ 68,
65
+ 70
66
+ ], _ = 1024, v = 8, y = 4, b = 45, x = 5, S = 6, se = 46, ce = 7, C = 48, w = 57, le = 49, ue = 55, de = 50, T = 48;
67
+ function E(t) {
68
+ if (r(t, g)) return e.MISMATCH;
69
+ let i = Math.min(t.length, _), a = !1, o = !1;
70
+ for (let r = 0; r + g.length <= i; r += 1) {
71
+ if (!n(t, g, r)) continue;
72
+ if (r + v > i) {
73
+ r + v > t.length ? a = !0 : o = !0;
74
+ continue;
75
+ }
76
+ let s = t[r + x], c = t[r + ce];
77
+ if (t[r + y] !== b || t[r + S] !== se || s < C || s > w || c < C || c > w) {
78
+ a = !0;
79
+ continue;
80
+ }
81
+ if (s === le && c <= ue || s === de && c === T) return e.MATCH;
82
+ o = !0;
83
+ }
84
+ return o ? e.UNSUPPORTED : a ? e.MISMATCH : e.UNSUPPORTED;
85
+ }
86
+ //#endregion
87
+ //#region src/file/checkFileSignature.ts
88
+ var D = [
89
+ 137,
90
+ 80,
91
+ 78,
92
+ 71,
93
+ 13,
94
+ 10,
95
+ 26,
96
+ 10
97
+ ], O = 255, k = 216, A = 32, j = [66, 77], M = 14, N = 6, P = [
98
+ 0,
99
+ 0,
100
+ 0,
101
+ 0
102
+ ], F = [
103
+ 56,
104
+ 66,
105
+ 80,
106
+ 83
107
+ ], I = [0, 1], L = [
108
+ 0,
109
+ 0,
110
+ 0,
111
+ 0,
112
+ 0,
113
+ 0
114
+ ], R = [
115
+ 123,
116
+ 92,
117
+ 114,
118
+ 116,
119
+ 102
120
+ ], z = [
121
+ 123,
122
+ 92,
123
+ 114,
124
+ 116,
125
+ 102,
126
+ 49
127
+ ], B = {
128
+ digit: [48, 57],
129
+ uppercase: [65, 90],
130
+ lowercase: [97, 122]
131
+ };
132
+ function V(t) {
133
+ if (t[0] !== O) return e.MISMATCH;
134
+ let n = 1;
135
+ for (; n < Math.min(t.length, A) && t[n] === O;) n += 1;
136
+ return n === A && t.length > n ? e.UNSUPPORTED : t[n] === k ? e.MATCH : e.MISMATCH;
137
+ }
138
+ function H(t) {
139
+ return r(t, j) ? e.MISMATCH : n(t, j) ? t.length < M ? e.MISMATCH : n(t, P, N) ? e.MATCH : e.MISMATCH : e.UNSUPPORTED;
140
+ }
141
+ function fe(t) {
142
+ if (!n(t, F)) return e.MISMATCH;
143
+ let r = F.length + I.length;
144
+ return t.length < r ? e.MISMATCH : n(t, I, F.length) ? n(t, L, r) ? e.MATCH : e.MISMATCH : e.UNSUPPORTED;
145
+ }
146
+ function pe(t) {
147
+ if (r(t, z)) return e.MISMATCH;
148
+ if (!n(t, R)) return e.UNSUPPORTED;
149
+ if (!n(t, z) || t.length <= z.length) return e.MISMATCH;
150
+ let i = t[z.length];
151
+ return i >= B.digit[0] && i <= B.digit[1] || i >= B.uppercase[0] && i <= B.uppercase[1] || i >= B.lowercase[0] && i <= B.lowercase[1] ? e.MISMATCH : e.MATCH;
152
+ }
153
+ function me({ extension: r, bytes: i }) {
154
+ let a = r.trim().toLowerCase();
155
+ switch (a) {
156
+ case t.PNG: return n(i, D) ? e.MATCH : e.MISMATCH;
157
+ case t.JPG:
158
+ case t.JPEG: return V(i);
159
+ case t.BMP: return H(i);
160
+ case t.PSD: return fe(i);
161
+ case t.RTF: return pe(i);
162
+ case t.RAR:
163
+ case t.ZIP:
164
+ case t.SEVEN_ZIP: return oe(a, i);
165
+ case t.PDF: return E(i);
166
+ default: return e.UNSUPPORTED;
167
+ }
168
+ }
169
+ //#endregion
1
170
  //#region src/file/contentDisposition.ts
2
- function e(e, t = "file") {
171
+ function U(e, t = "file") {
3
172
  let n = e.replace(/[\r\n]/g, " ").replace(/"/g, "'").trim();
4
173
  return n.length > 0 ? n : t;
5
174
  }
6
- function t(e) {
175
+ function W(e) {
7
176
  return encodeURIComponent(e).replace(/['()]/g, (e) => `%${e.charCodeAt(0).toString(16).toUpperCase()}`).replace(/\*/g, "%2A");
8
177
  }
9
- function n(e, t = "file.bin") {
178
+ function G(e, t = "file.bin") {
10
179
  let n = e.replace(/[\r\n]/g, " ").replace(/["\\]/g, "_").replace(/[^\x20-\x7E]/g, "_").replace(/\s+/g, " ").trim();
11
180
  return n.length > 0 ? n : t;
12
181
  }
13
- function r({ disposition: r, filename: i, asciiFallback: a = "file.bin" }) {
14
- let o = e(i, a);
15
- return `${r}; filename="${n(o, a)}"; filename*=UTF-8''${t(o)}`;
182
+ function he({ disposition: e, filename: t, asciiFallback: n = "file.bin" }) {
183
+ let r = U(t, n);
184
+ return `${e}; filename="${G(r, n)}"; filename*=UTF-8''${W(r)}`;
16
185
  }
17
186
  //#endregion
18
187
  //#region src/file/read-file/types.ts
19
- var i = class extends Error {
188
+ var K = class extends Error {
20
189
  code;
21
190
  cause;
22
191
  constructor(e, t, n) {
23
192
  super(t ?? e), this.name = "ReadFileError", this.code = e, this.cause = n;
24
193
  }
25
- }, a = "[a-z0-9!#$&^_.+-]+", o = RegExp(`^${a}\\/${a}$`, "i"), s = RegExp(`^${a}\\/(?:${a}|\\*)$`, "i"), c = /^\.[^\s,]+$/;
26
- function l(e) {
194
+ }, q = "[a-z0-9!#$&^_.+-]+", ge = RegExp(`^${q}\\/${q}$`, "i"), _e = RegExp(`^${q}\\/(?:${q}|\\*)$`, "i"), ve = /^\.[^\s,]+$/;
195
+ function ye(e) {
27
196
  if (e) {
28
- for (let [t, n] of e.entries()) if (!o.test(n)) throw Error(`DropZone: некорректный allowedMime[${t}]="${n}". Ожидается полный MIME-тип вида "application/pdf" без wildcard и параметров.`);
197
+ for (let [t, n] of e.entries()) if (!ge.test(n)) throw Error(`DropZone: некорректный allowedMime[${t}]="${n}". Ожидается полный MIME-тип вида "application/pdf" без wildcard и параметров.`);
29
198
  }
30
199
  }
31
- function u(e) {
200
+ function be(e) {
32
201
  if (e) {
33
- for (let [t, n] of e.entries()) if (!(c.test(n) || s.test(n))) throw Error(`DropZone: некорректный accept[${t}]="${n}". Ожидается расширение вида ".pdf" или MIME-тип вида "application/pdf" / "image/*".`);
202
+ for (let [t, n] of e.entries()) if (!(ve.test(n) || _e.test(n))) throw Error(`DropZone: некорректный accept[${t}]="${n}". Ожидается расширение вида ".pdf" или MIME-тип вида "application/pdf" / "image/*".`);
34
203
  }
35
204
  }
36
- function d(e) {
37
- if (e?.aborted) throw new i("READ_ABORTED", "Прервано по сигналу");
205
+ function J(e) {
206
+ if (e?.aborted) throw new K("READ_ABORTED", "Прервано по сигналу");
38
207
  }
39
208
  //#endregion
40
209
  //#region src/file/read-file/readFile.ts
41
- function f(e) {
210
+ function Y(e) {
42
211
  return e?.trim().toLowerCase() ?? "";
43
212
  }
44
- function p(e) {
45
- return f(e.split(".").pop());
213
+ function xe(e) {
214
+ return Y(e.split(".").pop());
46
215
  }
47
- function m(e) {
216
+ function X(e) {
48
217
  return {
49
218
  mime: e.type,
50
219
  size: e.size,
@@ -52,7 +221,7 @@ function m(e) {
52
221
  lastModified: e.lastModified
53
222
  };
54
223
  }
55
- function h(e, t) {
224
+ function Z(e, t) {
56
225
  if (!e) return;
57
226
  let n = () => {
58
227
  try {
@@ -61,81 +230,81 @@ function h(e, t) {
61
230
  };
62
231
  return e.addEventListener("abort", n, { once: !0 }), () => e.removeEventListener("abort", n);
63
232
  }
64
- function g(e, t) {
233
+ function Se(e, t) {
65
234
  if (t.allowedMime && t.allowedMime.length > 0) {
66
- let n = new Set(t.allowedMime.map((e) => f(e))), r = f(e.type);
67
- if (!n.has(r)) throw new i("MIME_NOT_ALLOWED", `Неподдерживаемый MIME тип: ${e.type}`);
235
+ let n = new Set(t.allowedMime.map((e) => Y(e))), r = Y(e.type);
236
+ if (!n.has(r)) throw new K("MIME_NOT_ALLOWED", `Неподдерживаемый MIME тип: ${e.type}`);
68
237
  } else if (t.accept && t.accept.length > 0) {
69
- let n = p(e.name) || e.type.split("/").pop();
70
- if (!n) throw new i("MIME_NOT_ALLOWED", "Неопределенный MIME тип");
71
- if (!t.accept.map((e) => f(e)).some((e) => e.includes(n))) throw new i("MIME_NOT_ALLOWED", `Неподдерживаемый MIME тип: ${n}`);
238
+ let n = xe(e.name) || e.type.split("/").pop();
239
+ if (!n) throw new K("MIME_NOT_ALLOWED", "Неопределенный MIME тип");
240
+ if (!t.accept.map((e) => Y(e)).some((e) => e.includes(n))) throw new K("MIME_NOT_ALLOWED", `Неподдерживаемый MIME тип: ${n}`);
72
241
  }
73
- if (typeof t.maxBytes == "number" && e.size > t.maxBytes) throw new i("FILE_TOO_LARGE", `Файл слишком большой: ${e.size} > ${t.maxBytes}`);
242
+ if (typeof t.maxBytes == "number" && e.size > t.maxBytes) throw new K("FILE_TOO_LARGE", `Файл слишком большой: ${e.size} > ${t.maxBytes}`);
74
243
  }
75
- function _(e, t) {
244
+ function Ce(e, t) {
76
245
  return new Promise((n, r) => {
77
- let a = new FileReader(), o = h(t.signal, a);
78
- a.onerror = () => {
79
- o?.(), r(new i("READ_FAILED", "Не удалось прочитать файл как DataURL", a.error));
80
- }, a.onabort = () => {
81
- o?.(), r(new i("READ_ABORTED", "Чтение файла прервано"));
82
- }, a.onload = () => {
83
- o?.();
84
- let e = a.result;
246
+ let i = new FileReader(), a = Z(t.signal, i);
247
+ i.onerror = () => {
248
+ a?.(), r(new K("READ_FAILED", "Не удалось прочитать файл как DataURL", i.error));
249
+ }, i.onabort = () => {
250
+ a?.(), r(new K("READ_ABORTED", "Чтение файла прервано"));
251
+ }, i.onload = () => {
252
+ a?.();
253
+ let e = i.result;
85
254
  if (typeof e != "string") {
86
- r(new i("READ_FAILED", "Неожиданный тип результата FileReader"));
255
+ r(new K("READ_FAILED", "Неожиданный тип результата FileReader"));
87
256
  return;
88
257
  }
89
258
  n(e);
90
259
  };
91
260
  try {
92
- a.readAsDataURL(e);
261
+ i.readAsDataURL(e);
93
262
  } catch (e) {
94
- o?.(), r(new i("READ_FAILED", "При чтении DataURL функция FileReader выдала ошибку", e));
263
+ a?.(), r(new K("READ_FAILED", "При чтении DataURL функция FileReader выдала ошибку", e));
95
264
  }
96
265
  });
97
266
  }
98
- function v(e, t) {
267
+ function we(e, t) {
99
268
  return new Promise((n, r) => {
100
- let a = new FileReader(), o = h(t.signal, a);
101
- a.onerror = () => {
102
- o?.(), r(new i("READ_FAILED", "Не удалось прочитать файл как ArrayBuffer.", a.error));
103
- }, a.onabort = () => {
104
- o?.(), r(new i("READ_ABORTED", "Чтение файла прервано"));
105
- }, a.onload = () => {
106
- o?.();
107
- let e = a.result;
269
+ let i = new FileReader(), a = Z(t.signal, i);
270
+ i.onerror = () => {
271
+ a?.(), r(new K("READ_FAILED", "Не удалось прочитать файл как ArrayBuffer.", i.error));
272
+ }, i.onabort = () => {
273
+ a?.(), r(new K("READ_ABORTED", "Чтение файла прервано"));
274
+ }, i.onload = () => {
275
+ a?.();
276
+ let e = i.result;
108
277
  if (!(e instanceof ArrayBuffer)) {
109
- r(new i("READ_FAILED", "Неожиданный тип результата FileReader"));
278
+ r(new K("READ_FAILED", "Неожиданный тип результата FileReader"));
110
279
  return;
111
280
  }
112
281
  n(e);
113
282
  };
114
283
  try {
115
- a.readAsArrayBuffer(e);
284
+ i.readAsArrayBuffer(e);
116
285
  } catch (e) {
117
- o?.(), r(new i("READ_FAILED", "При чтении ArrayBuffer возникла ошибка FileReader", e));
286
+ a?.(), r(new K("READ_FAILED", "При чтении ArrayBuffer возникла ошибка FileReader", e));
118
287
  }
119
288
  });
120
289
  }
121
- async function y(e, t = {}) {
122
- d(t.signal), g(e, t);
123
- let n = t.mode ?? "data-url", r = m(e);
290
+ async function Q(e, t = {}) {
291
+ J(t.signal), Se(e, t);
292
+ let n = t.mode ?? "data-url", r = X(e);
124
293
  return n === "array-buffer" ? {
125
294
  mode: "array-buffer",
126
295
  meta: r,
127
- buffer: await v(e, t),
296
+ buffer: await we(e, t),
128
297
  file: e
129
298
  } : {
130
299
  mode: "data-url",
131
300
  meta: r,
132
- dataUrl: await _(e, t),
301
+ dataUrl: await Ce(e, t),
133
302
  file: e
134
303
  };
135
304
  }
136
305
  //#endregion
137
306
  //#region src/file/image/types.ts
138
- var b = class extends Error {
307
+ var $ = class extends Error {
139
308
  code;
140
309
  cause;
141
310
  constructor(e, t, n) {
@@ -144,11 +313,11 @@ var b = class extends Error {
144
313
  };
145
314
  //#endregion
146
315
  //#region src/file/image/readImageFile.ts
147
- function x(e) {
316
+ function Te(e) {
148
317
  return e.startsWith("image/");
149
318
  }
150
- async function S(e, t) {
151
- d(t);
319
+ async function Ee(e, t) {
320
+ J(t);
152
321
  let n = new Image();
153
322
  return n.decoding = "async", n.src = e, await new Promise((e, t) => {
154
323
  let r = () => {
@@ -159,23 +328,23 @@ async function S(e, t) {
159
328
  }, n.onerror = (e) => {
160
329
  r(), t(e);
161
330
  };
162
- }), d(t), {
331
+ }), J(t), {
163
332
  width: n.naturalWidth,
164
333
  height: n.naturalHeight
165
334
  };
166
335
  }
167
- async function C(e, t = {}) {
168
- if (!x(e.type)) throw new b("NOT_AN_IMAGE", `Файл не является изображением. mime=${e.type || "unknown"}`);
336
+ async function De(e, t = {}) {
337
+ if (!Te(e.type)) throw new $("NOT_AN_IMAGE", `Файл не является изображением. mime=${e.type || "unknown"}`);
169
338
  let n = {
170
339
  allowedMime: t.allowedMime,
171
340
  maxBytes: t.maxBytes,
172
341
  signal: t.signal
173
342
  };
174
- if (t.mode === "array-buffer") return y(e, {
343
+ if (t.mode === "array-buffer") return Q(e, {
175
344
  ...n,
176
345
  mode: "array-buffer"
177
346
  });
178
- let r = await y(e, n), i = await S(r.dataUrl, t.signal);
347
+ let r = await Q(e, n), i = await Ee(r.dataUrl, t.signal);
179
348
  return {
180
349
  ...r,
181
350
  dimensions: i
@@ -183,7 +352,7 @@ async function C(e, t = {}) {
183
352
  }
184
353
  //#endregion
185
354
  //#region src/file/mimeType.ts
186
- var w = {
355
+ var Oe = {
187
356
  pdf: "application/pdf",
188
357
  doc: "application/msword",
189
358
  docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -202,10 +371,10 @@ var w = {
202
371
  gif: "image/gif",
203
372
  webp: "image/webp"
204
373
  };
205
- function T(e) {
206
- return w[e.trim().toLowerCase()] ?? "application/octet-stream";
374
+ function ke(e) {
375
+ return Oe[e.trim().toLowerCase()] ?? "application/octet-stream";
207
376
  }
208
377
  //#endregion
209
- export { i as ReadFileError, b as ReadImageError, d as assertNotAborted, u as assertValidAccept, l as assertValidAllowedMime, h as attachAbort, m as buildMeta, n as createAsciiFilenameFallback, r as createContentDispositionHeader, t as encodeFilenameRFC5987, y as readFile, C as readImageFile, T as resolveMimeTypeByExtension, e as sanitizeFileName };
378
+ export { K as ReadFileError, $ as ReadImageError, J as assertNotAborted, be as assertValidAccept, ye as assertValidAllowedMime, Z as attachAbort, X as buildMeta, me as checkFileSignature, G as createAsciiFilenameFallback, he as createContentDispositionHeader, W as encodeFilenameRFC5987, Q as readFile, De as readImageFile, ke as resolveMimeTypeByExtension, U as sanitizeFileName };
210
379
 
211
380
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/file/contentDisposition.ts","../../src/file/read-file/types.ts","../../src/file/read-file/assert.ts","../../src/file/read-file/readFile.ts","../../src/file/image/types.ts","../../src/file/image/readImageFile.ts","../../src/file/mimeType.ts"],"sourcesContent":["export type ContentDispositionKind = \"inline\" | \"attachment\";\n\nexport type ContentDispositionOptions = {\n\treadonly disposition: ContentDispositionKind;\n\treadonly filename: string;\n\treadonly asciiFallback?: string;\n};\n\nexport function sanitizeFileName(value: string, fallback = \"file\"): string {\n\tconst normalized = value\n\t\t.replace(/[\\r\\n]/g, \" \")\n\t\t.replace(/\"/g, \"'\")\n\t\t.trim();\n\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nexport function encodeFilenameRFC5987(value: string): string {\n\treturn encodeURIComponent(value)\n\t\t.replace(/['()]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)\n\t\t.replace(/\\*/g, \"%2A\");\n}\n\nexport function createAsciiFilenameFallback(value: string, fallback = \"file.bin\"): string {\n\tconst asciiValue = value\n\t\t.replace(/[\\r\\n]/g, \" \")\n\t\t.replace(/[\"\\\\]/g, \"_\")\n\t\t.replace(/[^\\x20-\\x7E]/g, \"_\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\n\treturn asciiValue.length > 0 ? asciiValue : fallback;\n}\n\nexport function createContentDispositionHeader({ disposition, filename, asciiFallback = \"file.bin\" }: ContentDispositionOptions): string {\n\tconst sanitizedFilename = sanitizeFileName(filename, asciiFallback);\n\tconst asciiFilename = createAsciiFilenameFallback(sanitizedFilename, asciiFallback);\n\tconst encodedFilename = encodeFilenameRFC5987(sanitizedFilename);\n\n\treturn `${disposition}; filename=\"${asciiFilename}\"; filename*=UTF-8''${encodedFilename}`;\n}\n","export type ReadMode = \"data-url\" | \"array-buffer\";\n\nexport type ReadFileErrorCode = \"NO_FILE\" | \"MIME_NOT_ALLOWED\" | \"FILE_TOO_LARGE\" | \"READ_ABORTED\" | \"READ_FAILED\";\n\nexport class ReadFileError extends Error {\n\tpublic readonly code: ReadFileErrorCode;\n\tpublic readonly cause?: unknown;\n\n\tconstructor(code: ReadFileErrorCode, message?: string, cause?: unknown) {\n\t\tsuper(message ?? code);\n\t\tthis.name = \"ReadFileError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\nexport type FileMeta = {\n\tmime: string;\n\tsize: number;\n\tname: string;\n\tlastModified: number;\n};\n\nexport type ReadFileOptionsBase = {\n\taccept?: readonly string[];\n\t/** Ограничение по MIME (если не указано — любые файлы) */\n\tallowedMime?: readonly string[];\n\t/** Ограничение по размеру файла (байты) */\n\tmaxBytes?: number;\n\t/** AbortController для отмены */\n\tsignal?: AbortSignal;\n};\n\nexport type ReadFileAsDataUrlOptions = ReadFileOptionsBase & {\n\tmode?: \"data-url\";\n};\n\nexport type ReadFileAsArrayBufferOptions = ReadFileOptionsBase & {\n\tmode: \"array-buffer\";\n};\n\nexport type ReadFileOptions = ReadFileAsDataUrlOptions | ReadFileAsArrayBufferOptions;\n\nexport type ReadFileAsDataUrlResult = {\n\tmode: \"data-url\";\n\tmeta: FileMeta;\n\tdataUrl: string;\n\tfile: File;\n};\n\nexport type ReadFileAsArrayBufferResult = {\n\tmode: \"array-buffer\";\n\tmeta: FileMeta;\n\tbuffer: ArrayBuffer;\n\tfile: File;\n};\n\nexport type ReadFileResult = ReadFileAsDataUrlResult | ReadFileAsArrayBufferResult;\n","import { ReadFileError } from \"./types\";\n\nconst MIME_PART_PATTERN = \"[a-z0-9!#$&^_.+-]+\";\nconst STRICT_MIME_PATTERN = new RegExp(`^${MIME_PART_PATTERN}\\\\/${MIME_PART_PATTERN}$`, \"i\");\nconst ACCEPT_MIME_PATTERN = new RegExp(`^${MIME_PART_PATTERN}\\\\/(?:${MIME_PART_PATTERN}|\\\\*)$`, \"i\");\nconst ACCEPT_EXTENSION_PATTERN = /^\\.[^\\s,]+$/;\n\nexport function assertValidAllowedMime(allowedMime?: readonly string[]): void {\n\tif (!allowedMime) return;\n\n\tfor (const [index, mime] of allowedMime.entries()) {\n\t\tif (!STRICT_MIME_PATTERN.test(mime)) {\n\t\t\tthrow new Error(\n\t\t\t\t`DropZone: некорректный allowedMime[${index}]=\"${mime}\". Ожидается полный MIME-тип вида \"application/pdf\" без wildcard и параметров.`\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function assertValidAccept(accept?: readonly string[]): void {\n\tif (!accept) return;\n\n\tfor (const [index, specifier] of accept.entries()) {\n\t\tconst isValidSpecifier = ACCEPT_EXTENSION_PATTERN.test(specifier) || ACCEPT_MIME_PATTERN.test(specifier);\n\n\t\tif (!isValidSpecifier) {\n\t\t\tthrow new Error(\n\t\t\t\t`DropZone: некорректный accept[${index}]=\"${specifier}\". Ожидается расширение вида \".pdf\" или MIME-тип вида \"application/pdf\" / \"image/*\".`\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function assertNotAborted(signal?: AbortSignal): void {\n\tif (signal?.aborted) {\n\t\tthrow new ReadFileError(\"READ_ABORTED\", \"Прервано по сигналу\");\n\t}\n}\n","import { assertNotAborted } from \"./assert\";\nimport { FileMeta, ReadFileError, ReadFileOptions, ReadFileResult, ReadMode } from \"./types\";\n\nfunction normalizeMime(mime?: string): string {\n\treturn mime?.trim().toLowerCase() ?? \"\";\n}\n\nfunction getFileExtension(fileName: string): string {\n\tconst extension = fileName.split(\".\").pop();\n\treturn normalizeMime(extension);\n}\n\nexport function buildMeta(file: File): FileMeta {\n\treturn {\n\t\tmime: file.type,\n\t\tsize: file.size,\n\t\tname: file.name,\n\t\tlastModified: file.lastModified\n\t};\n}\n\nexport function attachAbort(signal: AbortSignal | undefined, reader: FileReader): (() => void) | undefined {\n\tif (!signal) return undefined;\n\n\tconst onAbort = () => {\n\t\ttry {\n\t\t\treader.abort();\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t};\n\n\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\treturn () => signal.removeEventListener(\"abort\", onAbort);\n}\n\nfunction validateFile(file: File, opts: ReadFileOptions): void {\n\tif (opts.allowedMime && opts.allowedMime.length > 0) {\n\t\tconst allowed = new Set(opts.allowedMime.map((mime) => normalizeMime(mime)));\n\t\tconst fileMime = normalizeMime(file.type);\n\t\tif (!allowed.has(fileMime)) {\n\t\t\tthrow new ReadFileError(\"MIME_NOT_ALLOWED\", `Неподдерживаемый MIME тип: ${file.type}`);\n\t\t}\n\t} else if (opts.accept && opts.accept.length > 0) {\n\t\t// Берём расширение файла, если его нет, пытаемся вытащить из MIME\n\t\tconst fileMime = getFileExtension(file.name) || file.type.split(\"/\").pop();\n\t\tif (!fileMime) {\n\t\t\t// NOTE: Если неизвестный mime и нет расширения? Пока запретить такой сценарий\n\t\t\tthrow new ReadFileError(\"MIME_NOT_ALLOWED\", `Неопределенный MIME тип`);\n\t\t}\n\n\t\tconst allowed = opts.accept.map((mime) => normalizeMime(mime));\n\t\tconst isAllowed = allowed.some((mime) => mime.includes(fileMime));\n\n\t\tif (!isAllowed) {\n\t\t\tthrow new ReadFileError(\"MIME_NOT_ALLOWED\", `Неподдерживаемый MIME тип: ${fileMime}`);\n\t\t}\n\t}\n\n\tif (typeof opts.maxBytes === \"number\" && file.size > opts.maxBytes) {\n\t\tthrow new ReadFileError(\"FILE_TOO_LARGE\", `Файл слишком большой: ${file.size} > ${opts.maxBytes}`);\n\t}\n}\n\nfunction readAsDataUrl(file: File, opts: ReadFileOptions): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader();\n\n\t\tconst detachAbort = attachAbort(opts.signal, reader);\n\n\t\treader.onerror = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Не удалось прочитать файл как DataURL\", reader.error));\n\t\t};\n\n\t\treader.onabort = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_ABORTED\", \"Чтение файла прервано\"));\n\t\t};\n\n\t\treader.onload = () => {\n\t\t\tdetachAbort?.();\n\t\t\tconst result = reader.result;\n\t\t\tif (typeof result !== \"string\") {\n\t\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Неожиданный тип результата FileReader\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(result);\n\t\t};\n\n\t\ttry {\n\t\t\treader.readAsDataURL(file);\n\t\t} catch (cause) {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"При чтении DataURL функция FileReader выдала ошибку\", cause));\n\t\t}\n\t});\n}\n\nfunction readAsArrayBuffer(file: File, opts: ReadFileOptions): Promise<ArrayBuffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader();\n\n\t\tconst detachAbort = attachAbort(opts.signal, reader);\n\n\t\treader.onerror = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Не удалось прочитать файл как ArrayBuffer.\", reader.error));\n\t\t};\n\n\t\treader.onabort = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_ABORTED\", \"Чтение файла прервано\"));\n\t\t};\n\n\t\treader.onload = () => {\n\t\t\tdetachAbort?.();\n\t\t\tconst result = reader.result;\n\t\t\tif (!(result instanceof ArrayBuffer)) {\n\t\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Неожиданный тип результата FileReader\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(result);\n\t\t};\n\n\t\ttry {\n\t\t\treader.readAsArrayBuffer(file);\n\t\t} catch (cause) {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"При чтении ArrayBuffer возникла ошибка FileReader\", cause));\n\t\t}\n\t});\n}\n\n// Перегрузки для строгой типизации результата\n\nexport async function readFile(\n\tfile: File,\n\topts?: Omit<ReadFileOptions, \"mode\"> & { mode?: \"data-url\" }\n): Promise<Extract<ReadFileResult, { mode: \"data-url\" }>>;\nexport async function readFile(\n\tfile: File,\n\topts: Omit<ReadFileOptions, \"mode\"> & { mode: \"array-buffer\" }\n): Promise<Extract<ReadFileResult, { mode: \"array-buffer\" }>>;\n\nexport async function readFile(file: File, opts: ReadFileOptions = {}): Promise<ReadFileResult> {\n\tassertNotAborted(opts.signal);\n\tvalidateFile(file, opts);\n\n\tconst mode: ReadMode = opts.mode ?? \"data-url\";\n\tconst meta = buildMeta(file);\n\n\tif (mode === \"array-buffer\") {\n\t\tconst buffer = await readAsArrayBuffer(file, opts);\n\t\treturn { mode: \"array-buffer\", meta, buffer, file };\n\t}\n\n\tconst dataUrl = await readAsDataUrl(file, opts);\n\treturn { mode: \"data-url\", meta, dataUrl, file };\n}\n","import type { ReadFileAsArrayBufferResult, ReadFileAsDataUrlResult, ReadFileErrorCode, ReadFileOptionsBase } from \"../read-file\";\n\nexport type ImageMime = \"image/png\" | \"image/jpeg\" | \"image/webp\" | \"image/gif\" | \"image/svg+xml\" | \"image/avif\";\n\nexport type ReadImageErrorCode = ReadFileErrorCode | \"NOT_AN_IMAGE\" | \"IMAGE_DECODE_FAILED\";\n\nexport class ReadImageError extends Error {\n\tpublic readonly code: ReadImageErrorCode;\n\tpublic readonly cause?: unknown;\n\n\tconstructor(code: ReadImageErrorCode, message?: string, cause?: unknown) {\n\t\tsuper(message ?? code);\n\t\tthis.name = \"ReadImageError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\nexport type ReadImageOptionsBase = Omit<ReadFileOptionsBase, \"allowedMime\"> & {\n\t/** Ограничение по MIME (если не указано — любые image/*) */\n\tallowedMime?: readonly ImageMime[];\n};\n\nexport type ReadImageAsDataUrlOptions = ReadImageOptionsBase & {\n\tmode?: \"data-url\";\n};\n\nexport type ReadImageAsArrayBufferOptions = ReadImageOptionsBase & {\n\tmode: \"array-buffer\";\n};\n\nexport type ReadImageOptions = ReadImageAsDataUrlOptions | ReadImageAsArrayBufferOptions;\n\nexport type ImageDimensions = {\n\twidth: number;\n\theight: number;\n};\n\nexport type ReadImageAsDataUrlResult = ReadFileAsDataUrlResult & {\n\t/** безопасно вытащенные размеры (через decode) */\n\tdimensions?: ImageDimensions;\n};\n\nexport type ReadImageAsArrayBufferResult = ReadFileAsArrayBufferResult;\n\nexport type ReadImageResult = ReadImageAsDataUrlResult | ReadImageAsArrayBufferResult;\n","import { assertNotAborted, readFile } from \"../read-file\";\n\nimport { ReadImageError } from \"./types\";\n\nimport type { ImageDimensions, ReadImageAsArrayBufferResult, ReadImageAsDataUrlResult, ReadImageOptions } from \"./types\";\n\nfunction isImageMime(mime: string): boolean {\n\treturn mime.startsWith(\"image/\");\n}\n\nasync function decodeImageDimensions(dataUrl: string, signal?: AbortSignal): Promise<ImageDimensions> {\n\tassertNotAborted(signal);\n\n\tconst img = new Image();\n\timg.decoding = \"async\";\n\timg.src = dataUrl;\n\n\t// decode() поддерживается в современных браузерах; fallback на onload\n\t// NOTE: большого размера png падают...\n\t// if (typeof img.decode === \"function\") {\n\t// \ttry {\n\t// \t\tawait img.decode();\n\t// \t\tassertNotAborted(signal);\n\t// \t\treturn { width: img.naturalWidth, height: img.naturalHeight };\n\t// \t} catch (cause) {\n\t// \t\tthrow new ReadImageError(\"IMAGE_DECODE_FAILED\", \"Не удалось декодировать изображение\", cause);\n\t// \t}\n\t// }\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst cleanup = () => {\n\t\t\timg.onload = null;\n\t\t\timg.onerror = null;\n\t\t};\n\n\t\timg.onload = () => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t};\n\t\timg.onerror = (ev) => {\n\t\t\tcleanup();\n\t\t\treject(ev);\n\t\t};\n\t});\n\n\tassertNotAborted(signal);\n\treturn { width: img.naturalWidth, height: img.naturalHeight };\n}\n\n// Перегрузки для строгой типизации результата\n\nexport async function readImageFile(\n\tfile: File,\n\topts?: Omit<ReadImageOptions, \"mode\"> & { mode?: \"data-url\" }\n): Promise<ReadImageAsDataUrlResult>;\nexport async function readImageFile(\n\tfile: File,\n\topts: Omit<ReadImageOptions, \"mode\"> & { mode: \"array-buffer\" }\n): Promise<ReadImageAsArrayBufferResult>;\n\nexport async function readImageFile(\n\tfile: File,\n\topts: ReadImageOptions = {}\n): Promise<ReadImageAsDataUrlResult | ReadImageAsArrayBufferResult> {\n\tif (!isImageMime(file.type)) {\n\t\tthrow new ReadImageError(\"NOT_AN_IMAGE\", `Файл не является изображением. mime=${file.type || \"unknown\"}`);\n\t}\n\n\tconst baseOpts = { allowedMime: opts.allowedMime, maxBytes: opts.maxBytes, signal: opts.signal };\n\n\tif (opts.mode === \"array-buffer\") {\n\t\treturn readFile(file, { ...baseOpts, mode: \"array-buffer\" });\n\t}\n\n\tconst result = await readFile(file, baseOpts);\n\tconst dimensions = await decodeImageDimensions(result.dataUrl, opts.signal);\n\n\treturn { ...result, dimensions };\n}\n","const EXTENSION_CONTENT_TYPE: Readonly<Record<string, string>> = {\n\tpdf: \"application/pdf\",\n\tdoc: \"application/msword\",\n\tdocx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n\txls: \"application/vnd.ms-excel\",\n\txlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\tppt: \"application/vnd.ms-powerpoint\",\n\tpptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n\ttxt: \"text/plain; charset=UTF-8\",\n\trtf: \"application/rtf\",\n\todt: \"application/vnd.oasis.opendocument.text\",\n\tzip: \"application/zip\",\n\trar: \"application/vnd.rar\",\n\tjpg: \"image/jpeg\",\n\tjpeg: \"image/jpeg\",\n\tpng: \"image/png\",\n\tgif: \"image/gif\",\n\twebp: \"image/webp\"\n};\n\nexport function resolveMimeTypeByExtension(extension: string): string {\n\tconst normalizedExtension = extension.trim().toLowerCase();\n\n\treturn EXTENSION_CONTENT_TYPE[normalizedExtension] ?? \"application/octet-stream\";\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/file/fileSignatureConstants.ts","../../src/file/signature/hasSignatureBytes.ts","../../src/file/signature/checkArchiveSignature.ts","../../src/file/signature/checkPdfSignature.ts","../../src/file/checkFileSignature.ts","../../src/file/contentDisposition.ts","../../src/file/read-file/types.ts","../../src/file/read-file/assert.ts","../../src/file/read-file/readFile.ts","../../src/file/image/types.ts","../../src/file/image/readImageFile.ts","../../src/file/mimeType.ts"],"sourcesContent":["/** Приватный словарь результатов; значения сохраняют публичный string union. */\nexport const FILE_SIGNATURE_RESULT = {\n\tMATCH: \"match\",\n\tMISMATCH: \"mismatch\",\n\tUNSUPPORTED: \"unsupported\"\n} as const;\n\n/** Только dispatch поддержанных проверок; это не предметный allowlist caller. */\nexport const FILE_SIGNATURE_EXTENSION = {\n\tPNG: \"png\",\n\tJPG: \"jpg\",\n\tJPEG: \"jpeg\",\n\tBMP: \"bmp\",\n\tPSD: \"psd\",\n\tRTF: \"rtf\",\n\tRAR: \"rar\",\n\tZIP: \"zip\",\n\tSEVEN_ZIP: \"7z\",\n\tPDF: \"pdf\"\n} as const;\n","/** Читаем только переданный view: соседние bytes его backing buffer не участвуют. */\nexport function hasSignatureBytes(bytes: Uint8Array, signature: readonly number[], offset = 0): boolean {\n\tif (offset + signature.length > bytes.length) return false;\n\treturn signature.every((value, index) => bytes[offset + index] === value);\n}\n\n/** Точный оборванный prefix отличаем от неизвестного представления/SFX. */\nexport function isTruncatedSignature(bytes: Uint8Array, signature: readonly number[]): boolean {\n\treturn bytes.length < signature.length && bytes.every((value, index) => value === signature[index]);\n}\n","import { FILE_SIGNATURE_EXTENSION, FILE_SIGNATURE_RESULT } from \"../fileSignatureConstants\";\nimport { type ArchiveFileSignatureExtension, type FileSignatureCheckResult } from \"../fileSignatureTypes\";\n\nimport { hasSignatureBytes, isTruncatedSignature } from \"./hasSignatureBytes\";\n\nconst RAR_FAMILY = [0x52, 0x61, 0x72, 0x21];\nconst RAR_MARKER_SUFFIX = [0x1a, 0x07];\nconst RAR4_VERSION = 0;\nconst RAR5_VERSION = 1;\nconst RAR5_MARKER_END = 0;\nconst ZIP_FAMILY = [0x50, 0x4b];\nconst ZIP_LOCAL_FILE_MARKER = [0x03, 0x04];\nconst ZIP_END_OF_CENTRAL_DIRECTORY_MARKER = [0x05, 0x06];\nconst ZIP_LOCAL_FILE_HEADER_BYTES = 30;\nconst ZIP_END_OF_CENTRAL_DIRECTORY_BYTES = 22;\nconst SEVEN_ZIP_FAMILY = [0x37, 0x7a];\nconst SEVEN_ZIP_SIGNATURE = [0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c];\nconst SEVEN_ZIP_VERSION_BYTES = 2;\nconst SEVEN_ZIP_SUPPORTED_MAJOR_VERSION = 0;\nconst SEVEN_ZIP_SIGNATURE_HEADER_BYTES = 32;\n\n/**\n * RARLAB marker распознаёт RAR4/5 без main/encryption header. Не ищем marker\n * внутри SFX: отсутствие начального семейства не доказывает неверный архив.\n */\nfunction checkRarSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (isTruncatedSignature(bytes, RAR_FAMILY)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tif (!hasSignatureBytes(bytes, RAR_FAMILY)) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\tconst versionOffset = RAR_FAMILY.length + RAR_MARKER_SUFFIX.length;\n\tif (bytes.length <= versionOffset || !hasSignatureBytes(bytes, RAR_MARKER_SUFFIX, RAR_FAMILY.length))\n\t\treturn FILE_SIGNATURE_RESULT.MISMATCH;\n\tif (bytes[versionOffset] === RAR4_VERSION) return FILE_SIGNATURE_RESULT.MATCH;\n\tif (bytes[versionOffset] !== RAR5_VERSION) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\treturn bytes[versionOffset + 1] === RAR5_MARKER_END ? FILE_SIGNATURE_RESULT.MATCH : FILE_SIGNATURE_RESULT.MISMATCH;\n}\n\n/** PKWARE fixed header не проверяет payload, central directory, CRC или parts. */\nfunction checkZipSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (isTruncatedSignature(bytes, ZIP_FAMILY)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tif (!hasSignatureBytes(bytes, ZIP_FAMILY)) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\tconst recordOffset = ZIP_FAMILY.length;\n\tif (bytes.length <= recordOffset) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tif (bytes[recordOffset] === ZIP_LOCAL_FILE_MARKER[0]) {\n\t\treturn bytes.length >= ZIP_LOCAL_FILE_HEADER_BYTES && hasSignatureBytes(bytes, ZIP_LOCAL_FILE_MARKER, recordOffset)\n\t\t\t? FILE_SIGNATURE_RESULT.MATCH\n\t\t\t: FILE_SIGNATURE_RESULT.MISMATCH;\n\t}\n\tif (bytes[recordOffset] === ZIP_END_OF_CENTRAL_DIRECTORY_MARKER[0]) {\n\t\treturn bytes.length >= ZIP_END_OF_CENTRAL_DIRECTORY_BYTES &&\n\t\t\thasSignatureBytes(bytes, ZIP_END_OF_CENTRAL_DIRECTORY_MARKER, recordOffset)\n\t\t\t? FILE_SIGNATURE_RESULT.MATCH\n\t\t\t: FILE_SIGNATURE_RESULT.MISMATCH;\n\t}\n\t// Split/spanning, SFX и прочие records не подтверждаем как обычный start header.\n\treturn FILE_SIGNATURE_RESULT.UNSUPPORTED;\n}\n\n/** SignatureHeader 7-Zip занимает 32 bytes; NextHeader и CRC здесь не проверяются. */\nfunction checkSevenZipSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (isTruncatedSignature(bytes, SEVEN_ZIP_SIGNATURE)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tif (!hasSignatureBytes(bytes, SEVEN_ZIP_SIGNATURE)) {\n\t\treturn hasSignatureBytes(bytes, SEVEN_ZIP_FAMILY) ? FILE_SIGNATURE_RESULT.MISMATCH : FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\t}\n\tif (bytes.length < SEVEN_ZIP_SIGNATURE.length + SEVEN_ZIP_VERSION_BYTES) return FILE_SIGNATURE_RESULT.MISMATCH;\n\t// У официального reader major=0; minor не является запретом новой версии.\n\tif (bytes[SEVEN_ZIP_SIGNATURE.length] !== SEVEN_ZIP_SUPPORTED_MAJOR_VERSION) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\treturn bytes.length >= SEVEN_ZIP_SIGNATURE_HEADER_BYTES ? FILE_SIGNATURE_RESULT.MATCH : FILE_SIGNATURE_RESULT.MISMATCH;\n}\n\nexport function checkArchiveSignature(extension: ArchiveFileSignatureExtension, bytes: Uint8Array): FileSignatureCheckResult {\n\tif (extension === FILE_SIGNATURE_EXTENSION.RAR) return checkRarSignature(bytes);\n\tif (extension === FILE_SIGNATURE_EXTENSION.ZIP) return checkZipSignature(bytes);\n\treturn checkSevenZipSignature(bytes);\n}\n","import { FILE_SIGNATURE_RESULT } from \"../fileSignatureConstants\";\nimport { type FileSignatureCheckResult } from \"../fileSignatureTypes\";\n\nimport { hasSignatureBytes, isTruncatedSignature } from \"./hasSignatureBytes\";\n\nconst PDF_FAMILY = [0x25, 0x50, 0x44, 0x46];\nconst PDF_SCAN_LIMIT = 1024;\n// Позиции относятся к локальному %PDF-x.y header, а не ко всему документу.\nconst PDF_HEADER_BYTES = 8;\nconst PDF_FAMILY_SEPARATOR_OFFSET = 4;\nconst PDF_FAMILY_SEPARATOR = 0x2d;\nconst PDF_MAJOR_VERSION_OFFSET = 5;\nconst PDF_VERSION_SEPARATOR_OFFSET = 6;\nconst PDF_VERSION_SEPARATOR = 0x2e;\nconst PDF_MINOR_VERSION_OFFSET = 7;\nconst PDF_VERSION_DIGIT_FIRST = 0x30;\nconst PDF_VERSION_DIGIT_LAST = 0x39;\nconst PDF_VERSION_1_MAJOR = 0x31;\nconst PDF_VERSION_1_MINOR_LAST = 0x37;\nconst PDF_VERSION_2_MAJOR = 0x32;\nconst PDF_VERSION_2_MINOR = 0x30;\n\n/**\n * Adobe документирует header с ненулевым offset. Проверяем только первые 1024\n * bytes без копии/полного scan; альтернативный PS header и неизвестные версии\n * не объявляем mismatch. Версия в header не доказывает структуру документа.\n */\nexport function checkPdfSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (isTruncatedSignature(bytes, PDF_FAMILY)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tconst limit = Math.min(bytes.length, PDF_SCAN_LIMIT);\n\tlet malformedHeader = false;\n\tlet unknownRepresentation = false;\n\n\tfor (let offset = 0; offset + PDF_FAMILY.length <= limit; offset += 1) {\n\t\tif (!hasSignatureBytes(bytes, PDF_FAMILY, offset)) continue;\n\t\tif (offset + PDF_HEADER_BYTES > limit) {\n\t\t\t// EOF доказывает усечение; граница scan не является концом файла.\n\t\t\tif (offset + PDF_HEADER_BYTES > bytes.length) malformedHeader = true;\n\t\t\telse unknownRepresentation = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconst major = bytes[offset + PDF_MAJOR_VERSION_OFFSET];\n\t\tconst minor = bytes[offset + PDF_MINOR_VERSION_OFFSET];\n\t\tif (\n\t\t\tbytes[offset + PDF_FAMILY_SEPARATOR_OFFSET] !== PDF_FAMILY_SEPARATOR ||\n\t\t\tbytes[offset + PDF_VERSION_SEPARATOR_OFFSET] !== PDF_VERSION_SEPARATOR ||\n\t\t\tmajor < PDF_VERSION_DIGIT_FIRST ||\n\t\t\tmajor > PDF_VERSION_DIGIT_LAST ||\n\t\t\tminor < PDF_VERSION_DIGIT_FIRST ||\n\t\t\tminor > PDF_VERSION_DIGIT_LAST\n\t\t) {\n\t\t\tmalformedHeader = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\t(major === PDF_VERSION_1_MAJOR && minor <= PDF_VERSION_1_MINOR_LAST) ||\n\t\t\t(major === PDF_VERSION_2_MAJOR && minor === PDF_VERSION_2_MINOR)\n\t\t)\n\t\t\treturn FILE_SIGNATURE_RESULT.MATCH;\n\t\tunknownRepresentation = true;\n\t}\n\n\treturn unknownRepresentation\n\t\t? FILE_SIGNATURE_RESULT.UNSUPPORTED\n\t\t: malformedHeader\n\t\t\t? FILE_SIGNATURE_RESULT.MISMATCH\n\t\t\t: FILE_SIGNATURE_RESULT.UNSUPPORTED;\n}\n","import { FILE_SIGNATURE_EXTENSION, FILE_SIGNATURE_RESULT } from \"./fileSignatureConstants\";\nimport { type FileSignatureCheckOptions, type FileSignatureCheckResult } from \"./fileSignatureTypes\";\nimport { checkArchiveSignature } from \"./signature/checkArchiveSignature\";\nimport { checkPdfSignature } from \"./signature/checkPdfSignature\";\nimport { hasSignatureBytes, isTruncatedSignature } from \"./signature/hasSignatureBytes\";\n\nconst PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\nconst JPEG_MARKER_PREFIX = 0xff;\nconst JPEG_START_OF_IMAGE_MARKER = 0xd8;\n// Ограничение работы checker, а не максимальная длина fill bytes формата JPEG.\nconst JPEG_MARKER_SCAN_BYTES = 32;\nconst BITMAP_SIGNATURE = [0x42, 0x4d];\nconst BITMAP_FILE_HEADER_BYTES = 14;\nconst BITMAP_RESERVED_OFFSET = 6;\nconst BITMAP_RESERVED_BYTES = [0, 0, 0, 0];\nconst PSD_SIGNATURE = [0x38, 0x42, 0x50, 0x53];\nconst PSD_SUPPORTED_VERSION = [0, 1];\nconst PSD_RESERVED_BYTES = [0, 0, 0, 0, 0, 0];\nconst RTF_FAMILY = [0x7b, 0x5c, 0x72, 0x74, 0x66];\nconst RTF_SIGNATURE = [0x7b, 0x5c, 0x72, 0x74, 0x66, 0x31];\n// Цифра или буква продолжает control word вместо завершения версии rtf1.\nconst RTF_CONTROL_WORD_RANGES = {\n\tdigit: [0x30, 0x39],\n\tuppercase: [0x41, 0x5a],\n\tlowercase: [0x61, 0x7a]\n} as const;\n\nfunction checkJpegSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (bytes[0] !== JPEG_MARKER_PREFIX) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tlet offset = 1;\n\t// T.81 допускает FF fill перед marker. Длинный prefix не превращаем в scan файла.\n\twhile (offset < Math.min(bytes.length, JPEG_MARKER_SCAN_BYTES) && bytes[offset] === JPEG_MARKER_PREFIX) offset += 1;\n\tif (offset === JPEG_MARKER_SCAN_BYTES && bytes.length > offset) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\treturn bytes[offset] === JPEG_START_OF_IMAGE_MARKER ? FILE_SIGNATURE_RESULT.MATCH : FILE_SIGNATURE_RESULT.MISMATCH;\n}\n\nfunction checkBitmapSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (isTruncatedSignature(bytes, BITMAP_SIGNATURE)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\t// OS/2/DIB и неизвестные альтернативы не запрещаем по Windows BM header.\n\tif (!hasSignatureBytes(bytes, BITMAP_SIGNATURE)) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\tif (bytes.length < BITMAP_FILE_HEADER_BYTES) return FILE_SIGNATURE_RESULT.MISMATCH;\n\treturn hasSignatureBytes(bytes, BITMAP_RESERVED_BYTES, BITMAP_RESERVED_OFFSET)\n\t\t? FILE_SIGNATURE_RESULT.MATCH\n\t\t: FILE_SIGNATURE_RESULT.MISMATCH;\n}\n\nfunction checkPhotoshopSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (!hasSignatureBytes(bytes, PSD_SIGNATURE)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tconst reservedOffset = PSD_SIGNATURE.length + PSD_SUPPORTED_VERSION.length;\n\tif (bytes.length < reservedOffset) return FILE_SIGNATURE_RESULT.MISMATCH;\n\t// PSB=2 и неизвестные версии имеют общее семейство, но не подтверждают PSD=1.\n\tif (!hasSignatureBytes(bytes, PSD_SUPPORTED_VERSION, PSD_SIGNATURE.length)) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\treturn hasSignatureBytes(bytes, PSD_RESERVED_BYTES, reservedOffset) ? FILE_SIGNATURE_RESULT.MATCH : FILE_SIGNATURE_RESULT.MISMATCH;\n}\n\nfunction checkRtfSignature(bytes: Uint8Array): FileSignatureCheckResult {\n\tif (isTruncatedSignature(bytes, RTF_SIGNATURE)) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tif (!hasSignatureBytes(bytes, RTF_FAMILY)) return FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\tif (!hasSignatureBytes(bytes, RTF_SIGNATURE) || bytes.length <= RTF_SIGNATURE.length) return FILE_SIGNATURE_RESULT.MISMATCH;\n\tconst delimiter = bytes[RTF_SIGNATURE.length];\n\t// Полный control word не совпадает с \\rtf10 или \\rtf1abc.\n\treturn (delimiter >= RTF_CONTROL_WORD_RANGES.digit[0] && delimiter <= RTF_CONTROL_WORD_RANGES.digit[1]) ||\n\t\t(delimiter >= RTF_CONTROL_WORD_RANGES.uppercase[0] && delimiter <= RTF_CONTROL_WORD_RANGES.uppercase[1]) ||\n\t\t(delimiter >= RTF_CONTROL_WORD_RANGES.lowercase[0] && delimiter <= RTF_CONTROL_WORD_RANGES.lowercase[1])\n\t\t? FILE_SIGNATURE_RESULT.MISMATCH\n\t\t: FILE_SIGNATURE_RESULT.MATCH;\n}\n\n/**\n * Частичная pure-проверка fixed headers: нет FileReader, Node API, декодирования,\n * распаковки или чтения всего файла. Любой match относится только к header.\n * Общий ZIP/OLE marker не подтверждает Office/ODF/XFL; allowlist принадлежит caller.\n */\nexport function checkFileSignature({ extension, bytes }: FileSignatureCheckOptions): FileSignatureCheckResult {\n\tconst normalizedExtension = extension.trim().toLowerCase();\n\tswitch (normalizedExtension) {\n\t\tcase FILE_SIGNATURE_EXTENSION.PNG:\n\t\t\treturn hasSignatureBytes(bytes, PNG_SIGNATURE) ? FILE_SIGNATURE_RESULT.MATCH : FILE_SIGNATURE_RESULT.MISMATCH;\n\t\tcase FILE_SIGNATURE_EXTENSION.JPG:\n\t\tcase FILE_SIGNATURE_EXTENSION.JPEG:\n\t\t\treturn checkJpegSignature(bytes);\n\t\tcase FILE_SIGNATURE_EXTENSION.BMP:\n\t\t\treturn checkBitmapSignature(bytes);\n\t\tcase FILE_SIGNATURE_EXTENSION.PSD:\n\t\t\treturn checkPhotoshopSignature(bytes);\n\t\tcase FILE_SIGNATURE_EXTENSION.RTF:\n\t\t\treturn checkRtfSignature(bytes);\n\t\tcase FILE_SIGNATURE_EXTENSION.RAR:\n\t\tcase FILE_SIGNATURE_EXTENSION.ZIP:\n\t\tcase FILE_SIGNATURE_EXTENSION.SEVEN_ZIP:\n\t\t\treturn checkArchiveSignature(normalizedExtension, bytes);\n\t\tcase FILE_SIGNATURE_EXTENSION.PDF:\n\t\t\treturn checkPdfSignature(bytes);\n\t\tdefault:\n\t\t\treturn FILE_SIGNATURE_RESULT.UNSUPPORTED;\n\t}\n}\n","export type ContentDispositionKind = \"inline\" | \"attachment\";\n\nexport type ContentDispositionOptions = {\n\treadonly disposition: ContentDispositionKind;\n\treadonly filename: string;\n\treadonly asciiFallback?: string;\n};\n\nexport function sanitizeFileName(value: string, fallback = \"file\"): string {\n\tconst normalized = value\n\t\t.replace(/[\\r\\n]/g, \" \")\n\t\t.replace(/\"/g, \"'\")\n\t\t.trim();\n\n\treturn normalized.length > 0 ? normalized : fallback;\n}\n\nexport function encodeFilenameRFC5987(value: string): string {\n\treturn encodeURIComponent(value)\n\t\t.replace(/['()]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`)\n\t\t.replace(/\\*/g, \"%2A\");\n}\n\nexport function createAsciiFilenameFallback(value: string, fallback = \"file.bin\"): string {\n\tconst asciiValue = value\n\t\t.replace(/[\\r\\n]/g, \" \")\n\t\t.replace(/[\"\\\\]/g, \"_\")\n\t\t.replace(/[^\\x20-\\x7E]/g, \"_\")\n\t\t.replace(/\\s+/g, \" \")\n\t\t.trim();\n\n\treturn asciiValue.length > 0 ? asciiValue : fallback;\n}\n\nexport function createContentDispositionHeader({ disposition, filename, asciiFallback = \"file.bin\" }: ContentDispositionOptions): string {\n\tconst sanitizedFilename = sanitizeFileName(filename, asciiFallback);\n\tconst asciiFilename = createAsciiFilenameFallback(sanitizedFilename, asciiFallback);\n\tconst encodedFilename = encodeFilenameRFC5987(sanitizedFilename);\n\n\treturn `${disposition}; filename=\"${asciiFilename}\"; filename*=UTF-8''${encodedFilename}`;\n}\n","export type ReadMode = \"data-url\" | \"array-buffer\";\n\nexport type ReadFileErrorCode = \"NO_FILE\" | \"MIME_NOT_ALLOWED\" | \"FILE_TOO_LARGE\" | \"READ_ABORTED\" | \"READ_FAILED\";\n\nexport class ReadFileError extends Error {\n\tpublic readonly code: ReadFileErrorCode;\n\tpublic readonly cause?: unknown;\n\n\tconstructor(code: ReadFileErrorCode, message?: string, cause?: unknown) {\n\t\tsuper(message ?? code);\n\t\tthis.name = \"ReadFileError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\nexport type FileMeta = {\n\tmime: string;\n\tsize: number;\n\tname: string;\n\tlastModified: number;\n};\n\nexport type ReadFileOptionsBase = {\n\taccept?: readonly string[];\n\t/** Ограничение по MIME (если не указано — любые файлы) */\n\tallowedMime?: readonly string[];\n\t/** Ограничение по размеру файла (байты) */\n\tmaxBytes?: number;\n\t/** AbortController для отмены */\n\tsignal?: AbortSignal;\n};\n\nexport type ReadFileAsDataUrlOptions = ReadFileOptionsBase & {\n\tmode?: \"data-url\";\n};\n\nexport type ReadFileAsArrayBufferOptions = ReadFileOptionsBase & {\n\tmode: \"array-buffer\";\n};\n\nexport type ReadFileOptions = ReadFileAsDataUrlOptions | ReadFileAsArrayBufferOptions;\n\nexport type ReadFileAsDataUrlResult = {\n\tmode: \"data-url\";\n\tmeta: FileMeta;\n\tdataUrl: string;\n\tfile: File;\n};\n\nexport type ReadFileAsArrayBufferResult = {\n\tmode: \"array-buffer\";\n\tmeta: FileMeta;\n\tbuffer: ArrayBuffer;\n\tfile: File;\n};\n\nexport type ReadFileResult = ReadFileAsDataUrlResult | ReadFileAsArrayBufferResult;\n","import { ReadFileError } from \"./types\";\n\nconst MIME_PART_PATTERN = \"[a-z0-9!#$&^_.+-]+\";\nconst STRICT_MIME_PATTERN = new RegExp(`^${MIME_PART_PATTERN}\\\\/${MIME_PART_PATTERN}$`, \"i\");\nconst ACCEPT_MIME_PATTERN = new RegExp(`^${MIME_PART_PATTERN}\\\\/(?:${MIME_PART_PATTERN}|\\\\*)$`, \"i\");\nconst ACCEPT_EXTENSION_PATTERN = /^\\.[^\\s,]+$/;\n\nexport function assertValidAllowedMime(allowedMime?: readonly string[]): void {\n\tif (!allowedMime) return;\n\n\tfor (const [index, mime] of allowedMime.entries()) {\n\t\tif (!STRICT_MIME_PATTERN.test(mime)) {\n\t\t\tthrow new Error(\n\t\t\t\t`DropZone: некорректный allowedMime[${index}]=\"${mime}\". Ожидается полный MIME-тип вида \"application/pdf\" без wildcard и параметров.`\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function assertValidAccept(accept?: readonly string[]): void {\n\tif (!accept) return;\n\n\tfor (const [index, specifier] of accept.entries()) {\n\t\tconst isValidSpecifier = ACCEPT_EXTENSION_PATTERN.test(specifier) || ACCEPT_MIME_PATTERN.test(specifier);\n\n\t\tif (!isValidSpecifier) {\n\t\t\tthrow new Error(\n\t\t\t\t`DropZone: некорректный accept[${index}]=\"${specifier}\". Ожидается расширение вида \".pdf\" или MIME-тип вида \"application/pdf\" / \"image/*\".`\n\t\t\t);\n\t\t}\n\t}\n}\n\nexport function assertNotAborted(signal?: AbortSignal): void {\n\tif (signal?.aborted) {\n\t\tthrow new ReadFileError(\"READ_ABORTED\", \"Прервано по сигналу\");\n\t}\n}\n","import { assertNotAborted } from \"./assert\";\nimport { FileMeta, ReadFileError, ReadFileOptions, ReadFileResult, ReadMode } from \"./types\";\n\nfunction normalizeMime(mime?: string): string {\n\treturn mime?.trim().toLowerCase() ?? \"\";\n}\n\nfunction getFileExtension(fileName: string): string {\n\tconst extension = fileName.split(\".\").pop();\n\treturn normalizeMime(extension);\n}\n\nexport function buildMeta(file: File): FileMeta {\n\treturn {\n\t\tmime: file.type,\n\t\tsize: file.size,\n\t\tname: file.name,\n\t\tlastModified: file.lastModified\n\t};\n}\n\nexport function attachAbort(signal: AbortSignal | undefined, reader: FileReader): (() => void) | undefined {\n\tif (!signal) return undefined;\n\n\tconst onAbort = () => {\n\t\ttry {\n\t\t\treader.abort();\n\t\t} catch {\n\t\t\t// ignore\n\t\t}\n\t};\n\n\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\treturn () => signal.removeEventListener(\"abort\", onAbort);\n}\n\nfunction validateFile(file: File, opts: ReadFileOptions): void {\n\tif (opts.allowedMime && opts.allowedMime.length > 0) {\n\t\tconst allowed = new Set(opts.allowedMime.map((mime) => normalizeMime(mime)));\n\t\tconst fileMime = normalizeMime(file.type);\n\t\tif (!allowed.has(fileMime)) {\n\t\t\tthrow new ReadFileError(\"MIME_NOT_ALLOWED\", `Неподдерживаемый MIME тип: ${file.type}`);\n\t\t}\n\t} else if (opts.accept && opts.accept.length > 0) {\n\t\t// Берём расширение файла, если его нет, пытаемся вытащить из MIME\n\t\tconst fileMime = getFileExtension(file.name) || file.type.split(\"/\").pop();\n\t\tif (!fileMime) {\n\t\t\t// NOTE: Если неизвестный mime и нет расширения? Пока запретить такой сценарий\n\t\t\tthrow new ReadFileError(\"MIME_NOT_ALLOWED\", `Неопределенный MIME тип`);\n\t\t}\n\n\t\tconst allowed = opts.accept.map((mime) => normalizeMime(mime));\n\t\tconst isAllowed = allowed.some((mime) => mime.includes(fileMime));\n\n\t\tif (!isAllowed) {\n\t\t\tthrow new ReadFileError(\"MIME_NOT_ALLOWED\", `Неподдерживаемый MIME тип: ${fileMime}`);\n\t\t}\n\t}\n\n\tif (typeof opts.maxBytes === \"number\" && file.size > opts.maxBytes) {\n\t\tthrow new ReadFileError(\"FILE_TOO_LARGE\", `Файл слишком большой: ${file.size} > ${opts.maxBytes}`);\n\t}\n}\n\nfunction readAsDataUrl(file: File, opts: ReadFileOptions): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader();\n\n\t\tconst detachAbort = attachAbort(opts.signal, reader);\n\n\t\treader.onerror = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Не удалось прочитать файл как DataURL\", reader.error));\n\t\t};\n\n\t\treader.onabort = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_ABORTED\", \"Чтение файла прервано\"));\n\t\t};\n\n\t\treader.onload = () => {\n\t\t\tdetachAbort?.();\n\t\t\tconst result = reader.result;\n\t\t\tif (typeof result !== \"string\") {\n\t\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Неожиданный тип результата FileReader\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(result);\n\t\t};\n\n\t\ttry {\n\t\t\treader.readAsDataURL(file);\n\t\t} catch (cause) {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"При чтении DataURL функция FileReader выдала ошибку\", cause));\n\t\t}\n\t});\n}\n\nfunction readAsArrayBuffer(file: File, opts: ReadFileOptions): Promise<ArrayBuffer> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst reader = new FileReader();\n\n\t\tconst detachAbort = attachAbort(opts.signal, reader);\n\n\t\treader.onerror = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Не удалось прочитать файл как ArrayBuffer.\", reader.error));\n\t\t};\n\n\t\treader.onabort = () => {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_ABORTED\", \"Чтение файла прервано\"));\n\t\t};\n\n\t\treader.onload = () => {\n\t\t\tdetachAbort?.();\n\t\t\tconst result = reader.result;\n\t\t\tif (!(result instanceof ArrayBuffer)) {\n\t\t\t\treject(new ReadFileError(\"READ_FAILED\", \"Неожиданный тип результата FileReader\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(result);\n\t\t};\n\n\t\ttry {\n\t\t\treader.readAsArrayBuffer(file);\n\t\t} catch (cause) {\n\t\t\tdetachAbort?.();\n\t\t\treject(new ReadFileError(\"READ_FAILED\", \"При чтении ArrayBuffer возникла ошибка FileReader\", cause));\n\t\t}\n\t});\n}\n\n// Перегрузки для строгой типизации результата\n\nexport async function readFile(\n\tfile: File,\n\topts?: Omit<ReadFileOptions, \"mode\"> & { mode?: \"data-url\" }\n): Promise<Extract<ReadFileResult, { mode: \"data-url\" }>>;\nexport async function readFile(\n\tfile: File,\n\topts: Omit<ReadFileOptions, \"mode\"> & { mode: \"array-buffer\" }\n): Promise<Extract<ReadFileResult, { mode: \"array-buffer\" }>>;\n\nexport async function readFile(file: File, opts: ReadFileOptions = {}): Promise<ReadFileResult> {\n\tassertNotAborted(opts.signal);\n\tvalidateFile(file, opts);\n\n\tconst mode: ReadMode = opts.mode ?? \"data-url\";\n\tconst meta = buildMeta(file);\n\n\tif (mode === \"array-buffer\") {\n\t\tconst buffer = await readAsArrayBuffer(file, opts);\n\t\treturn { mode: \"array-buffer\", meta, buffer, file };\n\t}\n\n\tconst dataUrl = await readAsDataUrl(file, opts);\n\treturn { mode: \"data-url\", meta, dataUrl, file };\n}\n","import type { ReadFileAsArrayBufferResult, ReadFileAsDataUrlResult, ReadFileErrorCode, ReadFileOptionsBase } from \"../read-file\";\n\nexport type ImageMime = \"image/png\" | \"image/jpeg\" | \"image/webp\" | \"image/gif\" | \"image/svg+xml\" | \"image/avif\";\n\nexport type ReadImageErrorCode = ReadFileErrorCode | \"NOT_AN_IMAGE\" | \"IMAGE_DECODE_FAILED\";\n\nexport class ReadImageError extends Error {\n\tpublic readonly code: ReadImageErrorCode;\n\tpublic readonly cause?: unknown;\n\n\tconstructor(code: ReadImageErrorCode, message?: string, cause?: unknown) {\n\t\tsuper(message ?? code);\n\t\tthis.name = \"ReadImageError\";\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\nexport type ReadImageOptionsBase = Omit<ReadFileOptionsBase, \"allowedMime\"> & {\n\t/** Ограничение по MIME (если не указано — любые image/*) */\n\tallowedMime?: readonly ImageMime[];\n};\n\nexport type ReadImageAsDataUrlOptions = ReadImageOptionsBase & {\n\tmode?: \"data-url\";\n};\n\nexport type ReadImageAsArrayBufferOptions = ReadImageOptionsBase & {\n\tmode: \"array-buffer\";\n};\n\nexport type ReadImageOptions = ReadImageAsDataUrlOptions | ReadImageAsArrayBufferOptions;\n\nexport type ImageDimensions = {\n\twidth: number;\n\theight: number;\n};\n\nexport type ReadImageAsDataUrlResult = ReadFileAsDataUrlResult & {\n\t/** безопасно вытащенные размеры (через decode) */\n\tdimensions?: ImageDimensions;\n};\n\nexport type ReadImageAsArrayBufferResult = ReadFileAsArrayBufferResult;\n\nexport type ReadImageResult = ReadImageAsDataUrlResult | ReadImageAsArrayBufferResult;\n","import { assertNotAborted, readFile } from \"../read-file\";\n\nimport { ReadImageError } from \"./types\";\n\nimport type { ImageDimensions, ReadImageAsArrayBufferResult, ReadImageAsDataUrlResult, ReadImageOptions } from \"./types\";\n\nfunction isImageMime(mime: string): boolean {\n\treturn mime.startsWith(\"image/\");\n}\n\nasync function decodeImageDimensions(dataUrl: string, signal?: AbortSignal): Promise<ImageDimensions> {\n\tassertNotAborted(signal);\n\n\tconst img = new Image();\n\timg.decoding = \"async\";\n\timg.src = dataUrl;\n\n\t// decode() поддерживается в современных браузерах; fallback на onload\n\t// NOTE: большого размера png падают...\n\t// if (typeof img.decode === \"function\") {\n\t// \ttry {\n\t// \t\tawait img.decode();\n\t// \t\tassertNotAborted(signal);\n\t// \t\treturn { width: img.naturalWidth, height: img.naturalHeight };\n\t// \t} catch (cause) {\n\t// \t\tthrow new ReadImageError(\"IMAGE_DECODE_FAILED\", \"Не удалось декодировать изображение\", cause);\n\t// \t}\n\t// }\n\n\tawait new Promise<void>((resolve, reject) => {\n\t\tconst cleanup = () => {\n\t\t\timg.onload = null;\n\t\t\timg.onerror = null;\n\t\t};\n\n\t\timg.onload = () => {\n\t\t\tcleanup();\n\t\t\tresolve();\n\t\t};\n\t\timg.onerror = (ev) => {\n\t\t\tcleanup();\n\t\t\treject(ev);\n\t\t};\n\t});\n\n\tassertNotAborted(signal);\n\treturn { width: img.naturalWidth, height: img.naturalHeight };\n}\n\n// Перегрузки для строгой типизации результата\n\nexport async function readImageFile(\n\tfile: File,\n\topts?: Omit<ReadImageOptions, \"mode\"> & { mode?: \"data-url\" }\n): Promise<ReadImageAsDataUrlResult>;\nexport async function readImageFile(\n\tfile: File,\n\topts: Omit<ReadImageOptions, \"mode\"> & { mode: \"array-buffer\" }\n): Promise<ReadImageAsArrayBufferResult>;\n\nexport async function readImageFile(\n\tfile: File,\n\topts: ReadImageOptions = {}\n): Promise<ReadImageAsDataUrlResult | ReadImageAsArrayBufferResult> {\n\tif (!isImageMime(file.type)) {\n\t\tthrow new ReadImageError(\"NOT_AN_IMAGE\", `Файл не является изображением. mime=${file.type || \"unknown\"}`);\n\t}\n\n\tconst baseOpts = { allowedMime: opts.allowedMime, maxBytes: opts.maxBytes, signal: opts.signal };\n\n\tif (opts.mode === \"array-buffer\") {\n\t\treturn readFile(file, { ...baseOpts, mode: \"array-buffer\" });\n\t}\n\n\tconst result = await readFile(file, baseOpts);\n\tconst dimensions = await decodeImageDimensions(result.dataUrl, opts.signal);\n\n\treturn { ...result, dimensions };\n}\n","const EXTENSION_CONTENT_TYPE: Readonly<Record<string, string>> = {\n\tpdf: \"application/pdf\",\n\tdoc: \"application/msword\",\n\tdocx: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n\txls: \"application/vnd.ms-excel\",\n\txlsx: \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\",\n\tppt: \"application/vnd.ms-powerpoint\",\n\tpptx: \"application/vnd.openxmlformats-officedocument.presentationml.presentation\",\n\ttxt: \"text/plain; charset=UTF-8\",\n\trtf: \"application/rtf\",\n\todt: \"application/vnd.oasis.opendocument.text\",\n\tzip: \"application/zip\",\n\trar: \"application/vnd.rar\",\n\tjpg: \"image/jpeg\",\n\tjpeg: \"image/jpeg\",\n\tpng: \"image/png\",\n\tgif: \"image/gif\",\n\twebp: \"image/webp\"\n};\n\nexport function resolveMimeTypeByExtension(extension: string): string {\n\tconst normalizedExtension = extension.trim().toLowerCase();\n\n\treturn EXTENSION_CONTENT_TYPE[normalizedExtension] ?? \"application/octet-stream\";\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { type ArchiveFileSignatureExtension, type FileSignatureCheckResult } from "../fileSignatureTypes";
2
+ export declare function checkArchiveSignature(extension: ArchiveFileSignatureExtension, bytes: Uint8Array): FileSignatureCheckResult;
3
+ //# sourceMappingURL=checkArchiveSignature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkArchiveSignature.d.ts","sourceRoot":"","sources":["../../../src/file/signature/checkArchiveSignature.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,6BAA6B,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAoE1G,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,6BAA6B,EAAE,KAAK,EAAE,UAAU,GAAG,wBAAwB,CAI3H"}
@@ -0,0 +1,8 @@
1
+ import { type FileSignatureCheckResult } from "../fileSignatureTypes";
2
+ /**
3
+ * Adobe документирует header с ненулевым offset. Проверяем только первые 1024
4
+ * bytes без копии/полного scan; альтернативный PS header и неизвестные версии
5
+ * не объявляем mismatch. Версия в header не доказывает структуру документа.
6
+ */
7
+ export declare function checkPdfSignature(bytes: Uint8Array): FileSignatureCheckResult;
8
+ //# sourceMappingURL=checkPdfSignature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkPdfSignature.d.ts","sourceRoot":"","sources":["../../../src/file/signature/checkPdfSignature.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAqBtE;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,wBAAwB,CAwC7E"}
@@ -0,0 +1,5 @@
1
+ /** Читаем только переданный view: соседние bytes его backing buffer не участвуют. */
2
+ export declare function hasSignatureBytes(bytes: Uint8Array, signature: readonly number[], offset?: number): boolean;
3
+ /** Точный оборванный prefix отличаем от неизвестного представления/SFX. */
4
+ export declare function isTruncatedSignature(bytes: Uint8Array, signature: readonly number[]): boolean;
5
+ //# sourceMappingURL=hasSignatureBytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hasSignatureBytes.d.ts","sourceRoot":"","sources":["../../../src/file/signature/hasSignatureBytes.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,SAAI,GAAG,OAAO,CAGtG;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAE7F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuzaki13/react-foundation-lib",
3
- "version": "2.18.1",
3
+ "version": "2.19.0",
4
4
  "description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Ryuzaki13",
@@ -4,14 +4,15 @@ import { Meta } from "@storybook/addon-docs/blocks";
4
4
 
5
5
  # Работа с пользовательскими файлами
6
6
 
7
- Модуль `file` содержит browser helpers для четырёх связанных задач:
7
+ Модуль `file` содержит browser helpers и чистые функции для связанных задач:
8
8
 
9
9
  - прочитать выбранный пользователем `File` как data URL или `ArrayBuffer`;
10
10
  - проверить MIME-тип и размер файла перед чтением;
11
11
  - прочитать изображение и получить его естественные размеры;
12
12
  - определить MIME по расширению и безопаснее сформировать HTTP-заголовок `Content-Disposition` с Unicode-именем файла.
13
+ - проверить ограниченный набор сигнатур в `Uint8Array`, различая совпадение, противоречие и неподдержанный вариант.
13
14
 
14
- Модуль является низкоуровневым foundation-слоем. Он не рисует `<input type="file">`, не отправляет файл на backend, не показывает preview или progress, не проверяет реальное содержимое файла и не заменяет серверную валидацию.
15
+ Модуль является низкоуровневым foundation-слоем. Он не рисует `<input type="file">`, не отправляет файл на backend, не показывает preview или progress, не выполняет полную валидацию содержимого файла и не заменяет серверную policy.
15
16
 
16
17
  ## Содержание
17
18
 
@@ -29,6 +30,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
29
30
  - [Ошибки `readFile`](#ошибки-readfile)
30
31
  - [`readImageFile`](#readimagefile)
31
32
  - [Ошибки `readImageFile`](#ошибки-readimagefile)
33
+ - [`checkFileSignature`](#checkfilesignature)
32
34
  - [`resolveMimeTypeByExtension`](#resolvemimetypebyextension)
33
35
  - [Helpers для `Content-Disposition`](#helpers-для-content-disposition)
34
36
  - [Низкоуровневые helpers](#низкоуровневые-helpers)
@@ -54,6 +56,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
54
56
  - получает `naturalWidth` и `naturalHeight` изображения в режиме data URL;
55
57
  - сопоставляет ограниченный список расширений с MIME;
56
58
  - формирует `Content-Disposition` одновременно с ASCII fallback и UTF-8 `filename*`.
59
+ - проверяет поддержанные начальные headers; PDF header ищет только в первых 1024 bytes.
57
60
 
58
61
  ### Чего модуль не делает
59
62
 
@@ -64,7 +67,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
64
67
  - не скачивает `Blob` в браузере;
65
68
  - не превращает произвольный base64 в `Blob`;
66
69
  - не читает файл потоково и не сообщает progress;
67
- - не проверяет magic bytes, структуру PDF, ZIP, изображения или документа;
70
+ - не проверяет целостность и полную структуру PDF, ZIP, изображения или документа;
68
71
  - не обнаруживает вирусы и вредоносное содержимое;
69
72
  - не гарантирует, что `File.type` соответствует байтам;
70
73
  - не сжимает и не изменяет изображение;
@@ -136,13 +139,14 @@ import { readFile } from "@ryuzaki13/react-foundation-lib/src/file/read-file/rea
136
139
 
137
140
  Чистые helpers не зависят от DOM:
138
141
 
142
+ - `checkFileSignature`;
139
143
  - `resolveMimeTypeByExtension`;
140
144
  - `sanitizeFileName`;
141
145
  - `createAsciiFilenameFallback`;
142
146
  - `encodeFilenameRFC5987`;
143
147
  - `createContentDispositionHeader`.
144
148
 
145
- Их можно вызывать на серверной boundary, если runtime предоставляет стандартные строковые функции и `encodeURIComponent`.
149
+ Их можно вызывать на серверной boundary. Проверке сигнатуры нужен только стандартный `Uint8Array`; строковым helpers — стандартные строковые функции и `encodeURIComponent`.
146
150
 
147
151
  Важно различать:
148
152
 
@@ -151,6 +155,58 @@ import { readFile } from "@ryuzaki13/react-foundation-lib/src/file/read-file/rea
151
155
 
152
156
  Сам импорт не начинает чтение файла. Browser API используется только при вызове соответствующей функции.
153
157
 
158
+ ## `checkFileSignature`
159
+
160
+ ```ts
161
+ import { checkFileSignature, type FileSignatureCheckResult } from "@ryuzaki13/react-foundation-lib/file";
162
+
163
+ const result: FileSignatureCheckResult = checkFileSignature({ extension: "png", bytes });
164
+ ```
165
+
166
+ Функция принимает `FileSignatureCheckOptions = Readonly<{ extension: string; bytes: Uint8Array }>`.
167
+ Расширение передаётся без точки и имени файла; применяется только `trim().toLowerCase()`.
168
+ Bytes не копируются и не меняются, учитывается именно переданный view с его `byteOffset` и длиной.
169
+ Функция не использует Node API, DOM, MIME или filesystem и не вводит список разрешённых приложению форматов.
170
+
171
+ | Результат | Значение |
172
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
173
+ | `match` | Совпала проверяемая сигнатура/header; остальное содержимое не проверено. |
174
+ | `mismatch` | Поддержанная сигнатура противоречит bytes либо распознанный обязательный header усечён/повреждён. |
175
+ | `unsupported` | Этот формат или представление не распознано достаточно надёжно этой частичной проверкой. Это не совпадение и не самостоятельный запрет файла. |
176
+
177
+ ### Покрытие и пределы
178
+
179
+ | Расширение | Проверка |
180
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
181
+ | `png` | Восемь начальных bytes `89 50 4E 47 0D 0A 1A 0A`; без chunks/CRC. |
182
+ | `jpg`, `jpeg` | Начальный SOI `FF D8`; JFIF, Exif и конкретный APP marker не обязательны. Дополнительные FF fill bytes обрабатываются в первых 32 bytes; более длинный неизвестный prefix даёт `unsupported`. |
183
+ | `bmp` | Windows `BM`, полный 14-byte BITMAPFILEHEADER и нулевые reserved fields. Версия следующего DIB header не ограничивается; альтернативное начало даёт `unsupported`. |
184
+ | `psd` | `8BPS`, big-endian version 1, шесть нулевых reserved bytes. PSB version 2 и неизвестные версии дают `unsupported`. |
185
+ | `rtf` | Обычный текстовый header `{\rtf1` с границей control word; неизвестное начало и обёртки дают `unsupported`. |
186
+ | `rar` | Начальный RAR4/5 marker; неизвестные версии, SFX и неизвестное начало дают `unsupported`. Main/encryption header не проверяется. |
187
+ | `zip` | Local header `PK 03 04` с фиксированной частью 30 bytes или EOCD `PK 05 06` с частью 22 bytes. Split/SFX/неизвестное начало дают `unsupported`. Entries, payload, CRC и central directory не проверяются. |
188
+ | `7z` | Начальная сигнатура, major 0 и наличие SignatureHeader 32 bytes; minor не ограничивается. Неизвестный major, SFX и неизвестное начало дают `unsupported`. CRC/NextHeader не проверяются. |
189
+ | `pdf` | `%PDF-1.0`…`%PDF-1.7` или `%PDF-2.0` целиком в первых 1024 bytes, включая ненулевой offset. Неизвестные версии, альтернативный PS header и отсутствие распознанного header дают `unsupported`. Пересечение scan limit не считается EOF. |
190
+
191
+ Общий ZIP/CFB header **не доказывает** Office/ODF/XFL формат. Расширения `doc`, `ppt`, `xls`, `dot`, `docx`, `pptx`, `xlsx`, `docm`, `odt`, `fla`, `ai`, `txt`, `bak`, `cdr`, `tga`, `dt`, `mdb`, `accdb`, `erwin` и прочие не перечисленные выше возвращают `unsupported` независимо от общего контейнера.
192
+
193
+ Архивный header без начального marker не объявляется неверным архивом: SFX может иметь исполняемый prefix.
194
+ Но распознанный поддержанный header с неверными обязательными bytes или усечением не прячется за `unsupported`.
195
+ Например, `Rar!` с повреждёнными marker fields и `PK 03 04` короче 30 bytes дают `mismatch`.
196
+
197
+ Проверка читает фиксированные заголовки, максимум 32 bytes для JPEG и 1024 для PDF.
198
+ Она не сканирует весь файл, не распаковывает контейнеры и не валидирует структуру или безопасность payload.
199
+ Злоумышленник может добавить правильный header к произвольным bytes; результат останется `match`.
200
+ Обрабатывать `unsupported`, ограничивать размер/форматы и решать вопрос публикации обязан caller.
201
+ `readFile` не вызывает эту функцию автоматически и сохраняет прежнее поведение.
202
+
203
+ ### Основания проверки
204
+
205
+ - [W3C PNG](https://www.w3.org/TR/PNG-Structure.html), [ITU-T T.81, Annex B](https://www.w3.org/Graphics/JPEG/itu-t81.pdf).
206
+ - [Microsoft BITMAPFILEHEADER](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapfileheader), [Adobe Photoshop file format](https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/), [Microsoft RTF header](https://learn.microsoft.com/en-us/openspecs/exchange_server_protocols/ms-oxrtfex/3cf14977-3883-404a-8ed8-57c99263fb76).
207
+ - [RARLAB](https://www.rarlab.com/technote.htm), [PKWARE APPNOTE](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT), [7-Zip format](https://raw.githubusercontent.com/ip7z/7zip/main/DOC/7zFormat.txt).
208
+ - [RFC 8118](https://www.rfc-editor.org/rfc/rfc8118.html), [Adobe PDF Reference, Appendix H](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.4.pdf).
209
+
154
210
  ## Минимальные понятия
155
211
 
156
212
  ### Что такое `File`
@@ -1612,7 +1668,7 @@ Backend обязан повторно проверить:
1612
1668
 
1613
1669
  ### `File.type` недоверенный
1614
1670
 
1615
- `allowedMime` сравнивает только `file.type`. Модуль не открывает заголовок файла, чтобы подтвердить PNG/PDF/ZIP signature.
1671
+ `allowedMime` сравнивает только `file.type`. `readFile` не открывает заголовок для подтверждения signature; отдельный `checkFileSignature` работает с уже полученными bytes.
1616
1672
 
1617
1673
  ### Расширение недоверенное
1618
1674
 
@@ -1775,7 +1831,7 @@ expect(contentDisposition).toContain("filename*=UTF-8''%D0%9E");
1775
1831
  9. `assertValidAllowedMime` и `assertValidAccept` не вызываются автоматически.
1776
1832
  10. `maxBytes` не валидируется как положительное конечное число.
1777
1833
  11. Файл читается целиком; progress и streaming отсутствуют.
1778
- 12. MIME и extension не сверяются с реальными bytes.
1834
+ 12. `readFile` не сверяет MIME и extension с реальными bytes. Отдельный `checkFileSignature` имеет частичное покрытие headers, описанное выше.
1779
1835
  13. Resolver принимает только расширение без точки и поддерживает ограниченную таблицу.
1780
1836
  14. Filename helpers предназначены для header, а не для безопасного filesystem path.
1781
1837
 
@@ -1818,6 +1874,7 @@ const result = await readImageFile(file, {
1818
1874
  | `assertNotAborted` | Бросает `READ_ABORTED` для уже отменённого signal |
1819
1875
  | `attachAbort` | Связывает signal с `FileReader.abort()` и возвращает cleanup |
1820
1876
  | `buildMeta` | Копирует четыре metadata-поля `File` в новый объект |
1877
+ | `checkFileSignature` | Частично проверяет header в `Uint8Array`, не весь файл |
1821
1878
 
1822
1879
  ### MIME и HTTP filename
1823
1880