Skip to content

Instantly share code, notes, and snippets.

@PhoenixIllusion
Created March 10, 2020 21:30
Show Gist options
  • Save PhoenixIllusion/cc279948a5705dcefac238e6e7289841 to your computer and use it in GitHub Desktop.
Save PhoenixIllusion/cc279948a5705dcefac238e6e7289841 to your computer and use it in GitHub Desktop.
Read Zip File - Built-In DecompressionStream for entries
"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