Created
August 7, 2020 09:16
-
-
Save fillano/f9ca497b8f403c5dce6ae6ea8c67d00b to your computer and use it in GitHub Desktop.
zip file parser in js (with inflate provided by pako)
This file contains hidden or 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
<!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> |
This file contains hidden or 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
(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