UNPKG

@loaders.gl/zip

Version:

Zip Archive Loader

4 lines 99 kB
{ "version": 3, "sources": ["../src/index.ts", "../src/zip-loader.ts", "../src/zip-writer.ts", "../src/lib/tar/utils.ts", "../src/lib/tar/header.ts", "../src/lib/tar/tar.ts", "../src/tar-builder.ts", "../src/parse-zip/cd-file-header.ts", "../src/parse-zip/end-of-central-directory.ts", "../src/parse-zip/readable-file-utils.ts", "../src/parse-zip/search-from-the-end.ts", "../src/parse-zip/zip64-info-generation.ts", "../src/parse-zip/local-file-header.ts", "../src/parse-zip/zip-composition.ts", "../src/filesystems/zip-filesystem.ts", "../src/filesystems/IndexedArchive.ts", "../src/hash-file-utility.ts"], "sourcesContent": ["// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nexport {ZipLoader} from './zip-loader';\nexport {ZipWriter} from './zip-writer';\nexport {TarBuilder} from './tar-builder';\n\nexport {\n parseZipCDFileHeader,\n makeZipCDHeaderIterator,\n signature as CD_HEADER_SIGNATURE,\n generateCDHeader\n} from './parse-zip/cd-file-header';\nexport {\n parseZipLocalFileHeader,\n signature as localHeaderSignature,\n generateLocalHeader\n} from './parse-zip/local-file-header';\nexport {parseEoCDRecord} from './parse-zip/end-of-central-directory';\nexport {searchFromTheEnd} from './parse-zip/search-from-the-end';\nexport {\n readRange,\n getReadableFileSize,\n DataViewReadableFile\n} from './parse-zip/readable-file-utils';\nexport {addOneFile, createZip} from './parse-zip/zip-composition';\n\n// export type {HashElement} from './hash-file-utility';\nexport {IndexedArchive} from './filesystems/IndexedArchive';\nexport {parseHashTable, makeHashTableFromZipHeaders, composeHashFile} from './hash-file-utility';\n\nexport {ZipFileSystem, ZIP_COMPRESSION_HANDLERS} from './filesystems/zip-filesystem';\nexport type {CompressionHandler} from './filesystems/zip-filesystem';\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport type {LoaderWithParser, LoaderOptions} from '@loaders.gl/loader-utils';\nimport JSZip from 'jszip';\n\n// __VERSION__ is injected by babel-plugin-version-inline\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';\n\ntype FileMap = Record<string, ArrayBuffer>;\n\nexport const ZipLoader = {\n dataType: null as unknown as FileMap,\n batchType: null as unknown as never,\n\n id: 'zip',\n module: 'zip',\n name: 'Zip Archive',\n version: VERSION,\n extensions: ['zip'],\n mimeTypes: ['application/zip'],\n category: 'archive',\n tests: ['PK'],\n options: {},\n parse: parseZipAsync\n} as const satisfies LoaderWithParser<FileMap, never, LoaderOptions>;\n\n// TODO - Could return a map of promises, perhaps as an option...\nasync function parseZipAsync(data: any, options = {}): Promise<FileMap> {\n const promises: Promise<any>[] = [];\n const fileMap: Record<string, ArrayBuffer> = {};\n\n try {\n const jsZip = new JSZip();\n\n const zip = await jsZip.loadAsync(data, options);\n\n // start to load each file in this zip\n zip.forEach((relativePath, zipEntry) => {\n if (zipEntry.dir) {\n return;\n }\n\n const subFilename = zipEntry.name;\n\n const promise = loadZipEntry(jsZip, subFilename, options).then((arrayBufferOrError) => {\n fileMap[relativePath] = arrayBufferOrError;\n });\n\n // Ensure Promise.all doesn't ignore rejected promises.\n promises.push(promise);\n });\n\n await Promise.all(promises);\n return fileMap;\n } catch (error) {\n // @ts-ignore\n options.log.error(`Unable to read zip archive: ${error}`);\n throw error;\n }\n}\n\nasync function loadZipEntry(jsZip: any, subFilename: string, options: any = {}) {\n // jszip supports both arraybuffer and text, the main loaders.gl types\n // https://stuk.github.io/jszip/documentation/api_zipobject/async.html\n try {\n const arrayBuffer = await jsZip.file(subFilename).async(options.dataType || 'arraybuffer');\n return arrayBuffer;\n } catch (error) {\n options.log.error(`Unable to read ${subFilename} from zip archive: ${error}`);\n // Store error in place of data in map\n return error;\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport type {WriterWithEncoder, WriterOptions} from '@loaders.gl/loader-utils';\nimport JSZip, {JSZipFileOptions, JSZipGeneratorOptions} from 'jszip';\n\n// @ts-ignore TS2304: Cannot find name '__VERSION__'.\nconst VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'latest';\n\nexport type ZipWriterOptions = WriterOptions & {\n zip?: {\n onUpdate?: (metadata: {percent: number}) => void;\n /** When enabled, parent directory entries are created for nested file keys. */\n createFolders?: boolean;\n };\n /** Passthrough options to jszip. Note that jszip maybe replaced in future versions of loaders.gl */\n jszip?: JSZipGeneratorOptions & JSZipFileOptions;\n};\n\n/**\n * Zip exporter\n */\nexport const ZipWriter = {\n name: 'Zip Archive',\n id: 'zip',\n module: 'zip',\n version: VERSION,\n extensions: ['zip'],\n category: 'archive',\n mimeTypes: ['application/zip'],\n options: {\n zip: {\n onUpdate: () => {},\n createFolders: false\n },\n jszip: {}\n },\n encode: encodeZipAsync\n} as const satisfies WriterWithEncoder<Record<string, ArrayBuffer>, never, ZipWriterOptions>;\n\nasync function encodeZipAsync(\n fileMap: Record<string, ArrayBuffer>,\n options: ZipWriterOptions = {}\n): Promise<ArrayBuffer> {\n const jsZip = new JSZip();\n const directoryEntries = new Set<string>();\n const zipOptions = {...ZipWriter.options.zip, ...options?.zip};\n const jszipOptions = {...ZipWriter.options?.jszip, ...options.jszip};\n const jszipFileOptions: JSZipFileOptions = {\n createFolders: false,\n ...jszipOptions\n };\n\n // add files to the zip\n for (const subFileName in fileMap) {\n const subFileData = fileMap[subFileName];\n const isDirectoryEntry = subFileName.endsWith('/');\n\n if (isDirectoryEntry || zipOptions.createFolders) {\n addParentDirectoryEntries(jsZip, subFileName, options, directoryEntries);\n }\n\n // jszip supports both arraybuffer and string data (the main loaders.gl types)\n // https://stuk.github.io/jszip/documentation/api_zipobject/async.html\n if (isDirectoryEntry) {\n jsZip.file(subFileName, null, {...jszipFileOptions, dir: true});\n } else {\n jsZip.file(subFileName, subFileData, jszipFileOptions);\n }\n }\n\n const jszipGeneratorOptions: JSZipGeneratorOptions = jszipOptions;\n\n try {\n return await jsZip.generateAsync(\n {...jszipGeneratorOptions, type: 'arraybuffer'}, // generate an arraybuffer\n zipOptions.onUpdate\n );\n } catch (error) {\n options.core?.log?.error(`Unable to encode zip archive: ${error}`);\n throw error;\n }\n}\n\nfunction addParentDirectoryEntries(\n jsZip: JSZip,\n subFileName: string,\n options: ZipWriterOptions,\n directoryEntries: Set<string>\n): void {\n const subPathParts = subFileName.split('/').filter((part) => part.length > 0);\n const subPathPartCount = subFileName.endsWith('/')\n ? subPathParts.length\n : subPathParts.length - 1;\n\n let parentDirectoryPath = '';\n\n for (let index = 0; index < subPathPartCount; index++) {\n parentDirectoryPath = `${parentDirectoryPath}${subPathParts[index]}/`;\n\n if (directoryEntries.has(parentDirectoryPath)) {\n continue;\n }\n\n jsZip.file(parentDirectoryPath, null, {\n createFolders: false,\n ...options?.jszip,\n dir: true\n });\n directoryEntries.add(parentDirectoryPath);\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// This file is derived from the tar-js code base under MIT license\n// See https://github.com/beatgammit/tar-js/blob/master/LICENSE\n/*\n * tar-js\n * MIT (c) 2011 T. Jameson Little\n */\n/**\n * Returns the memory area specified by length\n * @param length\n * @returns {Uint8Array}\n */\nexport function clean(length: number): Uint8Array {\n let i: number;\n const buffer = new Uint8Array(length);\n for (i = 0; i < length; i += 1) {\n buffer[i] = 0;\n }\n return buffer;\n}\n/**\n * Converting data to a string\n * @param num\n * @param bytes\n * @param base\n * @returns string\n */\nexport function pad(num: number, bytes: number, base?: number): string {\n const numStr = num.toString(base || 8);\n return '000000000000'.substr(numStr.length + 12 - bytes) + numStr;\n}\n/**\n * Converting input to binary data\n * @param input\n * @param out\n * @param offset\n * @returns {Uint8Array}\n */\nexport function stringToUint8(input: string, out?: Uint8Array, offset?: number): Uint8Array {\n let i: number;\n let length: number;\n\n out = out || clean(input.length);\n\n offset = offset || 0;\n for (i = 0, length = input.length; i < length; i += 1) {\n out[offset] = input.charCodeAt(i);\n offset += 1;\n }\n\n return out;\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// This file is derived from the tar-js code base under MIT license\n// See https://github.com/beatgammit/tar-js/blob/master/LICENSE\n/*\n * tar-js\n * MIT (c) 2011 T. Jameson Little\n */\n/* eslint-disable */\nimport * as utils from './utils';\nimport type {TarStructure, TarData} from './types';\n/*\nstruct posix_header { // byte offset\n\tchar name[100]; // 0\n\tchar mode[8]; // 100\n\tchar uid[8]; // 108\n\tchar gid[8]; // 116\n\tchar size[12]; // 124\n\tchar mtime[12]; // 136\n\tchar chksum[8]; // 148\n\tchar typeflag; // 156\n\tchar linkname[100]; // 157\n\tchar magic[6]; // 257\n\tchar version[2]; // 263\n\tchar uname[32]; // 265\n\tchar gname[32]; // 297\n\tchar devmajor[8]; // 329\n\tchar devminor[8]; // 337\n\tchar prefix[155]; // 345\n // 500\n};\n*/\n\nconst structure: TarStructure = {\n fileName: 100,\n fileMode: 8,\n uid: 8,\n gid: 8,\n fileSize: 12,\n mtime: 12,\n checksum: 8,\n type: 1,\n linkName: 100,\n ustar: 8,\n owner: 32,\n group: 32,\n majorNumber: 8,\n minorNumber: 8,\n filenamePrefix: 155,\n padding: 12\n};\n/**\n * Getting the header\n * @param data\n * @param [cb]\n * @returns {Uint8Array} | Array\n */\nexport function format(data: TarData, cb?: any): [string, number][] | Uint8Array {\n const buffer = utils.clean(512);\n let offset = 0;\n\n Object.entries(structure).forEach(([field, length]) => {\n const str = data[field] || '';\n let i: number;\n let fieldLength: number;\n\n for (i = 0, fieldLength = str.length; i < fieldLength; i += 1) {\n buffer[offset] = str.charCodeAt(i);\n offset += 1;\n }\n\n // space it out with nulls\n offset += length - i;\n });\n\n if (typeof cb === 'function') {\n return cb(buffer, offset);\n }\n return buffer;\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\n// This file is derived from the tar-js code base under MIT license\n// See https://github.com/beatgammit/tar-js/blob/master/LICENSE\n/*\n * tar-js\n * MIT (c) 2011 T. Jameson Little\n */\n\nimport {clean, pad, stringToUint8} from './utils';\nimport {format} from './header';\nimport type {TarBlocks, TarOptions, TarChunks, TarChunk} from './types';\n\nlet blockSize: number;\nlet headerLength: number;\nlet inputLength: number;\n\nconst recordSize = 512;\n\nclass Tar {\n written: number;\n out: Uint8Array;\n blocks: TarBlocks = [];\n length: number;\n\n /**\n * @param [recordsPerBlock]\n */\n constructor(recordsPerBlock: number | undefined) {\n this.written = 0;\n blockSize = (recordsPerBlock || 20) * recordSize;\n this.out = clean(blockSize);\n\n this.blocks = [];\n this.length = 0;\n this.save = this.save.bind(this);\n this.clear = this.clear.bind(this);\n this.append = this.append.bind(this);\n }\n\n /**\n * Append a file to the tar archive\n * @param filepath\n * @param input\n * @param [opts]\n */\n // eslint-disable-next-line complexity\n append(filepath: string, input: string | Uint8Array, opts?: TarOptions | undefined) {\n let checksum: string | any;\n\n if (typeof input === 'string') {\n input = stringToUint8(input);\n } else if (input.constructor && input.constructor !== Uint8Array.prototype.constructor) {\n // @ts-ignore\n const errorInputMatch = /function\\s*([$A-Za-z_][0-9A-Za-z_]*)\\s*\\(/.exec(\n input.constructor.toString()\n );\n const errorInput = errorInputMatch && errorInputMatch[1];\n const errorMessage = `Invalid input type. You gave me: ${errorInput}`;\n throw errorMessage;\n }\n\n opts = opts || {};\n\n const mode = opts.mode || parseInt('777', 8) & 0xfff;\n const mtime = opts.mtime || Math.floor(Number(new Date()) / 1000);\n const uid = opts.uid || 0;\n const gid = opts.gid || 0;\n\n const data: Record<string, string> = {\n fileName: filepath,\n fileMode: pad(mode, 7),\n uid: pad(uid, 7),\n gid: pad(gid, 7),\n fileSize: pad(input.length, 11),\n mtime: pad(mtime, 11),\n checksum: ' ',\n // 0 = just a file\n type: '0',\n ustar: 'ustar ',\n owner: opts.owner || '',\n group: opts.group || ''\n };\n\n // calculate the checksum\n checksum = 0;\n Object.keys(data).forEach((key) => {\n let i: number;\n const value = data[key];\n let length: number;\n\n for (i = 0, length = value.length; i < length; i += 1) {\n checksum += value.charCodeAt(i);\n }\n });\n\n data.checksum = `${pad(checksum, 6)}\\u0000 `;\n\n const headerArr = format(data);\n\n headerLength = Math.ceil(headerArr.length / recordSize) * recordSize;\n inputLength = Math.ceil(input.length / recordSize) * recordSize;\n\n this.blocks.push({\n header: headerArr,\n input,\n headerLength,\n inputLength\n });\n }\n /**\n * Compiling data to a Blob object\n * @returns {Blob}\n */\n save(): Blob {\n const buffers: any = [];\n const chunks = new Array<TarChunks>();\n let length = 0;\n const max = Math.pow(2, 20);\n\n let chunk = new Array<TarChunk>();\n this.blocks.forEach((b: any = []) => {\n if (length + b.headerLength + b.inputLength > max) {\n chunks.push({blocks: chunk, length});\n chunk = [];\n length = 0;\n }\n chunk.push(b);\n length += b.headerLength + b.inputLength;\n });\n chunks.push({blocks: chunk, length});\n\n chunks.forEach((c: any = []) => {\n const buffer = new Uint8Array(c.length);\n let written = 0;\n c.blocks.forEach((b: any = []) => {\n buffer.set(b.header, written);\n written += b.headerLength;\n buffer.set(b.input, written);\n written += b.inputLength;\n });\n buffers.push(buffer);\n });\n\n buffers.push(new Uint8Array(2 * recordSize));\n\n return new Blob(buffers, {type: 'octet/stream'});\n }\n /**\n * Clear the data by its blocksize\n */\n clear() {\n this.written = 0;\n this.out = clean(blockSize);\n }\n}\n\nexport default Tar;\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport Tar from './lib/tar/tar';\n\nconst TAR_BUILDER_OPTIONS = {\n recordsPerBlock: 20\n};\n\ntype TarBuilderOptions = {\n recordsPerBlock?: number;\n};\n\n/**\n * Build a tar file by adding files\n */\nexport class TarBuilder {\n static get properties() {\n return {\n id: 'tar',\n name: 'TAR',\n extensions: ['tar'],\n mimeTypes: ['application/x-tar'],\n builder: TarBuilder,\n options: TAR_BUILDER_OPTIONS\n };\n }\n\n options: TarBuilderOptions;\n tape: Tar;\n count: number = 0;\n\n constructor(options?: Partial<TarBuilderOptions>) {\n this.options = {...TAR_BUILDER_OPTIONS, ...options};\n this.tape = new Tar(this.options.recordsPerBlock);\n }\n /** Adds a file to the archive. */\n addFile(filename: string, buffer: ArrayBuffer) {\n this.tape.append(filename, new Uint8Array(buffer));\n this.count++;\n }\n\n async build(): Promise<ArrayBuffer> {\n return new Response(this.tape.save()).arrayBuffer();\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {compareArrayBuffers, concatenateArrayBuffers} from '@loaders.gl/loader-utils';\nimport type {ReadableFile} from '@loaders.gl/loader-utils';\nimport {parseEoCDRecord} from './end-of-central-directory';\nimport {ZipSignature} from './search-from-the-end';\nimport {createZip64Info, setFieldToNumber} from './zip64-info-generation';\nimport {\n DataViewReadableFile,\n getReadableFileSize,\n readDataView,\n readRange\n} from './readable-file-utils';\n\n/**\n * zip central directory file header info\n * according to https://en.wikipedia.org/wiki/ZIP_(file_format)\n */\nexport type ZipCDFileHeader = {\n /** Compressed size */\n compressedSize: bigint;\n /** Uncompressed size */\n uncompressedSize: bigint;\n /** Extra field size */\n extraFieldLength: number;\n /** File name length */\n fileNameLength: number;\n /** File name */\n fileName: string;\n /** Extra field offset */\n extraOffset: bigint;\n /** Relative offset of local file header */\n localHeaderOffset: bigint;\n};\n\n/**\n * Data that might be in Zip64 notation inside extra data\n */\ntype Zip64Data = {\n /** Uncompressed size */\n uncompressedSize: bigint;\n /** Compressed size */\n compressedSize: bigint;\n /** Relative offset of local file header */\n localHeaderOffset: bigint;\n /** Start disk */\n startDisk: bigint;\n};\n\n// offsets accroding to https://en.wikipedia.org/wiki/ZIP_(file_format)\nconst CD_COMPRESSED_SIZE_OFFSET = 20;\nconst CD_UNCOMPRESSED_SIZE_OFFSET = 24;\nconst CD_FILE_NAME_LENGTH_OFFSET = 28;\nconst CD_EXTRA_FIELD_LENGTH_OFFSET = 30;\nconst CD_START_DISK_OFFSET = 32;\nconst CD_LOCAL_HEADER_OFFSET_OFFSET = 42;\nconst CD_FILE_NAME_OFFSET = 46n;\n\nexport const signature: ZipSignature = new Uint8Array([0x50, 0x4b, 0x01, 0x02]);\n\n/**\n * Parses central directory file header of zip file\n * @param headerOffset - offset in the archive where header starts\n * @param buffer - buffer containing whole array\n * @returns Info from the header\n */\nexport const parseZipCDFileHeader = async (\n headerOffset: bigint,\n file: ReadableFile\n): Promise<ZipCDFileHeader | null> => {\n const fileLength = await getReadableFileSize(file);\n if (headerOffset >= fileLength) {\n return null;\n }\n const mainHeader = await readDataView(file, headerOffset, headerOffset + CD_FILE_NAME_OFFSET);\n\n const magicBytes = mainHeader.buffer.slice(0, 4);\n if (!compareArrayBuffers(magicBytes, signature.buffer)) {\n return null;\n }\n\n const compressedSize = BigInt(mainHeader.getUint32(CD_COMPRESSED_SIZE_OFFSET, true));\n const uncompressedSize = BigInt(mainHeader.getUint32(CD_UNCOMPRESSED_SIZE_OFFSET, true));\n const extraFieldLength = mainHeader.getUint16(CD_EXTRA_FIELD_LENGTH_OFFSET, true);\n const startDisk = BigInt(mainHeader.getUint16(CD_START_DISK_OFFSET, true));\n const fileNameLength = mainHeader.getUint16(CD_FILE_NAME_LENGTH_OFFSET, true);\n\n const additionalHeader = await readRange(\n file,\n headerOffset + CD_FILE_NAME_OFFSET,\n headerOffset + CD_FILE_NAME_OFFSET + BigInt(fileNameLength + extraFieldLength)\n );\n\n const filenameBytes = additionalHeader.slice(0, fileNameLength);\n const fileName = new TextDecoder().decode(filenameBytes);\n\n const extraOffset = headerOffset + CD_FILE_NAME_OFFSET + BigInt(fileNameLength);\n const oldFormatOffset = mainHeader.getUint32(CD_LOCAL_HEADER_OFFSET_OFFSET, true);\n\n const localHeaderOffset = BigInt(oldFormatOffset);\n const extraField = new DataView(\n additionalHeader.slice(fileNameLength, additionalHeader.byteLength)\n );\n // looking for info that might be also be in zip64 extra field\n\n const zip64data: Zip64Data = {\n uncompressedSize,\n compressedSize,\n localHeaderOffset,\n startDisk\n };\n\n const res = findZip64DataInExtra(zip64data, extraField);\n\n return {\n ...zip64data,\n ...res,\n extraFieldLength,\n fileNameLength,\n fileName,\n extraOffset\n };\n};\n\n/**\n * Create iterator over files of zip archive\n * @param fileProvider - readable file that provides random access to the file\n */\nexport async function* makeZipCDHeaderIterator(\n fileProvider: ReadableFile\n): AsyncIterable<ZipCDFileHeader> {\n const {cdStartOffset, cdByteSize} = await parseEoCDRecord(fileProvider);\n const centralDirectory = new DataViewReadableFile(\n new DataView(await readRange(fileProvider, cdStartOffset, cdStartOffset + cdByteSize))\n );\n let cdHeader = await parseZipCDFileHeader(0n, centralDirectory);\n while (cdHeader) {\n yield cdHeader;\n cdHeader = await parseZipCDFileHeader(\n cdHeader.extraOffset + BigInt(cdHeader.extraFieldLength),\n centralDirectory\n );\n }\n}\n/**\n * returns the number written in the provided bytes\n * @param bytes two bytes containing the number\n * @returns the number written in the provided bytes\n */\nconst getUint16 = (...bytes: [number, number]) => {\n return bytes[0] + bytes[1] * 16;\n};\n\n/**\n * reads all nesessary data from zip64 record in the extra data\n * @param zip64data values that might be in zip64 record\n * @param extraField full extra data\n * @returns data read from zip64\n */\n\nconst findZip64DataInExtra = (zip64data: Zip64Data, extraField: DataView): Partial<Zip64Data> => {\n const zip64dataList = findExpectedData(zip64data);\n\n const zip64DataRes: Partial<Zip64Data> = {};\n if (zip64dataList.length > 0) {\n // total length of data in zip64 notation in bytes\n const zip64chunkSize = zip64dataList.reduce((sum, curr) => sum + curr.length, 0);\n // we're looking for the zip64 nontation header (0x0001)\n // and a size field with a correct value next to it\n const offsetInExtraData = new Uint8Array(extraField.buffer).findIndex(\n (_val, i, arr) =>\n getUint16(arr[i], arr[i + 1]) === 0x0001 &&\n getUint16(arr[i + 2], arr[i + 3]) === zip64chunkSize\n );\n // then we read all the nesessary fields from the zip64 data\n let bytesRead = 0;\n for (const note of zip64dataList) {\n const offset = bytesRead;\n zip64DataRes[note.name] = extraField.getBigUint64(offsetInExtraData + 4 + offset, true);\n bytesRead = offset + note.length;\n }\n }\n\n return zip64DataRes;\n};\n\n/**\n * frind data that's expected to be in zip64\n * @param zip64data values that might be in zip64 record\n * @returns zip64 data description\n */\n\nconst findExpectedData = (zip64data: Zip64Data): {length: number; name: string}[] => {\n // We define fields that should be in zip64 data\n const zip64dataList: {length: number; name: string}[] = [];\n if (zip64data.uncompressedSize === BigInt(0xffffffff)) {\n zip64dataList.push({name: 'uncompressedSize', length: 8});\n }\n if (zip64data.compressedSize === BigInt(0xffffffff)) {\n zip64dataList.push({name: 'compressedSize', length: 8});\n }\n if (zip64data.localHeaderOffset === BigInt(0xffffffff)) {\n zip64dataList.push({name: 'localHeaderOffset', length: 8});\n }\n if (zip64data.startDisk === BigInt(0xffffffff)) {\n zip64dataList.push({name: 'startDisk', length: 4});\n }\n\n return zip64dataList;\n};\n\n/** info that can be placed into cd header */\ntype GenerateCDOptions = {\n /** CRC-32 of uncompressed data */\n crc32: number;\n /** File name */\n fileName: string;\n /** File size */\n length: number;\n /** Relative offset of local file header */\n offset: bigint;\n};\n\n/**\n * generates cd header for the file\n * @param options info that can be placed into cd header\n * @returns buffer with header\n */\nexport function generateCDHeader(options: GenerateCDOptions): ArrayBuffer {\n const optionsToUse = {\n ...options,\n fnlength: options.fileName.length,\n extraLength: 0\n };\n\n let zip64header: ArrayBuffer = new ArrayBuffer(0);\n\n const optionsToZip64: any = {};\n if (optionsToUse.offset >= 0xffffffff) {\n optionsToZip64.offset = optionsToUse.offset;\n optionsToUse.offset = BigInt(0xffffffff);\n }\n if (optionsToUse.length >= 0xffffffff) {\n optionsToZip64.size = optionsToUse.length;\n optionsToUse.length = 0xffffffff;\n }\n\n if (Object.keys(optionsToZip64).length) {\n zip64header = createZip64Info(optionsToZip64);\n optionsToUse.extraLength = zip64header.byteLength;\n }\n const header = new DataView(new ArrayBuffer(Number(CD_FILE_NAME_OFFSET)));\n\n for (const field of ZIP_HEADER_FIELDS) {\n setFieldToNumber(\n header,\n field.size,\n field.offset,\n optionsToUse[field.name ?? ''] ?? field.default ?? 0\n );\n }\n\n const encodedName = new TextEncoder().encode(optionsToUse.fileName);\n\n const resHeader = concatenateArrayBuffers(header.buffer, encodedName, zip64header);\n\n return resHeader;\n}\n\n/** Fields map */\nconst ZIP_HEADER_FIELDS = [\n // Central directory file header signature = 0x02014b50\n {\n offset: 0,\n size: 4,\n default: new DataView(signature.buffer).getUint32(0, true)\n },\n\n // Version made by\n {\n offset: 4,\n size: 2,\n default: 45\n },\n\n // Version needed to extract (minimum)\n {\n offset: 6,\n size: 2,\n default: 45\n },\n\n // General purpose bit flag\n {\n offset: 8,\n size: 2,\n default: 0\n },\n\n // Compression method\n {\n offset: 10,\n size: 2,\n default: 0\n },\n\n // File last modification time\n {\n offset: 12,\n size: 2,\n default: 0\n },\n\n // File last modification date\n {\n offset: 14,\n size: 2,\n default: 0\n },\n\n // CRC-32 of uncompressed data\n {\n offset: 16,\n size: 4,\n name: 'crc32'\n },\n\n // Compressed size (or 0xffffffff for ZIP64)\n {\n offset: 20,\n size: 4,\n name: 'length'\n },\n\n // Uncompressed size (or 0xffffffff for ZIP64)\n {\n offset: 24,\n size: 4,\n name: 'length'\n },\n\n // File name length (n)\n {\n offset: 28,\n size: 2,\n name: 'fnlength'\n },\n\n // Extra field length (m)\n {\n offset: 30,\n size: 2,\n default: 0,\n name: 'extraLength'\n },\n\n // File comment length (k)\n {\n offset: 32,\n size: 2,\n default: 0\n },\n\n // Disk number where file starts (or 0xffff for ZIP64)\n {\n offset: 34,\n size: 2,\n default: 0\n },\n\n // Internal file attributes\n {\n offset: 36,\n size: 2,\n default: 0\n },\n\n // External file attributes\n {\n offset: 38,\n size: 4,\n default: 0\n },\n\n // Relative offset of local file header\n {\n offset: 42,\n size: 4,\n name: 'offset'\n }\n];\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {compareArrayBuffers, concatenateArrayBuffers} from '@loaders.gl/loader-utils';\nimport type {ReadableFile} from '@loaders.gl/loader-utils';\nimport {ZipSignature, searchFromTheEnd} from './search-from-the-end';\nimport {readBigUint64, readUint16, readUint32, readRange} from './readable-file-utils';\nimport {setFieldToNumber} from './zip64-info-generation';\n\n/**\n * End of central directory info\n * according to https://en.wikipedia.org/wiki/ZIP_(file_format)\n */\nexport type ZipEoCDRecord = {\n /** Relative offset of cd start */\n cdStartOffset: bigint;\n /** Total number of central directory records */\n cdRecordsNumber: bigint;\n /** Size of central directory */\n cdByteSize: bigint;\n offsets: ZipEoCDRecordOffsets;\n};\n\n/**\n * End of central directory offsets\n * according to https://en.wikipedia.org/wiki/ZIP_(file_format)\n */\nexport type ZipEoCDRecordOffsets = {\n zipEoCDOffset: bigint;\n\n zip64EoCDOffset?: bigint;\n zip64EoCDLocatorOffset?: bigint;\n};\n\n/**\n * Data to generate End of central directory record\n * according to https://en.wikipedia.org/wiki/ZIP_(file_format)\n */\nexport type ZipEoCDGenerationOptions = {\n recordsNumber: number;\n cdSize: number;\n cdOffset: bigint;\n eoCDStart: bigint;\n};\n\nconst eoCDSignature: ZipSignature = new Uint8Array([0x50, 0x4b, 0x05, 0x06]);\nconst zip64EoCDLocatorSignature = new Uint8Array([0x50, 0x4b, 0x06, 0x07]);\nconst zip64EoCDSignature = new Uint8Array([0x50, 0x4b, 0x06, 0x06]);\n\n// offsets accroding to https://en.wikipedia.org/wiki/ZIP_(file_format)\nconst CD_RECORDS_NUMBER_OFFSET = 8n;\nconst CD_RECORDS_NUMBER_ON_DISC_OFFSET = 10n;\nconst CD_CD_BYTE_SIZE_OFFSET = 12n;\nconst CD_START_OFFSET_OFFSET = 16n;\nconst CD_COMMENT_OFFSET = 22n;\nconst ZIP64_EOCD_START_OFFSET_OFFSET = 8n;\nconst ZIP64_CD_RECORDS_NUMBER_OFFSET = 24n;\nconst ZIP64_CD_RECORDS_NUMBER_ON_DISC_OFFSET = 32n;\nconst ZIP64_CD_CD_BYTE_SIZE_OFFSET = 40n;\nconst ZIP64_CD_START_OFFSET_OFFSET = 48n;\nconst ZIP64_COMMENT_OFFSET = 56n;\n\n/**\n * Parses end of central directory record of zip file\n * @param file - ReadableFile instance\n * @returns Info from the header\n */\nexport const parseEoCDRecord = async (file: ReadableFile): Promise<ZipEoCDRecord> => {\n const zipEoCDOffset = await searchFromTheEnd(file, eoCDSignature);\n\n let cdRecordsNumber = BigInt(await readUint16(file, zipEoCDOffset + CD_RECORDS_NUMBER_OFFSET));\n let cdByteSize = BigInt(await readUint32(file, zipEoCDOffset + CD_CD_BYTE_SIZE_OFFSET));\n let cdStartOffset = BigInt(await readUint32(file, zipEoCDOffset + CD_START_OFFSET_OFFSET));\n\n let zip64EoCDLocatorOffset = zipEoCDOffset - 20n;\n let zip64EoCDOffset = 0n;\n\n const magicBytes = await readRange(file, zip64EoCDLocatorOffset, zip64EoCDLocatorOffset + 4n);\n if (compareArrayBuffers(magicBytes, zip64EoCDLocatorSignature.buffer)) {\n zip64EoCDOffset = await readBigUint64(\n file,\n zip64EoCDLocatorOffset + ZIP64_EOCD_START_OFFSET_OFFSET\n );\n\n const endOfCDMagicBytes = await readRange(file, zip64EoCDOffset, zip64EoCDOffset + 4n);\n if (!compareArrayBuffers(endOfCDMagicBytes, zip64EoCDSignature.buffer)) {\n throw new Error('zip64 EoCD not found');\n }\n\n cdRecordsNumber = await readBigUint64(file, zip64EoCDOffset + ZIP64_CD_RECORDS_NUMBER_OFFSET);\n cdByteSize = await readBigUint64(file, zip64EoCDOffset + ZIP64_CD_CD_BYTE_SIZE_OFFSET);\n cdStartOffset = await readBigUint64(file, zip64EoCDOffset + ZIP64_CD_START_OFFSET_OFFSET);\n } else {\n zip64EoCDLocatorOffset = 0n;\n }\n\n return {\n cdRecordsNumber,\n cdStartOffset,\n cdByteSize,\n offsets: {\n zip64EoCDOffset,\n zip64EoCDLocatorOffset,\n zipEoCDOffset\n }\n };\n};\n\n/**\n * updates EoCD record to add more files to the archieve\n * @param eocdBody buffer containing header\n * @param oldEoCDOffsets info read from EoCD record befor updating\n * @param newCDStartOffset CD start offset to be updated\n * @param eocdStartOffset EoCD start offset to be updated\n * @returns new EoCD header\n */\nexport function updateEoCD(\n eocdBody: ArrayBuffer,\n oldEoCDOffsets: ZipEoCDRecordOffsets,\n newCDStartOffset: bigint,\n eocdStartOffset: bigint,\n newCDRecordsNumber: bigint\n): Uint8Array {\n const eocd = new DataView(eocdBody);\n\n const classicEoCDOffset = oldEoCDOffsets.zip64EoCDOffset\n ? oldEoCDOffsets.zipEoCDOffset - oldEoCDOffsets.zip64EoCDOffset\n : 0n;\n\n // updating classic EoCD record with new CD records number in general and on disc\n if (Number(newCDRecordsNumber) <= 0xffff) {\n setFieldToNumber(eocd, 2, classicEoCDOffset + CD_RECORDS_NUMBER_OFFSET, newCDRecordsNumber);\n setFieldToNumber(\n eocd,\n 2,\n classicEoCDOffset + CD_RECORDS_NUMBER_ON_DISC_OFFSET,\n newCDRecordsNumber\n );\n }\n\n // updating zip64 EoCD record with new size of CD\n if (eocdStartOffset - newCDStartOffset <= 0xffffffff) {\n setFieldToNumber(\n eocd,\n 4,\n classicEoCDOffset + CD_CD_BYTE_SIZE_OFFSET,\n eocdStartOffset - newCDStartOffset\n );\n }\n\n // updating classic EoCD record with new CD start offset\n if (newCDStartOffset < 0xffffffff) {\n setFieldToNumber(eocd, 4, classicEoCDOffset + CD_START_OFFSET_OFFSET, newCDStartOffset);\n }\n\n // updating zip64 EoCD locator and record with new EoCD record start offset and cd records number\n if (oldEoCDOffsets.zip64EoCDLocatorOffset && oldEoCDOffsets.zip64EoCDOffset) {\n // updating zip64 EoCD locator with new EoCD record start offset\n const locatorOffset = oldEoCDOffsets.zip64EoCDLocatorOffset - oldEoCDOffsets.zip64EoCDOffset;\n setFieldToNumber(eocd, 8, locatorOffset + ZIP64_EOCD_START_OFFSET_OFFSET, eocdStartOffset);\n\n // updating zip64 EoCD record with new cd start offset\n setFieldToNumber(eocd, 8, ZIP64_CD_START_OFFSET_OFFSET, newCDStartOffset);\n\n // updating zip64 EoCD record with new cd records number\n setFieldToNumber(eocd, 8, ZIP64_CD_RECORDS_NUMBER_OFFSET, newCDRecordsNumber);\n setFieldToNumber(eocd, 8, ZIP64_CD_RECORDS_NUMBER_ON_DISC_OFFSET, newCDRecordsNumber);\n\n // updating zip64 EoCD record with new size of CD\n setFieldToNumber(eocd, 8, ZIP64_CD_CD_BYTE_SIZE_OFFSET, eocdStartOffset - newCDStartOffset);\n }\n\n return new Uint8Array(eocd.buffer);\n}\n\n/**\n * generates EoCD record\n * @param options data to generate EoCD record\n * @returns ArrayBuffer with EoCD record\n */\nexport function generateEoCD(options: ZipEoCDGenerationOptions): ArrayBuffer {\n const header = new DataView(new ArrayBuffer(Number(CD_COMMENT_OFFSET)));\n\n for (const field of EOCD_FIELDS) {\n setFieldToNumber(\n header,\n field.size,\n field.offset,\n options[field.name ?? ''] ?? field.default ?? 0\n );\n }\n const locator = generateZip64InfoLocator(options);\n\n const zip64Record = generateZip64Info(options);\n\n return concatenateArrayBuffers(zip64Record, locator, header.buffer);\n}\n\n/** standart EoCD fields */\nconst EOCD_FIELDS = [\n // End of central directory signature = 0x06054b50\n {\n offset: 0,\n size: 4,\n default: new DataView(eoCDSignature.buffer).getUint32(0, true)\n },\n\n // Number of this disk (or 0xffff for ZIP64)\n {\n offset: 4,\n size: 2,\n default: 0\n },\n\n // Disk where central directory starts (or 0xffff for ZIP64)\n {\n offset: 6,\n size: 2,\n default: 0\n },\n\n // Number of central directory records on this disk (or 0xffff for ZIP64)\n {\n offset: 8,\n size: 2,\n name: 'recordsNumber'\n },\n\n // Total number of central directory records (or 0xffff for ZIP64)\n {\n offset: 10,\n size: 2,\n name: 'recordsNumber'\n },\n\n // Size of central directory (bytes) (or 0xffffffff for ZIP64)\n {\n offset: 12,\n size: 4,\n name: 'cdSize'\n },\n\n // Offset of start of central directory, relative to start of archive (or 0xffffffff for ZIP64)\n {\n offset: 16,\n size: 4,\n name: 'cdOffset'\n },\n\n // Comment length (n)\n {\n offset: 20,\n size: 2,\n default: 0\n }\n];\n\n/**\n * generates eocd zip64 record\n * @param options data to generate eocd zip64 record\n * @returns buffer with eocd zip64 record\n */\nfunction generateZip64Info(options: ZipEoCDGenerationOptions): ArrayBuffer {\n const record = new DataView(new ArrayBuffer(Number(ZIP64_COMMENT_OFFSET)));\n for (const field of ZIP64_EOCD_FIELDS) {\n setFieldToNumber(\n record,\n field.size,\n field.offset,\n options[field.name ?? ''] ?? field.default ?? 0\n );\n }\n\n return record.buffer;\n}\n\n/**\n * generates eocd zip64 record locator\n * @param options data to generate eocd zip64 record\n * @returns buffer with eocd zip64 record\n */\nfunction generateZip64InfoLocator(options: ZipEoCDGenerationOptions): ArrayBuffer {\n const locator = new DataView(new ArrayBuffer(Number(20)));\n\n for (const field of ZIP64_EOCD_LOCATOR_FIELDS) {\n setFieldToNumber(\n locator,\n field.size,\n field.offset,\n options[field.name ?? ''] ?? field.default ?? 0\n );\n }\n\n return locator.buffer;\n}\n\n/** zip64 EoCD record locater fields */\nconst ZIP64_EOCD_LOCATOR_FIELDS = [\n // zip64 end of central dir locator signature\n {\n offset: 0,\n size: 4,\n default: new DataView(zip64EoCDLocatorSignature.buffer).getUint32(0, true)\n },\n\n // number of the disk with the start of the zip64 end of\n {\n offset: 4,\n size: 4,\n default: 0\n },\n\n // start of the zip64 end of central directory\n {\n offset: 8,\n size: 8,\n name: 'eoCDStart'\n },\n\n // total number of disks\n {\n offset: 16,\n size: 4,\n default: 1\n }\n];\n\n/** zip64 EoCD recodrd fields */\nconst ZIP64_EOCD_FIELDS = [\n // End of central directory signature = 0x06064b50\n {\n offset: 0,\n size: 4,\n default: new DataView(zip64EoCDSignature.buffer).getUint32(0, true)\n },\n\n // Size of the EOCD64 minus 12\n {\n offset: 4,\n size: 8,\n default: 44\n },\n\n // Version made by\n {\n offset: 12,\n size: 2,\n default: 45\n },\n\n // Version needed to extract (minimum)\n {\n offset: 14,\n size: 2,\n default: 45\n },\n\n // Number of this disk\n {\n offset: 16,\n size: 4,\n default: 0\n },\n\n // Disk where central directory starts\n {\n offset: 20,\n size: 4,\n default: 0\n },\n\n // Number of central directory records on this disk\n {\n offset: 24,\n size: 8,\n name: 'recordsNumber'\n },\n\n // Total number of central directory records\n {\n offset: 32,\n size: 8,\n name: 'recordsNumber'\n },\n\n // Size of central directory (bytes)\n {\n offset: 40,\n size: 8,\n name: 'cdSize'\n },\n\n // Offset of start of central directory, relative to start of archive\n {\n offset: 48,\n size: 8,\n name: 'cdOffset'\n }\n];\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {copyToArrayBuffer, type ReadableFile, type Stat} from '@loaders.gl/loader-utils';\n\nfunction toBigInt(value: number | bigint): bigint {\n return typeof value === 'bigint' ? value : BigInt(value);\n}\n\nfunction toNumber(value: number | bigint): number {\n const numberValue = Number(value);\n if (!Number.isFinite(numberValue)) {\n throw new Error('Offset is out of bounds');\n }\n return numberValue;\n}\n\nfunction normalizeOffset(offset: number, size: number): number {\n if (offset < 0) {\n return Math.max(size + offset, 0);\n }\n return Math.min(offset, size);\n}\n\n/**\n * Read a byte range from a readable file.\n * @param file readable file handle\n * @param start inclusive start offset\n * @param end exclusive end offset\n * @returns requested slice\n */\nexport async function readRange(\n file: ReadableFile,\n start: number | bigint,\n end: number | bigint\n): Promise<ArrayBuffer> {\n const startOffset = toBigInt(start);\n const endOffset = toBigInt(end);\n const length = endOffset - startOffset;\n if (length < 0) {\n throw new Error('Invalid range requested');\n }\n return await file.read(startOffset, toNumber(length));\n}\n\nexport async function readDataView(\n file: ReadableFile,\n start: number | bigint,\n end: number | bigint\n): Promise<DataView> {\n const arrayBuffer = await readRange(file, start, end);\n return new DataView(arrayBuffer);\n}\n\nexport async function readUint8(file: ReadableFile, offset: number | bigint): Promise<number> {\n const dataView = await readDataView(file, offset, toBigInt(offset) + 1n);\n return dataView.getUint8(0);\n}\n\nexport async function readUint16(file: ReadableFile, offset: number | bigint): Promise<number> {\n const dataView = await readDataView(file, offset, toBigInt(offset) + 2n);\n return dataView.getUint16(0, true);\n}\n\nexport async function readUint32(file: ReadableFile, offset: number | bigint): Promise<number> {\n const dataView = await readDataView(file, offset, toBigInt(offset) + 4n);\n return dataView.getUint32(0, true);\n}\n\nexport async function readBigUint64(file: ReadableFile, offset: number | bigint): Promise<bigint> {\n const dataView = await readDataView(file, offset, toBigInt(offset) + 8n);\n return dataView.getBigUint64(0, true);\n}\n\n/**\n * Resolve the size of a readable file.\n * @param file readable file handle\n * @returns file size as bigint\n */\nexport async function getReadableFileSize(file: ReadableFile): Promise<bigint> {\n if (file.bigsize > 0n) {\n return file.bigsize;\n }\n if (file.size > 0) {\n return BigInt(file.size);\n }\n if (file.stat) {\n const stats: Stat = await file.stat();\n if (stats?.bigsize !== undefined) {\n return stats.bigsize;\n }\n if (stats?.size !== undefined) {\n return BigInt(stats.size);\n }\n }\n return 0n;\n}\n\n/**\n * Minimal readable file backed by a DataView.\n */\nexport class DataViewReadableFile implements ReadableFile {\n readonly handle: DataView;\n readonly size: number;\n readonly bigsize: bigint;\n readonly url: string;\n\n constructor(dataView: DataView, url: string = '') {\n this.handle = dataView;\n this.size = dataView.byteLength;\n this.bigsize = BigInt(dataView.byteLength);\n this.url = url;\n }\n\n async close(): Promise<void> {}\n\n async stat(): Promise<Stat> {\n return {size: this.size, bigsize: this.bigsize, isDirectory: false};\n }\n\n async read(start: number | bigint = 0, length?: number): Promise<ArrayBuffer> {\n const offset = toNumber(start);\n const end = length ? offset + length : this.size;\n const normalizedStart = normalizeOffset(offset, this.size);\n const normalizedEnd = normalizeOffset(end, this.size);\n const clampedEnd = Math.max(normalizedEnd, normalizedStart);\n const lengthToRead = clampedEnd - normalizedStart;\n if (lengthToRead <= 0) {\n return new ArrayBuffer(0);\n }\n return copyToArrayBuffer(this.handle.buffer, normalizedStart, lengthToRead);\n }\n}\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport type {ReadableFile} from '@loaders.gl/loader-utils';\nimport {getReadableFileSize, readRange} from './readable-file-utils';\n\n/** Description of zip signature type */\nexport type ZipSignature = Uint8Array;\n\nconst buffLength = 1024;\n\n/**\n * looking for the last occurrence of the provided\n * @param file\n * @param target\n * @returns\n */\nexport const searchFromTheEnd = async (\n file: ReadableFile,\n target: ZipSignature\n): Promise<bigint> => {\n const fileLength = await getReadableFileSize(file);\n const lastBytes = new Uint8Array(await readRange(file, fileLength - 3n, fileLength + 1n));\n const searchWindow = [lastBytes[3], lastBytes[2], lastBytes[1], undefined];\n\n let targetOffset = -1;\n\n // looking for the last record in the central directory\n let point = fileLength - 4n;\n do {\n const prevPoint = point;\n point -= BigInt(buffLength);\n point = point >= 0n ? point : 0n;\n const buff = new Uint8Array(await readRange(file, point, prevPoint));\n for (let i = buff.length - 1; i > -1; i--) {\n searchWindow[3] = searchWindow[2];\n searchWindow[2] = searchWindow[1];\n searchWindow[1] = searchWindow[0];\n searchWindow[0] = buff[i];\n if (searchWindow.every((val, index) => val === target[index])) {\n targetOffset = i;\n break;\n }\n }\n } while (targetOffset === -1 && point > 0n);\n\n return point + BigInt(targetOffset);\n};\n", "import {concatenateArrayBuffers} from '@loaders.gl/loader-utils';\n\nexport const signature = new Uint8Array([0x01, 0x00]);\n\n/** info that can be placed into zip64 field, doc: https://en.wikipedia.org/wiki/ZIP_(file_format)#ZIP64 */\ntype Zip64Options = {\n /** Original uncompressed file size and Size of compressed data */\n size?: number;\n /** Offset of local header record */\n offset?: number;\n};\n\n/**\n * creates zip64 extra field\n * @param options info that can be placed into zip64 field\n * @returns buffer with field\n */\nexport function createZip64Info(options: Zip64Options): ArrayBuffer {\n const optionsToUse = {\n ...options,\n zip64Length: (options.offset ? 1 : 0) * 8 + (options.size ? 1 : 0) * 16\n };\n\n const arraysToConcat: ArrayBuffer[] = [];\n\n for (const field of ZIP64_FIELDS) {\n if (!optionsToUse[field.name ?? ''] && !field.default) {\n continue; // eslint-disable-line no-continue\n }\n const newValue = new DataView(new ArrayBuffer(field.size));\n NUMBER_SETTERS[field.size](newValue, 0, optionsToUse[field.name ?? ''] ?? field.default);\n arraysToConcat.push(newValue.buffer);\n }\n\n return concatenateArrayBuffers(...arraysToConcat);\n}\n\n/**\n * Function to write values into buffer\n * @param header buffer where to write a value\n * @param offset offset of the writing start\n * @param value value to be written\n */\ntype NumberSetter = (header: DataView, offset: number, value: number | bigint) => void;\n\n/**\n * Writes values into buffer according to the bytes amount\n * @param header header where to write the data\n * @param fieldSize size of the field in bytes\n * @param fieldOffset offset of the field\n * @param value value to be written\n */\nexport function setFieldToNumber(\n header: DataView,\n fieldSize: number,\n fieldOffset: number | bigint,\n value: number | bigint\n): void {\n NUMBER_SETTERS[fieldSize](header, Number(fieldOffset), value);\n}\n\n/** functions to write values into buffer according to the bytes amount */\nconst NUMBER_SETTERS: {[key: number]: NumberSetter} = {\n 2: (header, offset, value) => {\n header.setUint16(offset, Number(value > 0xffff ? 0xffff : value), true);\n },\n 4: (header, offset, value) => {\n header.setUint32(offset, Number(value > 0xffffffff ? 0xffffffff : value), true);\n },\n 8: (header, offset, value) => {\n header.setBigUint64(offset, BigInt(value), true);\n }\n};\n\n/** zip64 info fields description, we need it as a pattern to build a zip64 info */\nconst ZIP64_FIELDS = [\n // Header ID 0x0001\n {\n size: 2,\n default: new DataView(signature.buffer).getUint16(0, true)\n },\n\n // Size of the extra field chunk (8, 16, 24 or 28)\n {\n size: 2,\n name: 'zip64Length'\n },\n\n // Original uncompressed file size\n {\n size: 8,\n name: 'size'\n },\n\n // Size of compressed data\n {\n size: 8,\n name: 'size'\n },\n\n // Offset of local header record\n {\n size: 8,\n name: 'offset'\n }\n];\n", "// loaders.gl\n// SPDX-License-Identifier: MIT\n// Copyright (c) vis.gl contributors\n\nimport {compareArrayBuffers, concatenateArrayBuffers} from '@loaders.gl/loader-utils';\nimport type {ReadableFile} from '@loaders.gl/loader-utils';\nimport {ZipSignature} from './search-from-the-end';\nimport {createZip64Info, setFieldToNumber} from './zip64-info-generation';\nimport {readDataView, readRange} from './readable-file-utils';\n\n/**\n * zip local file header info\n * according to https://en.wikipedia.org/wiki/ZIP_(file_format)\n */\nexport type ZipLocalFileHeader = {\n /** File name length */\n fileNameLength: number;\n /** File name */\n fileName: string;\n /** Extra field length */\n extraFieldLength: number;\n /** Offset of the file data */\n fileDataOffset: bigint;\n /** Compressed size */\n compressedSize: bigint;\n /** Compression method */\n compressionMethod: number;\n};\n\n// offsets accroding to https://en.wikipedia.org/wiki/ZIP_(file_format)\nconst COMPRESSION_METHOD_OFFSET = 8;\nconst COMPRESSED_SIZE_OFFSET = 18;\nconst UNCOMPRESSED_SIZE_OFFSET = 22;\nconst FILE_NAME_LENGTH_OFFSET = 26;\nconst EXTRA_FIELD_LENGTH_OFFSET = 28;\nconst FILE_NAME_OFFSET = 30n;\n\nexport const signature: ZipSignature = new Uint8Array([0x50, 0x4b, 0x03, 0x04]);\n\n/**\n * Parses local file header of zip file\n * @param headerOffset - offset in the archive where header starts\n * @param buffer - buffer containing whole array\n * @returns Info from the header\n */\nexport const parseZipLocalFileHeader = async (\n headerOffset: bigint,\n file: ReadableFile\n): Promise<ZipLocalFileHeader | null> => {\n const mainHeader = await readDataView(file, headerOffset, headerOffset + FILE_NAME_OFFSET);\n\n const magicBytes = mainHeader.buffer.slice(0, 4);\n if (!compareArrayBuffers(magicBytes, signature.buffer)) {\n return null;\n }\n\n const fileNameLength = mainHeader.getUint16(FILE_NAME_LENGTH_OFFSET, true);\n\n const extraFieldLength = mainHeader.getUint16(EXTRA_FIELD_LENGTH_OFFSET, true);\n\n const additionalHeader = await readRange(\n file,\n headerOffset + FILE_NAME_OFFSET,\n headerOffset + FILE_NAME_OFFSET + BigInt(fileNameLength + extraFieldLength)\n );\n\n const fileNameBuffer = additionalHeader.slice(0, fileNameLength);\n\n const extraDataBuffer = new DataView(\n additionalHeader.slice(fileNameLength, additionalHeader.byteLength)\n );\n\n const fileName = new TextDecoder().decode(fileNameBuffer).split('\\\\').join('/');\n\n let fileDataOffset = headerOffset + FILE_NAME_OFFSET + BigInt(fileNameLength + extraFieldLength);\n\n const compressionMethod = mainHeader.getUint16(COMPRESSION_METHOD_OFFSET, true);\n\n let compressedSize = BigInt(mainHeader.getUint32(COMPRESSED_SIZE_OFFSET, true)); // add zip 64 logic\n\n let uncompressedSize = BigInt(mainHeader.getUint32(UNCOMPRESSED_SIZE_OFFSET, true)); // add zip 64 logic\n\n let offsetInZip64Data = 4;\n // looking for info that might be also be in zip64 extra field\n if (uncompressedSize === BigInt(0xffffffff)) {\n uncompressedSize = extraDataBuffer.getBigUint64(offsetInZip64Data, true);\n offsetInZip64Data += 8;\n