// Copyright (c) 2017 Matthew Brennan Jones <matthew.brennan.jones@gmail.com> // This software is licensed under a MIT License // https://github.com/workhorsy/uncompress.js "use strict"; function loadScript(url, cb) { // Window if (typeof window === 'object') { let script = document.createElement('script'); script.type = "text/javascript"; script.src = url; script.onload = function() { if (cb) cb(); }; document.head.appendChild(script); // Web Worker } else if (typeof importScripts === 'function') { importScripts(url); if (cb) cb(); } } function currentScriptPath() { // NOTE: document.currentScript does not work in a Web Worker // So we have to parse a stack trace maually try { throw new Error(''); } catch(e) { let stack = e.stack; let line = null; // Chrome and IE if (stack.indexOf('@') !== -1) { line = stack.split('@')[1].split('\n')[0]; // Firefox } else { line = stack.split('(')[1].split(')')[0]; } line = line.substring(0, line.lastIndexOf('/')) + '/'; return line; } } // This is used by libunrar.js to load libunrar.js.mem let unrarMemoryFileLocation = null; let g_on_loaded_cb = null; (function() { let _loaded_archive_formats = []; // Polyfill for missing array slice method (IE 11) if (typeof Uint8Array !== 'undefined') { if (! Uint8Array.prototype.slice) { Uint8Array.prototype.slice = function(start, end) { let retval = new Uint8Array(end - start); let j = 0; for (let i=start; i<end; ++i) { retval[j] = this[i]; j++; } return retval; }; } } // FIXME: This function is super inefficient function saneJoin(array, separator) { let retval = ''; for (let i=0; i<array.length; ++i) { if (i === 0) { retval += array[i]; } else { retval += separator + array[i]; } } return retval; } function saneMap(array, cb) { let retval = new Array(array.length); for (let i=0; i<retval.length; ++i) { retval[i] = cb(array[i]); } return retval; } function loadArchiveFormats(formats, cb) { // Get the path of the current script let path = currentScriptPath(); let load_counter = 0; let checkForLoadDone = function() { load_counter++; // Get the total number of loads before we are done loading // If loading RAR in a Window, have 1 extra load. let load_total = formats.length; if (formats.indexOf('rar') !== -1 && typeof window === 'object') { load_total++; } // run the callback if the last script has loaded if (load_counter === load_total) { cb(); } }; g_on_loaded_cb = checkForLoadDone; // Load the formats formats.forEach(function(archive_format) { // Skip this format if it is already loaded if (_loaded_archive_formats.indexOf(archive_format) !== -1) { return; } // Load the archive format switch (archive_format) { case 'rar': unrarMemoryFileLocation = path + 'libunrar.js.mem'; loadScript(path + 'libunrar.js', checkForLoadDone); _loaded_archive_formats.push(archive_format); break; case 'zip': loadScript(path + 'jszip.js', checkForLoadDone); _loaded_archive_formats.push(archive_format); break; case 'tar': loadScript(path + 'libuntar.js', checkForLoadDone); _loaded_archive_formats.push(archive_format); break; default: throw new Error("Unknown archive format '" + archive_format + "'."); } }); } function archiveOpenFile(array_buffer, cb) { let file_name = "Hugo"; //file.name; let password = null; try { let archive = archiveOpenArrayBuffer(file_name, password, array_buffer); cb(archive, null); } catch(e) { cb(null, e); } } function archiveOpenArrayBuffer(file_name, password, array_buffer) { // Get the archive type let archive_type = null; if (isRarFile(array_buffer)) { archive_type = 'rar'; } else if(isZipFile(array_buffer)) { archive_type = 'zip'; } else if(isTarFile(array_buffer)) { archive_type = 'tar'; } else { throw new Error("The archive type is unknown"); } // Make sure the archive format is loaded if (_loaded_archive_formats.indexOf(archive_type) === -1) { throw new Error("The archive format '" + archive_type + "' is not loaded."); } // Get the entries let handle = null; let entries = []; try { switch (archive_type) { case 'rar': handle = _rarOpen(file_name, password, array_buffer); entries = _rarGetEntries(handle); break; case 'zip': handle = _zipOpen(file_name, password, array_buffer); entries = _zipGetEntries(handle); break; case 'tar': handle = _tarOpen(file_name, password, array_buffer); entries = _tarGetEntries(handle); break; } } catch(e) { throw new Error("Failed to open '" + archive_type + "' archive."); } // Sort the entries by name entries.sort(function(a, b) { if(a.name < b.name) return -1; if(a.name > b.name) return 1; return 0; }); // Return the archive object return { file_name: file_name, archive_type: archive_type, array_buffer: array_buffer, entries: entries, handle: handle }; } function archiveClose(archive) { archive.file_name = null; archive.archive_type = null; archive.array_buffer = null; archive.entries = null; archive.handle = null; } function _rarOpen(file_name, password, array_buffer) { // Create an array of rar files let rar_files = [{ name: file_name, size: array_buffer.byteLength, type: '', content: new Uint8Array(array_buffer) }]; // Return rar handle return { file_name: file_name, array_buffer: array_buffer, password: password, rar_files: rar_files }; } function _zipOpen(file_name, password, array_buffer) { let zip = new JSZip(array_buffer); // Return zip handle return { file_name: file_name, array_buffer: array_buffer, password: password, zip: zip }; } function _tarOpen(file_name, password, array_buffer) { // Return tar handle return { file_name: file_name, array_buffer: array_buffer, password: password }; } function _rarGetEntries(rar_handle) { // Get the entries let info = readRARFileNames(rar_handle.rar_files, rar_handle.password); let entries = []; Object.keys(info).forEach(function(i) { let name = info[i].name; let is_file = info[i].is_file; if (is_file) { entries.push({ name: name, is_file: is_file, // info[i].is_file, size_compressed: info[i].size_compressed, size_uncompressed: info[i].size_uncompressed, readData: function (cb) { setTimeout(function () { if (is_file) { try { readRARContent(rar_handle.rar_files, rar_handle.password, name, cb); } catch (e) { cb(null, e); } } else { cb(null, null); } }, 0); } }); } }); return entries; } function _zipGetEntries(zip_handle) { let zip = zip_handle.zip; // Get all the entries let entries = []; Object.keys(zip.files).forEach(function(i) { let zip_entry = zip.files[i]; let name = zip_entry.name; let is_file = ! zip_entry.dir; let size_compressed = zip_entry._data ? zip_entry._data.compressedSize : 0; let size_uncompressed = zip_entry._data ? zip_entry._data.uncompressedSize : 0; if (is_file) { entries.push({ name: name, is_file: is_file, size_compressed: size_compressed, size_uncompressed: size_uncompressed, readData: function (cb) { setTimeout(function () { if (is_file) { let data = zip_entry.asArrayBuffer(); cb(data, null); } else { cb(null, null); } }, 0); } }); } }); return entries; } function _tarGetEntries(tar_handle) { let tar_entries = tarGetEntries(tar_handle.file_name, tar_handle.array_buffer); // Get all the entries let entries = []; tar_entries.forEach(function(entry) { let name = entry.name; let is_file = entry.is_file; let size = entry.size; if (is_file) { entries.push({ name: name, is_file: is_file, size_compressed: size, size_uncompressed: size, readData: function (cb) { setTimeout(function () { if (is_file) { let data = tarGetEntryData(entry, tar_handle.array_buffer); cb(data.buffer, null); } else { cb(null, null); } }, 0); } }); } }); return entries; } function isRarFile(array_buffer) { // The three styles of RAR headers let rar_header1 = saneJoin([0x52, 0x45, 0x7E, 0x5E], ', '); // old let rar_header2 = saneJoin([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00], ', '); // 1.5 to 4.0 let rar_header3 = saneJoin([0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x01, 0x00], ', '); // 5.0 // Just return false if the file is smaller than the header if (array_buffer.byteLength < 8) { return false; } // Return true if the header matches one of the RAR headers let header1 = saneJoin(new Uint8Array(array_buffer).slice(0, 4), ', '); let header2 = saneJoin(new Uint8Array(array_buffer).slice(0, 7), ', '); let header3 = saneJoin(new Uint8Array(array_buffer).slice(0, 8), ', '); return (header1 === rar_header1 || header2 === rar_header2 || header3 === rar_header3); } function isZipFile(array_buffer) { // The ZIP header let zip_header = saneJoin([0x50, 0x4b, 0x03, 0x04], ', '); // Just return false if the file is smaller than the header if (array_buffer.byteLength < 4) { return false; } // Return true if the header matches the ZIP header let header = saneJoin(new Uint8Array(array_buffer).slice(0, 4), ', '); return (header === zip_header); } function isTarFile(array_buffer) { // The TAR header let tar_header = saneJoin(['u', 's', 't', 'a', 'r'], ', '); // Just return false if the file is smaller than the header size if (array_buffer.byteLength < 512) { return false; } // Return true if the header matches the TAR header let header = saneJoin(saneMap(new Uint8Array(array_buffer).slice(257, 257 + 5), String.fromCharCode), ', '); return (header === tar_header); } // Figure out if we are running in a Window or Web Worker let scope = null; if (typeof window === 'object') { scope = window; } else if (typeof importScripts === 'function') { scope = self; } // Set exports scope.loadArchiveFormats = loadArchiveFormats; scope.archiveOpenFile = archiveOpenFile; scope.archiveOpenArrayBuffer = archiveOpenArrayBuffer; scope.archiveClose = archiveClose; scope.isRarFile = isRarFile; scope.isZipFile = isZipFile; scope.isTarFile = isTarFile; scope.saneJoin = saneJoin; scope.saneMap = saneMap; })();