michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ "ZipUtils" ]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "OS", michael@0: "resource://gre/modules/osfile.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: michael@0: michael@0: // The maximum amount of file data to buffer at a time during file extraction michael@0: const EXTRACTION_BUFFER = 1024 * 512; michael@0: michael@0: michael@0: /** michael@0: * Asynchronously writes data from an nsIInputStream to an OS.File instance. michael@0: * The source stream and OS.File are closed regardless of whether the operation michael@0: * succeeds or fails. michael@0: * Returns a promise that will be resolved when complete. michael@0: * michael@0: * @param aPath michael@0: * The name of the file being extracted for logging purposes. michael@0: * @param aStream michael@0: * The source nsIInputStream. michael@0: * @param aFile michael@0: * The open OS.File instance to write to. michael@0: */ michael@0: function saveStreamAsync(aPath, aStream, aFile) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: // Read the input stream on a background thread michael@0: let sts = Cc["@mozilla.org/network/stream-transport-service;1"]. michael@0: getService(Ci.nsIStreamTransportService); michael@0: let transport = sts.createInputTransport(aStream, -1, -1, true); michael@0: let input = transport.openInputStream(0, 0, 0) michael@0: .QueryInterface(Ci.nsIAsyncInputStream); michael@0: let source = Cc["@mozilla.org/binaryinputstream;1"]. michael@0: createInstance(Ci.nsIBinaryInputStream); michael@0: source.setInputStream(input); michael@0: michael@0: let data = new Uint8Array(EXTRACTION_BUFFER); michael@0: michael@0: function readFailed(error) { michael@0: try { michael@0: aStream.close(); michael@0: } michael@0: catch (e) { michael@0: logger.error("Failed to close JAR stream for " + aPath); michael@0: } michael@0: michael@0: aFile.close().then(function() { michael@0: deferred.reject(error); michael@0: }, function(e) { michael@0: logger.error("Failed to close file for " + aPath); michael@0: deferred.reject(error); michael@0: }); michael@0: } michael@0: michael@0: function readData() { michael@0: try { michael@0: let count = Math.min(source.available(), data.byteLength); michael@0: source.readArrayBuffer(count, data.buffer); michael@0: michael@0: aFile.write(data, { bytes: count }).then(function() { michael@0: input.asyncWait(readData, 0, 0, Services.tm.currentThread); michael@0: }, readFailed); michael@0: } michael@0: catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) { michael@0: deferred.resolve(aFile.close()); michael@0: } michael@0: catch (e) { michael@0: readFailed(e); michael@0: } michael@0: } michael@0: michael@0: input.asyncWait(readData, 0, 0, Services.tm.currentThread); michael@0: michael@0: return deferred.promise; michael@0: } michael@0: michael@0: michael@0: this.ZipUtils = { michael@0: michael@0: /** michael@0: * Asynchronously extracts files from a ZIP file into a directory. michael@0: * Returns a promise that will be resolved when the extraction is complete. michael@0: * michael@0: * @param aZipFile michael@0: * The source ZIP file that contains the add-on. michael@0: * @param aDir michael@0: * The nsIFile to extract to. michael@0: */ michael@0: extractFilesAsync: function ZipUtils_extractFilesAsync(aZipFile, aDir) { michael@0: let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. michael@0: createInstance(Ci.nsIZipReader); michael@0: michael@0: try { michael@0: zipReader.open(aZipFile); michael@0: } michael@0: catch (e) { michael@0: return Promise.reject(e); michael@0: } michael@0: michael@0: return Task.spawn(function() { michael@0: // Get all of the entries in the zip and sort them so we create directories michael@0: // before files michael@0: let entries = zipReader.findEntries(null); michael@0: let names = []; michael@0: while (entries.hasMore()) michael@0: names.push(entries.getNext()); michael@0: names.sort(); michael@0: michael@0: for (let name of names) { michael@0: let entryName = name; michael@0: let zipentry = zipReader.getEntry(name); michael@0: let path = OS.Path.join(aDir.path, ...name.split("/")); michael@0: michael@0: if (zipentry.isDirectory) { michael@0: try { michael@0: yield OS.File.makeDir(path); michael@0: } michael@0: catch (e) { michael@0: dump("extractFilesAsync: failed to create directory " + path + "\n"); michael@0: throw e; michael@0: } michael@0: } michael@0: else { michael@0: let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE }; michael@0: try { michael@0: let file = yield OS.File.open(path, { truncate: true }, options); michael@0: if (zipentry.realSize == 0) michael@0: yield file.close(); michael@0: else michael@0: yield saveStreamAsync(path, zipReader.getInputStream(entryName), file); michael@0: } michael@0: catch (e) { michael@0: dump("extractFilesAsync: failed to extract file " + path + "\n"); michael@0: throw e; michael@0: } michael@0: } michael@0: } michael@0: michael@0: zipReader.close(); michael@0: }).then(null, (e) => { michael@0: zipReader.close(); michael@0: throw e; michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Extracts files from a ZIP file into a directory. michael@0: * michael@0: * @param aZipFile michael@0: * The source ZIP file that contains the add-on. michael@0: * @param aDir michael@0: * The nsIFile to extract to. michael@0: */ michael@0: extractFiles: function ZipUtils_extractFiles(aZipFile, aDir) { michael@0: function getTargetFile(aDir, entry) { michael@0: let target = aDir.clone(); michael@0: entry.split("/").forEach(function(aPart) { michael@0: target.append(aPart); michael@0: }); michael@0: return target; michael@0: } michael@0: michael@0: let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. michael@0: createInstance(Ci.nsIZipReader); michael@0: zipReader.open(aZipFile); michael@0: michael@0: try { michael@0: // create directories first michael@0: let entries = zipReader.findEntries("*/"); michael@0: while (entries.hasMore()) { michael@0: let entryName = entries.getNext(); michael@0: let target = getTargetFile(aDir, entryName); michael@0: if (!target.exists()) { michael@0: try { michael@0: target.create(Ci.nsIFile.DIRECTORY_TYPE, michael@0: FileUtils.PERMS_DIRECTORY); michael@0: } michael@0: catch (e) { michael@0: dump("extractFiles: failed to create target directory for extraction file = " + target.path + "\n"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: entries = zipReader.findEntries(null); michael@0: while (entries.hasMore()) { michael@0: let entryName = entries.getNext(); michael@0: let target = getTargetFile(aDir, entryName); michael@0: if (target.exists()) michael@0: continue; michael@0: michael@0: zipReader.extract(entryName, target); michael@0: try { michael@0: target.permissions |= FileUtils.PERMS_FILE; michael@0: } michael@0: catch (e) { michael@0: dump("Failed to set permissions " + aPermissions.toString(8) + " on " + target.path + "\n"); michael@0: } michael@0: } michael@0: } michael@0: finally { michael@0: zipReader.close(); michael@0: } michael@0: } michael@0: michael@0: };