index.js (4231B)
1 import utf8 from 'utf8'; 2 3 import {BinStream} from '~/lib/binary-stream'; 4 5 // Reference: 6 // https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html 7 // https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT 8 9 const CRC_TABLE = new Uint32Array(256); 10 11 (function fillCrcTable() { 12 for (let i = 0; i < 256; ++i) { 13 let crc = i; 14 for (let j = 0; j < 8; ++j) { 15 crc = (crc >>> 1) ^ (crc & 0x01 && 0xedb88320); 16 } 17 CRC_TABLE[i] = crc; 18 } 19 })(); 20 21 function crc32(s) { 22 let crc = -1; 23 for (let i = 0, l = s.length; i < l; i++) { 24 crc = (crc >>> 8) ^ CRC_TABLE[(crc & 0xff) ^ s.charCodeAt(i)]; 25 } 26 return (crc ^ -1) >>> 0; 27 } 28 29 function buildDOSDateTime(date) { 30 return { 31 dosTime: (date.getSeconds() >> 1) | (date.getMinutes() << 5) | (date.getHours() << 11), 32 dosDate: date.getDate() | ((date.getMonth() + 1) << 5) | ((date.getFullYear() - 1980) << 9), 33 }; 34 } 35 36 function writeLocalFileHeader(stream, fileName, size, date, crc) { 37 const dosDateTime = buildDOSDateTime(date); 38 stream.writeBinaryString('\x50\x4b\x03\x04'); // signature 39 stream.writeUint16(10); // Version to extract 40 stream.writeUint16(1 << 11); // Flags = language encoding 41 stream.writeUint16(0); // No compression 42 stream.writeUint16(dosDateTime.dosTime); 43 stream.writeUint16(dosDateTime.dosDate); 44 stream.writeUint32(crc); 45 stream.writeUint32(size); 46 stream.writeUint32(size); 47 stream.writeUint16(fileName.length); 48 stream.writeUint16(0); // Extra field len 49 stream.writeBinaryString(fileName); 50 } 51 52 function writeCentralDirectoryFileHeader(stream, fileName, size, date, crc, localHeaderOffset) { 53 const dosDateTime = buildDOSDateTime(date); 54 stream.writeBinaryString('\x50\x4b\x01\x02'); // signature 55 stream.writeUint8(10); // Version made by = 1.0 56 stream.writeUint8(3); // Made in OS = UNIX 57 stream.writeUint16(10); // Version to extract = 1.0 58 stream.writeUint16(1 << 11); // Flags = language encoding 59 stream.writeUint16(0); // No compression 60 stream.writeUint16(dosDateTime.dosTime); 61 stream.writeUint16(dosDateTime.dosDate); 62 stream.writeUint32(crc); 63 stream.writeUint32(size); 64 stream.writeUint32(size); 65 stream.writeUint16(fileName.length); 66 stream.writeUint16(0); // Extra field len 67 stream.writeUint16(0); // File comment length 68 stream.writeUint16(0); // Disk # start 69 stream.writeUint16(0); // Internal attr 70 stream.writeUint32(0); // External attr 71 stream.writeUint32(localHeaderOffset); // External attr 72 stream.writeBinaryString(fileName); 73 } 74 75 function writeEndOfCentralDirectory(stream, filesNumber, centralDirectoryOffset, centralDirectorySize) { 76 stream.writeBinaryString('\x50\x4b\x05\x06'); 77 stream.writeUint16(0); // Disk number 78 stream.writeUint16(0); // Disk number with central directory 79 stream.writeUint16(filesNumber); 80 stream.writeUint16(filesNumber); 81 stream.writeUint32(centralDirectorySize); 82 stream.writeUint32(centralDirectoryOffset); 83 stream.writeUint16(0); // Comment length 84 } 85 86 // files: Array of Object with items: 87 // "filename" - string 88 // "content" - binary string 89 // returns: ArrayBuffer 90 function createZipFile(files) { 91 const stream = new BinStream(true); 92 const now = new Date(); 93 const encodedFilenames = []; 94 const checkSums = []; 95 const headerOffsets = []; 96 97 for (const [i, file] of files.entries()) { 98 encodedFilenames[i] = utf8.encode(file.filename); 99 checkSums[i] = crc32(file.content); 100 headerOffsets[i] = stream.tell(); 101 writeLocalFileHeader(stream, encodedFilenames[i], file.content.length, now, checkSums[i]); 102 stream.writeBinaryString(file.content); 103 } 104 105 const centralDirectoryOffset = stream.tell(); 106 for (const [i, file] of files.entries()) { 107 writeCentralDirectoryFileHeader( 108 stream, 109 encodedFilenames[i], 110 file.content.length, 111 now, 112 checkSums[i], 113 headerOffsets[i] 114 ); 115 } 116 117 writeEndOfCentralDirectory(stream, files.length, centralDirectoryOffset, stream.tell() - centralDirectoryOffset); 118 119 return stream.getBuffer(); 120 } 121 122 export {createZipFile};