Created
March 10, 2020 21:30
-
-
Save PhoenixIllusion/cc279948a5705dcefac238e6e7289841 to your computer and use it in GitHub Desktop.
Read Zip File - Built-In DecompressionStream for entries
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
"use strict" | |
const ZIP_EOCD = 0x06054b50; | |
const ZIP_CENTRAL_DIR_HEADER = 0x02014b50; | |
const ZIP_LOCAL_HEADER = 0x04034b50; | |
const decoder = new TextDecoder(); | |
const GZIP_HEADER = new Uint8Array([31,139, 8, 0, 0,0,0,0, 0, 255]); | |
function deflateFooter(crc, isize){ | |
const footer = new DataView(new Uint8Array(8).buffer); | |
footer.setUint32(0, crc, true); | |
footer.setUint32(4, isize, true); | |
return footer; | |
} | |
function addScript(url) { | |
return new Promise(function (resolve,reject) { | |
if (!document.querySelector("script[src='"+url+"']")) { | |
const ele = document.createElement('script'); | |
ele.src = url; | |
ele.onload = function() { | |
resolve(url) | |
} | |
const head = document.getElementsByTagName('head')[0]; | |
head.appendChild(ele); | |
} else { | |
resolve(url); | |
} | |
}); | |
} | |
async function decodeInflate(array, crc, isize) { | |
if(window.DecompressionStream) { | |
const ds = new DecompressionStream('gzip'); | |
return new Response(new Blob([GZIP_HEADER, array, deflateFooter(crc,isize)]).stream().pipeThrough(ds)).blob() | |
} else { | |
await addScript('https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.10/pako_inflate.min.js'); | |
const decompressed = pako.inflateRaw(array); | |
return new Blob([decompressed]); | |
} | |
} | |
class DataReader { | |
constructor(dataView) { | |
this.dataView = dataView; | |
this.index = 0; | |
} | |
U2() { | |
this.index+=2; | |
return this.dataView.getUint16(this.index-2,true); | |
} | |
U4() { | |
this.index+=4; | |
return this.dataView.getUint32(this.index-4,true); | |
} | |
CHUNK(len) { | |
this.index+=len; | |
return new Uint8Array(this.dataView.buffer, this.index-len, len) | |
} | |
SKIP(len) { | |
this.index+=len; | |
} | |
} | |
class ZipReader { | |
constructor(blob) { | |
this.blob = blob; | |
} | |
async parse() { | |
this.EOCD = await this.getEOCD(); | |
this.RECORDS = await this.getCentralDirectory(); | |
} | |
generalHeader(entry, reader) { | |
entry.versionReq = reader.U2();0 | |
entry.generalFlags = reader.U2(); | |
entry.compressionMethod = reader.U2(); | |
entry.lastModifiedDate = reader.U2(); | |
entry.lastModifiedTime = reader.U2(); | |
entry.CRC32 = reader.U4(); | |
entry.compressedSize = reader.U4(); | |
entry.uncompressedSize = reader.U4(); | |
entry.fileNameLen = reader.U2(); | |
entry.extraFieldLen = reader.U2(); | |
} | |
parseLocalHeader(reader) { | |
const entry = {}; | |
entry.magic = reader.U4(); | |
entry.MAGIC = entry.magic.toString(16); | |
if(entry.magic != ZIP_LOCAL_HEADER) { | |
throw new Error("Error parsing entry") | |
} | |
this.generalHeader(entry, reader); | |
reader.SKIP(entry.fileNameLen) | |
reader.SKIP(entry.extraFieldLen); | |
return entry; | |
} | |
async getRecord(number) { | |
const record = this.RECORDS[number]; | |
const offset = record.relativeOffset | |
const end = offset + record.compressedSize + 2048; | |
const blob = this.blob.slice(offset, end); | |
const reader = new DataReader(new DataView(await readBlobAsArrayBuffer(blob))) | |
const localHeader = this.parseLocalHeader(reader); | |
return await decodeInflate(reader.CHUNK(localHeader.compressedSize), record.CRC32, record.uncompressedSize); | |
} | |
parseCentralDirectoryHeader(reader) { | |
const entry = {}; | |
entry.magic = reader.U4(); | |
entry.MAGIC = entry.magic.toString(16); | |
if(entry.magic != ZIP_CENTRAL_DIR_HEADER) { | |
throw new Error("Error parsing entry") | |
} | |
entry.versionMade = reader.U2(); | |
this.generalHeader(entry, reader); | |
entry.commentLen = reader.U2(); | |
entry.diskStart = reader.U2(); | |
entry.internalFileAttrib = reader.U2(); | |
entry.externalFileAttrib = reader.U4(); | |
entry.relativeOffset = reader.U4(); | |
entry.filename = decoder.decode(reader.CHUNK(entry.fileNameLen)) | |
reader.SKIP(entry.extraFieldLen) | |
reader.SKIP(entry.commentLen) | |
return entry; | |
} | |
async getCentralDirectory() { | |
const centralDirectory = this.blob.slice(this.EOCD.directoryOffset,this.EOCD.directoryEnd); | |
const dataView = new DataView(await readBlobAsArrayBuffer(centralDirectory)); | |
const reader = new DataReader(dataView); | |
const entries = []; | |
for(var i=0;i<this.EOCD.records;i++){ | |
const entry = this.parseCentralDirectoryHeader(reader); | |
entries.push(entry); | |
} | |
return entries | |
} | |
parseEOCD(dataView) { | |
const response = {}; | |
const reader = new DataReader(dataView); | |
response.magic = reader.U4().toString(16); | |
response.diskNum = reader.U2(); | |
response.diskDir = reader.U2(); | |
response.diskRecords = reader.U2(); | |
response.records = reader.U2(); | |
response.directorySize = reader.U4(); | |
response.directoryOffset = reader.U4(); | |
response.directoryEnd = response.directoryOffset+response.directorySize; | |
response.commentLen = reader.U2(); | |
return response; | |
} | |
async getEOCD() { | |
const tail = this.blob.slice(this.blob.size-65000); | |
const data = new DataView(await readBlobAsArrayBuffer(tail)); | |
for(var i=22;i<data.byteLength;i++){ | |
if(data.getUint32(data.byteLength-i,true) == ZIP_EOCD) { | |
return this.parseEOCD(new DataView(data.buffer,data.byteLength-i)) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment