zip-reader.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /*
  2. Copyright (c) 2022 Gildas Lormeau. All rights reserved.
  3. Redistribution and use in source and binary forms, with or without
  4. modification, are permitted provided that the following conditions are met:
  5. 1. Redistributions of source code must retain the above copyright notice,
  6. this list of conditions and the following disclaimer.
  7. 2. Redistributions in binary form must reproduce the above copyright
  8. notice, this list of conditions and the following disclaimer in
  9. the documentation and/or other materials provided with the distribution.
  10. 3. The names of the authors may not be used to endorse or promote products
  11. derived from this software without specific prior written permission.
  12. THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
  13. INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  14. FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
  15. INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
  16. INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  17. LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  18. OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  19. LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  20. NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  21. EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. */
  23. /* global BigInt */
  24. import {
  25. MAX_32_BITS,
  26. MAX_16_BITS,
  27. COMPRESSION_METHOD_DEFLATE,
  28. COMPRESSION_METHOD_STORE,
  29. COMPRESSION_METHOD_AES,
  30. LOCAL_FILE_HEADER_SIGNATURE,
  31. CENTRAL_FILE_HEADER_SIGNATURE,
  32. END_OF_CENTRAL_DIR_SIGNATURE,
  33. ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE,
  34. ZIP64_END_OF_CENTRAL_DIR_SIGNATURE,
  35. EXTRAFIELD_TYPE_ZIP64,
  36. EXTRAFIELD_TYPE_UNICODE_PATH,
  37. EXTRAFIELD_TYPE_UNICODE_COMMENT,
  38. EXTRAFIELD_TYPE_AES,
  39. EXTRAFIELD_TYPE_NTFS,
  40. EXTRAFIELD_TYPE_NTFS_TAG1,
  41. EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP,
  42. END_OF_CENTRAL_DIR_LENGTH,
  43. ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH,
  44. ZIP64_END_OF_CENTRAL_DIR_LENGTH,
  45. BITFLAG_ENCRYPTED,
  46. BITFLAG_LEVEL,
  47. BITFLAG_DATA_DESCRIPTOR,
  48. BITFLAG_LANG_ENCODING_FLAG,
  49. FILE_ATTR_MSDOS_DIR_MASK,
  50. DIRECTORY_SIGNATURE
  51. } from "./constants.js";
  52. import { getConfiguration } from "./configuration.js";
  53. import { createCodec, CODEC_INFLATE, ERR_INVALID_SIGNATURE, ERR_INVALID_PASSWORD } from "./codecs/codec-pool.js";
  54. import decodeText from "./util/decode-text.js";
  55. import Crc32 from "./codecs/crc32.js";
  56. import { processData } from "./engine.js";
  57. import Entry from "./zip-entry.js";
  58. const ERR_BAD_FORMAT = "File format is not recognized";
  59. const ERR_EOCDR_NOT_FOUND = "End of central directory not found";
  60. const ERR_EOCDR_ZIP64_NOT_FOUND = "End of Zip64 central directory not found";
  61. const ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND = "End of Zip64 central directory locator not found";
  62. const ERR_CENTRAL_DIRECTORY_NOT_FOUND = "Central directory header not found";
  63. const ERR_LOCAL_FILE_HEADER_NOT_FOUND = "Local file header not found";
  64. const ERR_EXTRAFIELD_ZIP64_NOT_FOUND = "Zip64 extra field not found";
  65. const ERR_ENCRYPTED = "File contains encrypted entry";
  66. const ERR_UNSUPPORTED_ENCRYPTION = "Encryption method not supported";
  67. const ERR_UNSUPPORTED_COMPRESSION = "Compression method not supported";
  68. const CHARSET_UTF8 = "utf-8";
  69. const CHARSET_CP437 = "cp437";
  70. const ZIP64_PROPERTIES = ["uncompressedSize", "compressedSize", "offset"];
  71. class ZipReader {
  72. constructor(reader, options = {}) {
  73. Object.assign(this, {
  74. reader,
  75. options,
  76. config: getConfiguration()
  77. });
  78. }
  79. async* getEntriesGenerator(options = {}) {
  80. const zipReader = this;
  81. const reader = zipReader.reader;
  82. if (!reader.initialized) {
  83. await reader.init();
  84. }
  85. if (reader.size < END_OF_CENTRAL_DIR_LENGTH) {
  86. throw new Error(ERR_BAD_FORMAT);
  87. }
  88. const endOfDirectoryInfo = await seekSignature(reader, END_OF_CENTRAL_DIR_SIGNATURE, reader.size, END_OF_CENTRAL_DIR_LENGTH, MAX_16_BITS * 16);
  89. if (!endOfDirectoryInfo) {
  90. throw new Error(ERR_EOCDR_NOT_FOUND);
  91. }
  92. const endOfDirectoryView = getDataView(endOfDirectoryInfo);
  93. let directoryDataLength = getUint32(endOfDirectoryView, 12);
  94. let directoryDataOffset = getUint32(endOfDirectoryView, 16);
  95. let filesLength = getUint16(endOfDirectoryView, 8);
  96. let prependedDataLength = 0;
  97. if (directoryDataOffset == MAX_32_BITS || directoryDataLength == MAX_32_BITS || filesLength == MAX_16_BITS) {
  98. const endOfDirectoryLocatorArray = await readUint8Array(reader, endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH, ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH);
  99. const endOfDirectoryLocatorView = getDataView(endOfDirectoryLocatorArray);
  100. if (getUint32(endOfDirectoryLocatorView, 0) != ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIGNATURE) {
  101. throw new Error(ERR_EOCDR_ZIP64_NOT_FOUND);
  102. }
  103. directoryDataOffset = getBigUint64(endOfDirectoryLocatorView, 8);
  104. let endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
  105. let endOfDirectoryView = getDataView(endOfDirectoryArray);
  106. const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - ZIP64_END_OF_CENTRAL_DIR_LOCATOR_LENGTH - ZIP64_END_OF_CENTRAL_DIR_LENGTH;
  107. if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
  108. const originalDirectoryDataOffset = directoryDataOffset;
  109. directoryDataOffset = expectedDirectoryDataOffset;
  110. prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
  111. endOfDirectoryArray = await readUint8Array(reader, directoryDataOffset, ZIP64_END_OF_CENTRAL_DIR_LENGTH);
  112. endOfDirectoryView = getDataView(endOfDirectoryArray);
  113. }
  114. if (getUint32(endOfDirectoryView, 0) != ZIP64_END_OF_CENTRAL_DIR_SIGNATURE) {
  115. throw new Error(ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND);
  116. }
  117. filesLength = getBigUint64(endOfDirectoryView, 32);
  118. directoryDataLength = getBigUint64(endOfDirectoryView, 40);
  119. directoryDataOffset -= directoryDataLength;
  120. }
  121. if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
  122. throw new Error(ERR_BAD_FORMAT);
  123. }
  124. let offset = 0;
  125. let directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
  126. let directoryView = getDataView(directoryArray);
  127. if (directoryDataLength) {
  128. const expectedDirectoryDataOffset = endOfDirectoryInfo.offset - directoryDataLength;
  129. if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE && directoryDataOffset != expectedDirectoryDataOffset) {
  130. const originalDirectoryDataOffset = directoryDataOffset;
  131. directoryDataOffset = expectedDirectoryDataOffset;
  132. prependedDataLength = directoryDataOffset - originalDirectoryDataOffset;
  133. directoryArray = await readUint8Array(reader, directoryDataOffset, directoryDataLength);
  134. directoryView = getDataView(directoryArray);
  135. }
  136. }
  137. if (directoryDataOffset < 0 || directoryDataOffset >= reader.size) {
  138. throw new Error(ERR_BAD_FORMAT);
  139. }
  140. for (let indexFile = 0; indexFile < filesLength; indexFile++) {
  141. const fileEntry = new ZipEntry(reader, zipReader.config, zipReader.options);
  142. if (getUint32(directoryView, offset) != CENTRAL_FILE_HEADER_SIGNATURE) {
  143. throw new Error(ERR_CENTRAL_DIRECTORY_NOT_FOUND);
  144. }
  145. readCommonHeader(fileEntry, directoryView, offset + 6);
  146. const languageEncodingFlag = Boolean(fileEntry.bitFlag.languageEncodingFlag);
  147. const filenameOffset = offset + 46;
  148. const extraFieldOffset = filenameOffset + fileEntry.filenameLength;
  149. const commentOffset = extraFieldOffset + fileEntry.extraFieldLength;
  150. const versionMadeBy = getUint16(directoryView, offset + 4);
  151. const msDosCompatible = (versionMadeBy & 0) == 0;
  152. Object.assign(fileEntry, {
  153. versionMadeBy,
  154. msDosCompatible,
  155. compressedSize: 0,
  156. uncompressedSize: 0,
  157. commentLength: getUint16(directoryView, offset + 32),
  158. directory: msDosCompatible && ((getUint8(directoryView, offset + 38) & FILE_ATTR_MSDOS_DIR_MASK) == FILE_ATTR_MSDOS_DIR_MASK),
  159. offset: getUint32(directoryView, offset + 42) + prependedDataLength,
  160. internalFileAttribute: getUint32(directoryView, offset + 34),
  161. externalFileAttribute: getUint32(directoryView, offset + 38),
  162. rawFilename: directoryArray.subarray(filenameOffset, extraFieldOffset),
  163. filenameUTF8: languageEncodingFlag,
  164. commentUTF8: languageEncodingFlag,
  165. rawExtraField: directoryArray.subarray(extraFieldOffset, commentOffset)
  166. });
  167. const endOffset = commentOffset + fileEntry.commentLength;
  168. fileEntry.rawComment = directoryArray.subarray(commentOffset, endOffset);
  169. const filenameEncoding = getOptionValue(zipReader, options, "filenameEncoding");
  170. const commentEncoding = getOptionValue(zipReader, options, "commentEncoding");
  171. const [filename, comment] = await Promise.all([
  172. decodeText(fileEntry.rawFilename, fileEntry.filenameUTF8 ? CHARSET_UTF8 : filenameEncoding || CHARSET_CP437),
  173. decodeText(fileEntry.rawComment, fileEntry.commentUTF8 ? CHARSET_UTF8 : commentEncoding || CHARSET_CP437)
  174. ]);
  175. fileEntry.filename = filename;
  176. fileEntry.comment = comment;
  177. if (!fileEntry.directory && fileEntry.filename.endsWith(DIRECTORY_SIGNATURE)) {
  178. fileEntry.directory = true;
  179. }
  180. await readCommonFooter(fileEntry, fileEntry, directoryView, offset + 6);
  181. const entry = new Entry(fileEntry);
  182. entry.getData = (writer, options) => fileEntry.getData(writer, entry, options);
  183. offset = endOffset;
  184. if (options.onprogress) {
  185. try {
  186. options.onprogress(indexFile + 1, filesLength, new Entry(fileEntry));
  187. } catch (_error) {
  188. // ignored
  189. }
  190. }
  191. yield entry;
  192. }
  193. return true;
  194. }
  195. async getEntries(options = {}) {
  196. const entries = [];
  197. const iter = this.getEntriesGenerator(options);
  198. let curr = iter.next();
  199. while(!(await curr).done) {
  200. entries.push((await curr).value);
  201. curr = iter.next();
  202. }
  203. return entries;
  204. }
  205. async close() {
  206. }
  207. }
  208. export {
  209. ZipReader,
  210. ERR_BAD_FORMAT,
  211. ERR_EOCDR_NOT_FOUND,
  212. ERR_EOCDR_ZIP64_NOT_FOUND,
  213. ERR_EOCDR_LOCATOR_ZIP64_NOT_FOUND,
  214. ERR_CENTRAL_DIRECTORY_NOT_FOUND,
  215. ERR_LOCAL_FILE_HEADER_NOT_FOUND,
  216. ERR_EXTRAFIELD_ZIP64_NOT_FOUND,
  217. ERR_ENCRYPTED,
  218. ERR_UNSUPPORTED_ENCRYPTION,
  219. ERR_UNSUPPORTED_COMPRESSION,
  220. ERR_INVALID_SIGNATURE,
  221. ERR_INVALID_PASSWORD
  222. };
  223. class ZipEntry {
  224. constructor(reader, config, options) {
  225. Object.assign(this, {
  226. reader,
  227. config,
  228. options
  229. });
  230. }
  231. async getData(writer, fileEntry, options = {}) {
  232. const zipEntry = this;
  233. const {
  234. reader,
  235. offset,
  236. extraFieldAES,
  237. compressionMethod,
  238. config,
  239. bitFlag,
  240. signature,
  241. rawLastModDate,
  242. compressedSize
  243. } = zipEntry;
  244. const localDirectory = zipEntry.localDirectory = {};
  245. if (!reader.initialized) {
  246. await reader.init();
  247. }
  248. let dataArray = await readUint8Array(reader, offset, 30);
  249. const dataView = getDataView(dataArray);
  250. let password = getOptionValue(zipEntry, options, "password");
  251. password = password && password.length && password;
  252. if (extraFieldAES) {
  253. if (extraFieldAES.originalCompressionMethod != COMPRESSION_METHOD_AES) {
  254. throw new Error(ERR_UNSUPPORTED_COMPRESSION);
  255. }
  256. }
  257. if (compressionMethod != COMPRESSION_METHOD_STORE && compressionMethod != COMPRESSION_METHOD_DEFLATE) {
  258. throw new Error(ERR_UNSUPPORTED_COMPRESSION);
  259. }
  260. if (getUint32(dataView, 0) != LOCAL_FILE_HEADER_SIGNATURE) {
  261. throw new Error(ERR_LOCAL_FILE_HEADER_NOT_FOUND);
  262. }
  263. readCommonHeader(localDirectory, dataView, 4);
  264. dataArray = await readUint8Array(reader, offset, 30 + localDirectory.filenameLength + localDirectory.extraFieldLength);
  265. localDirectory.rawExtraField = dataArray.subarray(30 + localDirectory.filenameLength);
  266. await readCommonFooter(zipEntry, localDirectory, dataView, 4);
  267. fileEntry.lastAccessDate = localDirectory.lastAccessDate;
  268. fileEntry.creationDate = localDirectory.creationDate;
  269. const encrypted = zipEntry.encrypted && localDirectory.encrypted;
  270. const zipCrypto = encrypted && !extraFieldAES;
  271. if (encrypted) {
  272. if (!zipCrypto && extraFieldAES.strength === undefined) {
  273. throw new Error(ERR_UNSUPPORTED_ENCRYPTION);
  274. } else if (!password) {
  275. throw new Error(ERR_ENCRYPTED);
  276. }
  277. }
  278. const codec = await createCodec(config.Inflate, {
  279. codecType: CODEC_INFLATE,
  280. password,
  281. zipCrypto,
  282. encryptionStrength: extraFieldAES && extraFieldAES.strength,
  283. signed: getOptionValue(zipEntry, options, "checkSignature"),
  284. passwordVerification: zipCrypto && (bitFlag.dataDescriptor ? ((rawLastModDate >>> 8) & 0xFF) : ((signature >>> 24) & 0xFF)),
  285. signature,
  286. compressed: compressionMethod != 0,
  287. encrypted,
  288. useWebWorkers: getOptionValue(zipEntry, options, "useWebWorkers")
  289. }, config);
  290. if (!writer.initialized) {
  291. await writer.init();
  292. }
  293. const signal = getOptionValue(zipEntry, options, "signal");
  294. const dataOffset = offset + 30 + localDirectory.filenameLength + localDirectory.extraFieldLength;
  295. await processData(codec, reader, writer, dataOffset, () => compressedSize, config, { onprogress: options.onprogress, signal });
  296. return writer.getData();
  297. }
  298. }
  299. function readCommonHeader(directory, dataView, offset) {
  300. const rawBitFlag = directory.rawBitFlag = getUint16(dataView, offset + 2);
  301. const encrypted = (rawBitFlag & BITFLAG_ENCRYPTED) == BITFLAG_ENCRYPTED;
  302. const rawLastModDate = getUint32(dataView, offset + 6);
  303. Object.assign(directory, {
  304. encrypted,
  305. version: getUint16(dataView, offset),
  306. bitFlag: {
  307. level: (rawBitFlag & BITFLAG_LEVEL) >> 1,
  308. dataDescriptor: (rawBitFlag & BITFLAG_DATA_DESCRIPTOR) == BITFLAG_DATA_DESCRIPTOR,
  309. languageEncodingFlag: (rawBitFlag & BITFLAG_LANG_ENCODING_FLAG) == BITFLAG_LANG_ENCODING_FLAG
  310. },
  311. rawLastModDate,
  312. lastModDate: getDate(rawLastModDate),
  313. filenameLength: getUint16(dataView, offset + 22),
  314. extraFieldLength: getUint16(dataView, offset + 24)
  315. });
  316. }
  317. async function readCommonFooter(fileEntry, directory, dataView, offset) {
  318. const rawExtraField = directory.rawExtraField;
  319. const extraField = directory.extraField = new Map();
  320. const rawExtraFieldView = getDataView(new Uint8Array(rawExtraField));
  321. let offsetExtraField = 0;
  322. try {
  323. while (offsetExtraField < rawExtraField.length) {
  324. const type = getUint16(rawExtraFieldView, offsetExtraField);
  325. const size = getUint16(rawExtraFieldView, offsetExtraField + 2);
  326. extraField.set(type, {
  327. type,
  328. data: rawExtraField.slice(offsetExtraField + 4, offsetExtraField + 4 + size)
  329. });
  330. offsetExtraField += 4 + size;
  331. }
  332. } catch (_error) {
  333. // ignored
  334. }
  335. const compressionMethod = getUint16(dataView, offset + 4);
  336. directory.signature = getUint32(dataView, offset + 10);
  337. directory.uncompressedSize = getUint32(dataView, offset + 18);
  338. directory.compressedSize = getUint32(dataView, offset + 14);
  339. const extraFieldZip64 = extraField.get(EXTRAFIELD_TYPE_ZIP64);
  340. if (extraFieldZip64) {
  341. readExtraFieldZip64(extraFieldZip64, directory);
  342. directory.extraFieldZip64 = extraFieldZip64;
  343. }
  344. const extraFieldUnicodePath = extraField.get(EXTRAFIELD_TYPE_UNICODE_PATH);
  345. if (extraFieldUnicodePath) {
  346. await readExtraFieldUnicode(extraFieldUnicodePath, "filename", "rawFilename", directory, fileEntry);
  347. directory.extraFieldUnicodePath = extraFieldUnicodePath;
  348. }
  349. const extraFieldUnicodeComment = extraField.get(EXTRAFIELD_TYPE_UNICODE_COMMENT);
  350. if (extraFieldUnicodeComment) {
  351. await readExtraFieldUnicode(extraFieldUnicodeComment, "comment", "rawComment", directory, fileEntry);
  352. directory.extraFieldUnicodeComment = extraFieldUnicodeComment;
  353. }
  354. const extraFieldAES = extraField.get(EXTRAFIELD_TYPE_AES);
  355. if (extraFieldAES) {
  356. readExtraFieldAES(extraFieldAES, directory, compressionMethod);
  357. directory.extraFieldAES = extraFieldAES;
  358. } else {
  359. directory.compressionMethod = compressionMethod;
  360. }
  361. const extraFieldNTFS = extraField.get(EXTRAFIELD_TYPE_NTFS);
  362. if (extraFieldNTFS) {
  363. readExtraFieldNTFS(extraFieldNTFS, directory);
  364. directory.extraFieldNTFS = extraFieldNTFS;
  365. }
  366. const extraFieldExtendedTimestamp = extraField.get(EXTRAFIELD_TYPE_EXTENDED_TIMESTAMP);
  367. if (extraFieldExtendedTimestamp) {
  368. readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory);
  369. directory.extraFieldExtendedTimestamp = extraFieldExtendedTimestamp;
  370. }
  371. }
  372. function readExtraFieldZip64(extraFieldZip64, directory) {
  373. directory.zip64 = true;
  374. const extraFieldView = getDataView(extraFieldZip64.data);
  375. extraFieldZip64.values = [];
  376. for (let indexValue = 0; indexValue < Math.floor(extraFieldZip64.data.length / 8); indexValue++) {
  377. extraFieldZip64.values.push(getBigUint64(extraFieldView, 0 + indexValue * 8));
  378. }
  379. const missingProperties = ZIP64_PROPERTIES.filter(propertyName => directory[propertyName] == MAX_32_BITS);
  380. for (let indexMissingProperty = 0; indexMissingProperty < missingProperties.length; indexMissingProperty++) {
  381. extraFieldZip64[missingProperties[indexMissingProperty]] = extraFieldZip64.values[indexMissingProperty];
  382. }
  383. ZIP64_PROPERTIES.forEach(propertyName => {
  384. if (directory[propertyName] == MAX_32_BITS) {
  385. if (extraFieldZip64[propertyName] !== undefined) {
  386. directory[propertyName] = extraFieldZip64[propertyName];
  387. } else {
  388. throw new Error(ERR_EXTRAFIELD_ZIP64_NOT_FOUND);
  389. }
  390. }
  391. });
  392. }
  393. async function readExtraFieldUnicode(extraFieldUnicode, propertyName, rawPropertyName, directory, fileEntry) {
  394. const extraFieldView = getDataView(extraFieldUnicode.data);
  395. extraFieldUnicode.version = getUint8(extraFieldView, 0);
  396. extraFieldUnicode.signature = getUint32(extraFieldView, 1);
  397. const crc32 = new Crc32();
  398. crc32.append(fileEntry[rawPropertyName]);
  399. const dataViewSignature = getDataView(new Uint8Array(4));
  400. dataViewSignature.setUint32(0, crc32.get(), true);
  401. extraFieldUnicode[propertyName] = await decodeText(extraFieldUnicode.data.subarray(5));
  402. extraFieldUnicode.valid = !fileEntry.bitFlag.languageEncodingFlag && extraFieldUnicode.signature == getUint32(dataViewSignature, 0);
  403. if (extraFieldUnicode.valid) {
  404. directory[propertyName] = extraFieldUnicode[propertyName];
  405. directory[propertyName + "UTF8"] = true;
  406. }
  407. }
  408. function readExtraFieldAES(extraFieldAES, directory, compressionMethod) {
  409. const extraFieldView = getDataView(extraFieldAES.data);
  410. extraFieldAES.vendorVersion = getUint8(extraFieldView, 0);
  411. extraFieldAES.vendorId = getUint8(extraFieldView, 2);
  412. const strength = getUint8(extraFieldView, 4);
  413. extraFieldAES.strength = strength;
  414. extraFieldAES.originalCompressionMethod = compressionMethod;
  415. directory.compressionMethod = extraFieldAES.compressionMethod = getUint16(extraFieldView, 5);
  416. }
  417. function readExtraFieldNTFS(extraFieldNTFS, directory) {
  418. const extraFieldView = getDataView(extraFieldNTFS.data);
  419. let offsetExtraField = 4;
  420. let tag1Data;
  421. try {
  422. while (offsetExtraField < extraFieldNTFS.data.length && !tag1Data) {
  423. const tagValue = getUint16(extraFieldView, offsetExtraField);
  424. const attributeSize = getUint16(extraFieldView, offsetExtraField + 2);
  425. if (tagValue == EXTRAFIELD_TYPE_NTFS_TAG1) {
  426. tag1Data = extraFieldNTFS.data.slice(offsetExtraField + 4, offsetExtraField + 4 + attributeSize);
  427. }
  428. offsetExtraField += 4 + attributeSize;
  429. }
  430. } catch (_error) {
  431. // ignored
  432. }
  433. try {
  434. if (tag1Data && tag1Data.length == 24) {
  435. const tag1View = getDataView(tag1Data);
  436. const rawLastModDate = tag1View.getBigUint64(0, true);
  437. const rawLastAccessDate = tag1View.getBigUint64(8, true);
  438. const rawCreationDate = tag1View.getBigUint64(16, true);
  439. Object.assign(extraFieldNTFS, {
  440. rawLastModDate,
  441. rawLastAccessDate,
  442. rawCreationDate
  443. });
  444. const lastModDate = getDateNTFS(rawLastModDate);
  445. const lastAccessDate = getDateNTFS(rawLastAccessDate);
  446. const creationDate = getDateNTFS(rawCreationDate);
  447. const extraFieldData = { lastModDate, lastAccessDate, creationDate };
  448. Object.assign(extraFieldNTFS, extraFieldData);
  449. Object.assign(directory, extraFieldData);
  450. }
  451. } catch (_error) {
  452. // ignored
  453. }
  454. }
  455. function readExtraFieldExtendedTimestamp(extraFieldExtendedTimestamp, directory) {
  456. const extraFieldView = getDataView(extraFieldExtendedTimestamp.data);
  457. const flags = getUint8(extraFieldView, 0);
  458. const timeProperties = [];
  459. const timeRawProperties = [];
  460. if ((flags & 0x1) == 0x1) {
  461. timeProperties.push("lastModDate");
  462. timeRawProperties.push("rawLastModDate");
  463. }
  464. if ((flags & 0x2) == 0x2) {
  465. timeProperties.push("lastAccessDate");
  466. timeRawProperties.push("rawLastAccessDate");
  467. }
  468. if ((flags & 0x4) == 0x4) {
  469. timeProperties.push("creationDate");
  470. timeRawProperties.push("rawCreationDate");
  471. }
  472. let offset = 1;
  473. timeProperties.forEach((propertyName, indexProperty) => {
  474. if (extraFieldExtendedTimestamp.data.length >= offset + 4) {
  475. const time = getUint32(extraFieldView, offset);
  476. directory[propertyName] = extraFieldExtendedTimestamp[propertyName] = new Date(time * 1000);
  477. const rawPropertyName = timeRawProperties[indexProperty];
  478. extraFieldExtendedTimestamp[rawPropertyName] = time;
  479. }
  480. offset += 4;
  481. });
  482. }
  483. async function seekSignature(reader, signature, startOffset, minimumBytes, maximumLength) {
  484. const signatureArray = new Uint8Array(4);
  485. const signatureView = getDataView(signatureArray);
  486. setUint32(signatureView, 0, signature);
  487. const maximumBytes = minimumBytes + maximumLength;
  488. return (await seek(minimumBytes)) || await seek(Math.min(maximumBytes, startOffset));
  489. async function seek(length) {
  490. const offset = startOffset - length;
  491. const bytes = await readUint8Array(reader, offset, length);
  492. for (let indexByte = bytes.length - minimumBytes; indexByte >= 0; indexByte--) {
  493. if (bytes[indexByte] == signatureArray[0] && bytes[indexByte + 1] == signatureArray[1] &&
  494. bytes[indexByte + 2] == signatureArray[2] && bytes[indexByte + 3] == signatureArray[3]) {
  495. return {
  496. offset: offset + indexByte,
  497. buffer: bytes.slice(indexByte, indexByte + minimumBytes).buffer
  498. };
  499. }
  500. }
  501. }
  502. }
  503. function getOptionValue(zipReader, options, name) {
  504. return options[name] === undefined ? zipReader.options[name] : options[name];
  505. }
  506. function getDate(timeRaw) {
  507. const date = (timeRaw & 0xffff0000) >> 16, time = timeRaw & 0x0000ffff;
  508. try {
  509. return new Date(1980 + ((date & 0xFE00) >> 9), ((date & 0x01E0) >> 5) - 1, date & 0x001F, (time & 0xF800) >> 11, (time & 0x07E0) >> 5, (time & 0x001F) * 2, 0);
  510. } catch (_error) {
  511. // ignored
  512. }
  513. }
  514. function getDateNTFS(timeRaw) {
  515. return new Date((Number((timeRaw / BigInt(10000)) - BigInt(11644473600000))));
  516. }
  517. function getUint8(view, offset) {
  518. return view.getUint8(offset);
  519. }
  520. function getUint16(view, offset) {
  521. return view.getUint16(offset, true);
  522. }
  523. function getUint32(view, offset) {
  524. return view.getUint32(offset, true);
  525. }
  526. function getBigUint64(view, offset) {
  527. return Number(view.getBigUint64(offset, true));
  528. }
  529. function setUint32(view, offset, value) {
  530. view.setUint32(offset, value, true);
  531. }
  532. function getDataView(array) {
  533. return new DataView(array.buffer);
  534. }
  535. function readUint8Array(reader, offset, size) {
  536. return reader.readUint8Array(offset, size);
  537. }