Skip to content

Instantly share code, notes, and snippets.

@fillano
Created August 7, 2020 09:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save fillano/f9ca497b8f403c5dce6ae6ea8c67d00b to your computer and use it in GitHub Desktop.
Save fillano/f9ca497b8f403c5dce6ae6ea8c67d00b to your computer and use it in GitHub Desktop.
zip file parser in js (with inflate provided by pako)
<!DOCTYPE html>
<html>
<head>
<title>file reader</title>
<style>
.dropable {
width: 100%;
height: 100px;
background-color: #369;
color: white;
border: solid 3px gray;
border-radius: 5px;
padding: 5px 5px 5px 5px;
}
.message {
width: 100%;
background-color: #ddd;
border: solid 1px gray;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="target" class="dropable"><input type="file" id="file" /></div>
<div id="panel" class="message"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/1.0.11/pako_inflate.min.js"></script>
<script src="zipfs.js"></script>
<script>
var _target = document.getElementById('target');
var _message = document.getElementById('panel');
var _file = document.getElementById('file');
_file.onchange = function (e) {
if (this.files.length > 0) {
//console.log('step 1', this.files[0]);
var reader = new FileReader();
reader.onload = function (e) {
var buffer = e.target.result;
//console.log('file size: ' + buffer.byteLength);
zipfs(buffer, pako.inflateRaw, function (err, files) {
//console.log('step 3', 'zipfs callback', files);
if (!!err) return console.error(err);
let str = '<table border="1" cellspacing="0" cellpadding="5">';
files.forEach(file => {
str += '<tr><td>' + file.file_name + '</td><td>';
if (file.file_name.lastIndexOf('.xml') === file.file_name.length - 4 ||
file.file_name.lastIndexOf('.rels') === file.file_name.length - 5) {
str += zipfs.stringToHtmlEntity(zipfs.uintToString(file.content));
}
if (file.file_name.lastIndexOf('.jpeg') === file.file_name.length - 5) {
str += '<img src="data:image/jpeg;base64,' +
zipfs.arrayBufferToBase64(file.content) + '">';
}
if (file.file_name.lastIndexOf('.png') === file.file_name.length - 4) {
str += '<img src="data:image/png;base64,' +
zipfs.arrayBufferToBase64(file.content) + '">';
}
str += '</td></tr>';
});
str += '</table>';
document.getElementById('panel').innerHTML = str;
});
};
reader.readAsArrayBuffer(this.files[0]);
}
}
</script>
</body>
</html>
(function () {
_zipfs.uintToString = uintToString;
_zipfs.arrayBufferToBase64 = arrayBufferToBase64;
_zipfs.stringToHtmlEntity = stringToHtmlEntity;
//return _zipfs;
if('undefined' !== typeof module && 'undefined' !== typeof module.exports) {
module.exports = _zipfs;
}
if('undefined' !== typeof window) {
window.zipfs = _zipfs;
}
/**
*
* @param {*} buf : ArrayBuffer
* @param {*} inflate : function implemented the inflate action
* @param {*} cb : callback
*/
function _zipfs(buf, inflate, cb) {
//console.log('zipfs enter');
buf = new Uint8Array(buf);
try {
let entries = cdr(buf);
//console.log('entries: ', entries);
let result = entries.map(mapper(buf, inflate));
if (!!cb && 'function' === typeof cb) {
cb(null, result);
} else {
return result;
}
} catch (e) {
if (!!cb && 'function' === typeof cb) {
cb(e.message + "\n" + e.stack);
} else {
throw e;
}
}
}
/**
*
* @param {*} arr : Uint8Array
* @param {*} inflate : function implemented the inflate action
*/
function mapper(arr, inflate) {
//console.log('mapper enter');
return function (e) {
let directory = readcentraldirectory(arr, e);
let entry = readentry(arr, directory.file_entry_offset, inflate);
return entry;
};
}
/**
*
* @param {*} arr : UInt8Array
*/
function cdr(arr) {
//console.log('cdr enter');
return searchrecord(arr, [0x50, 0x4B, 0x01, 0x02]);
}
/**
*
* @param {*} arr : UInt8Array
* @param {*} inp : Array, the data to match
*/
function searchrecord(arr, inp) {
//console.log('searchrecord enter');
let ret = [];
for (let i = 0; i < arr.length; i++) {
let found = search(arr, inp, i);
if (found > -1) {
i = found;
ret.push(found)
}
}
return ret;
}
/**
*
* @param {*} arr : UInt8Array
* @param {*} inp : Array, the data to match
* @param {*} off : offset to start search
*/
function search(arr, inp, off) {
//console.log('search enter');
let start = off;
while (arr.length - start > inp.length) {
if (inp.every((v, i) => v === arr[start + i])) return start;
start++;
}
return -1;
//return buf.indexOf(Buffer.from(inp), off);
}
/**
*
* @param {*} arr : UInt8Array
* @param {*} loc : location to start
* @param {*} inflate : function implemented the inflate action
*/
function readentry(arr, loc, inflate) {
//console.log('readentry enter');
let version_required = readUInt16LE(arr, loc + 4);
let flags = readUInt16LE(arr, loc + 6);
let compression_method = readUInt16LE(arr, loc + 8);
let last_modified_time = readUInt16LE(arr, loc + 10);
let last_modified_date = readUInt16LE(arr, loc + 12);
let crc32 = readUInt32LE(arr, loc + 14);
let compressed_size = readUInt32LE(arr, loc + 18);
let uncompressed_size = readUInt32LE(arr, loc + 22);
let n = readUInt16LE(arr, loc + 26);
let m = readUInt16LE(arr, loc + 28);
//let file_name = arr.toString('utf8', loc + 30, loc + 30 + n);
let file_name = uintToString(arr.slice(loc + 30, loc + 30 + n));
let data = arr.slice(loc + 30 + n + m, loc + 30 + n + m + compressed_size - 1);
let content = compression_method === 8 ? inflate(data) : data;
return {
version_required,
flags,
compression_method,
last_modified_time,
last_modified_date,
crc32,
compressed_size,
uncompressed_size,
n,
m,
file_name,
content
};
}
/**
*
* @param {*} arr : UInt8Array
* @param {*} loc : location to start
*/
function readcentraldirectory(arr, loc) {
//console.log('readcentraldirectory enter');
let version_made = readUInt16LE(arr, loc + 4);
let version_required = readUInt16LE(arr, loc + 6);
let flags = readUInt16LE(arr, loc + 8);
let compression_method = readUInt16LE(arr, loc + 10);
let last_modified_time = readUInt16LE(arr, loc + 12);
let last_modified_date = readUInt16LE(arr, loc + 14);
let crc32 = readUInt32LE(arr, loc + 16);
let compressed_size = readUInt32LE(arr, loc + 20);
let uncompressed_size = readUInt32LE(arr, loc + 24);
let n = readUInt16LE(arr, loc + 28);
let m = readUInt16LE(arr, loc + 30);
let k = readUInt16LE(arr, loc + 32);
let disk_number = readUInt16LE(arr, loc + 34);
let internal_file_attributes = readUInt16LE(arr, loc + 36);
let external_file_attributes = readUInt32LE(arr, loc + 38);
let file_entry_offset = readUInt32LE(arr, loc + 42);
//let file_name = buf.slice('utf8', loc + 46, loc + 46 + n);
let file_name = uintToString(arr.slice(loc + 46, loc + 46 + n));
let extra_field = arr.slice(loc + 46 + n, loc + 46 + n + m);
let file_comment = arr.slice(loc + 46 + n + m, loc + 46 + n + m + k);
return {
version_made,
version_required,
flags,
compression_method,
last_modified_time,
last_modified_date,
crc32,
compressed_size,
uncompressed_size,
n,
m,
k,
disk_number,
internal_file_attributes,
external_file_attributes,
file_entry_offset,
file_name,
extra_field,
file_comment
};
}
/**
*
* @param {*} arr : UInt8Array
* @param {*} loc : location to start
*/
function readUInt16LE(arr, loc) {
let view = new DataView(arr.buffer);
return view.getUint16(loc, true);
}
/**
*
* @param {*} arr : UInt8Array
* @param {*} loc : location to start
*/
function readUInt32LE(arr, loc) {
let view = new DataView(arr.buffer);
return view.getUint32(loc, true);
}
/**
* convert UInt8Array to UTF8 String
* @param {*} uintArray : UInt8Array
*/
function uintToString(uintArray) {
var encodedString = String.fromCharCode.apply(null, uintArray),
decodedString = decodeURIComponent(escape(encodedString));
return decodedString;
}
function arrayBufferToBase64(bytes) {
var binary = '';
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
function stringToHtmlEntity (str) {
return str.replace(
/[\u00A0-\u9999<>\&]/gim,
function (i) {
return '&#' + i.charCodeAt(0) + ';';
});
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment