@ryuzaki13/react-foundation-lib 2.18.1 → 2.20.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.
@@ -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.20.0",
4
4
  "description": "Reusable React and TypeScript foundation utilities, hooks, and primitives.",
5
5
  "license": "MIT",
6
6
  "author": "Ryuzaki13",
@@ -4,7 +4,7 @@ import { Meta } from "@storybook/addon-docs/blocks";
4
4
 
5
5
  # Error Report
6
6
 
7
- Модуль `@ryuzaki13/react-foundation-lib/error-report` собирает клиентскую диагностику ошибки в versioned bounded draft, временно хранит последние drafts в `sessionStorage` и предоставляет durable IndexedDB queue для отложенной доставки.
7
+ Модуль `@ryuzaki13/react-foundation-lib/error-report` собирает клиентскую диагностику ошибки в versioned bounded draft, по умолчанию временно хранит последние drafts в `sessionStorage` и предоставляет durable IndexedDB queue для отложенной доставки. Browser bootstrap может односторонне перевести capture/drafts в память документа.
8
8
 
9
9
  Он умеет объединить:
10
10
 
@@ -36,6 +36,7 @@ import {
36
36
  captureRuntimeErrorReport,
37
37
  createErrorReportQueue,
38
38
  createErrorInfo,
39
+ disableErrorReportBrowserStorage,
39
40
  installErrorReportBrowserBreadcrumbs,
40
41
  setErrorReportRuntimeErrorReporter,
41
42
  setErrorReportCaptureOptions,
@@ -76,6 +77,44 @@ isErrorReportingEnabled({ isDev: false });
76
77
  - в development capture-функции возвращают `undefined`;
77
78
  - тест может явно передать `{ isDev: false }` только в эту функцию, но остальные capture-функции всё равно используют общий `__DEV__`.
78
79
 
80
+ ## Документ без browser persistence
81
+
82
+ ### `disableErrorReportBrowserStorage(): void`
83
+
84
+ ```ts
85
+ // Browser bootstrap: до первого capture и запуска UI/error-report lifecycle.
86
+ disableErrorReportBrowserStorage();
87
+ ```
88
+
89
+ Вызов односторонне переводит **capture, drafts и persisted-query diagnostics**
90
+ текущего документа в память. Повторный вызов ничего не сбрасывает. Обратного
91
+ включения нет: документ с другим режимом приложение открывает заново.
92
+ `setErrorReportCaptureOptions`, регистрация reporter и их cleanup не меняют
93
+ эту политику. В SSR вызов ничего не делает и не переключает общий process state.
94
+
95
+ Сам импорт `/error-report` не читает browser storage. В обычном режиме первый
96
+ `getErrorReportDrafts`, `getErrorReportDraft`, `updateErrorReportDraft` или capture
97
+ лениво загружает прежние drafts; дальнейшие чтение и сохранение остаются прежними.
98
+ В memory-режиме последние 10 drafts и отдельный стабильный session ID живут
99
+ только в памяти документа. Уже загруженные обычные drafts не копируются туда,
100
+ а существующие записи в browser storage не удаляются.
101
+
102
+ Memory capture сохраняет прежний контракт: runtime/query/mutation возвращают
103
+ `Promise<ErrorReportDraft | undefined>`, прямой draft capture —
104
+ `ErrorReportDraft | undefined`. В production создаётся обычный draft в памяти;
105
+ SSR/dev по-прежнему не создают draft. Поэтому UI boundary может продолжать
106
+ использовать `.then` и report ID. Сбор QueryClient diagnostics из памяти остаётся
107
+ доступным; `collectPersistedQueryDiagnostics()` возвращает `[]` без открытия IDB.
108
+
109
+ Вызов не отменяет native I/O, начатый ранее. Однако его поздний persisted result
110
+ не попадёт в memory draft, а после отключения drafts больше не пишутся в browser
111
+ storage. Для строгого отсутствия чтений выбирать режим нужно в bootstrap.
112
+
113
+ **Durable queue независима.** `createErrorReportQueue()` — явный storage API;
114
+ эта функция его не переключает. Приложение отдельно исключает создание очереди,
115
+ enqueue и запуск delivery в документе без persistence. Вызов также не меняет
116
+ Query persistence, Service Worker, пользовательские черновики или transport.
117
+
79
118
  ## Compile-time константы
80
119
 
81
120
  Потребитель должен определить:
@@ -430,7 +469,7 @@ type ErrorReportDraft = {
430
469
  };
431
470
  ```
432
471
 
433
- `reportId` — UUID отдельного отчёта. `sessionId` стабилен в текущем `sessionStorage`. `createdUtc` формируется как OData datetime UTC.
472
+ `reportId` — UUID отдельного отчёта. По умолчанию `sessionId` стабилен в текущем `sessionStorage`; после browser opt-out используется отдельный ID только в памяти документа. `createdUtc` формируется как OData datetime UTC.
434
473
 
435
474
  Payload содержит category/source/error, build environment, client, location, viewport, breadcrumbs и optional секции query/mutation/react/context.
436
475
 
@@ -452,11 +491,11 @@ parseErrorReportDrafts(value);
452
491
 
453
492
  ### Хранение
454
493
 
455
- Versioned drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v2`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива. Предыдущая unversioned storage-форма не загружается.
494
+ По умолчанию versioned drafts сохраняются под ключом `${__APP_ID__}.errorReport.drafts.v2`. В одной вкладке остаются последние 10 элементов. Старые удаляются с начала массива. Предыдущая unversioned storage-форма не загружается. После browser opt-out тот же лимит применяется к отдельному memory store без чтения или удаления browser records.
456
495
 
457
496
  Storage errors и некорректный JSON подавляются. При переполнении sessionStorage текущее in-memory состояние обновится, но запись на диск может не сохраниться.
458
497
 
459
- Модуль загружает drafts при первом импорте. Это модульное состояние, общее для всех consumers одного bundle.
498
+ Импорт не загружает drafts. В обычном режиме они читаются лениво при первом обращении к draft API, после чего модульное состояние разделяют consumers одного bundle. В memory-режиме чтения browser drafts нет.
460
499
 
461
500
  ## Durable IndexedDB queue
462
501
 
@@ -708,7 +747,7 @@ Foundation удаляет известные secret-like keys и email, но н
708
747
 
709
748
  ### Мутировать результат `getErrorReportDrafts()`
710
749
 
711
- Используйте `updateErrorReportDraft`; прямое изменение обходит сохранение в sessionStorage.
750
+ Используйте `updateErrorReportDraft`; прямое изменение обходит валидацию lifecycle и выбранное хранилище drafts.
712
751
 
713
752
  ### Передавать `UseQueryResult` вместо cache Query
714
753
 
@@ -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