1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/modules/ZipUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,223 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = [ "ZipUtils" ]; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cr = Components.results; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 + 1.18 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", 1.19 + "resource://gre/modules/FileUtils.jsm"); 1.20 +XPCOMUtils.defineLazyModuleGetter(this, "OS", 1.21 + "resource://gre/modules/osfile.jsm"); 1.22 +XPCOMUtils.defineLazyModuleGetter(this, "Promise", 1.23 + "resource://gre/modules/Promise.jsm"); 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "Task", 1.25 + "resource://gre/modules/Task.jsm"); 1.26 + 1.27 + 1.28 +// The maximum amount of file data to buffer at a time during file extraction 1.29 +const EXTRACTION_BUFFER = 1024 * 512; 1.30 + 1.31 + 1.32 +/** 1.33 + * Asynchronously writes data from an nsIInputStream to an OS.File instance. 1.34 + * The source stream and OS.File are closed regardless of whether the operation 1.35 + * succeeds or fails. 1.36 + * Returns a promise that will be resolved when complete. 1.37 + * 1.38 + * @param aPath 1.39 + * The name of the file being extracted for logging purposes. 1.40 + * @param aStream 1.41 + * The source nsIInputStream. 1.42 + * @param aFile 1.43 + * The open OS.File instance to write to. 1.44 + */ 1.45 +function saveStreamAsync(aPath, aStream, aFile) { 1.46 + let deferred = Promise.defer(); 1.47 + 1.48 + // Read the input stream on a background thread 1.49 + let sts = Cc["@mozilla.org/network/stream-transport-service;1"]. 1.50 + getService(Ci.nsIStreamTransportService); 1.51 + let transport = sts.createInputTransport(aStream, -1, -1, true); 1.52 + let input = transport.openInputStream(0, 0, 0) 1.53 + .QueryInterface(Ci.nsIAsyncInputStream); 1.54 + let source = Cc["@mozilla.org/binaryinputstream;1"]. 1.55 + createInstance(Ci.nsIBinaryInputStream); 1.56 + source.setInputStream(input); 1.57 + 1.58 + let data = new Uint8Array(EXTRACTION_BUFFER); 1.59 + 1.60 + function readFailed(error) { 1.61 + try { 1.62 + aStream.close(); 1.63 + } 1.64 + catch (e) { 1.65 + logger.error("Failed to close JAR stream for " + aPath); 1.66 + } 1.67 + 1.68 + aFile.close().then(function() { 1.69 + deferred.reject(error); 1.70 + }, function(e) { 1.71 + logger.error("Failed to close file for " + aPath); 1.72 + deferred.reject(error); 1.73 + }); 1.74 + } 1.75 + 1.76 + function readData() { 1.77 + try { 1.78 + let count = Math.min(source.available(), data.byteLength); 1.79 + source.readArrayBuffer(count, data.buffer); 1.80 + 1.81 + aFile.write(data, { bytes: count }).then(function() { 1.82 + input.asyncWait(readData, 0, 0, Services.tm.currentThread); 1.83 + }, readFailed); 1.84 + } 1.85 + catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) { 1.86 + deferred.resolve(aFile.close()); 1.87 + } 1.88 + catch (e) { 1.89 + readFailed(e); 1.90 + } 1.91 + } 1.92 + 1.93 + input.asyncWait(readData, 0, 0, Services.tm.currentThread); 1.94 + 1.95 + return deferred.promise; 1.96 +} 1.97 + 1.98 + 1.99 +this.ZipUtils = { 1.100 + 1.101 + /** 1.102 + * Asynchronously extracts files from a ZIP file into a directory. 1.103 + * Returns a promise that will be resolved when the extraction is complete. 1.104 + * 1.105 + * @param aZipFile 1.106 + * The source ZIP file that contains the add-on. 1.107 + * @param aDir 1.108 + * The nsIFile to extract to. 1.109 + */ 1.110 + extractFilesAsync: function ZipUtils_extractFilesAsync(aZipFile, aDir) { 1.111 + let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. 1.112 + createInstance(Ci.nsIZipReader); 1.113 + 1.114 + try { 1.115 + zipReader.open(aZipFile); 1.116 + } 1.117 + catch (e) { 1.118 + return Promise.reject(e); 1.119 + } 1.120 + 1.121 + return Task.spawn(function() { 1.122 + // Get all of the entries in the zip and sort them so we create directories 1.123 + // before files 1.124 + let entries = zipReader.findEntries(null); 1.125 + let names = []; 1.126 + while (entries.hasMore()) 1.127 + names.push(entries.getNext()); 1.128 + names.sort(); 1.129 + 1.130 + for (let name of names) { 1.131 + let entryName = name; 1.132 + let zipentry = zipReader.getEntry(name); 1.133 + let path = OS.Path.join(aDir.path, ...name.split("/")); 1.134 + 1.135 + if (zipentry.isDirectory) { 1.136 + try { 1.137 + yield OS.File.makeDir(path); 1.138 + } 1.139 + catch (e) { 1.140 + dump("extractFilesAsync: failed to create directory " + path + "\n"); 1.141 + throw e; 1.142 + } 1.143 + } 1.144 + else { 1.145 + let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE }; 1.146 + try { 1.147 + let file = yield OS.File.open(path, { truncate: true }, options); 1.148 + if (zipentry.realSize == 0) 1.149 + yield file.close(); 1.150 + else 1.151 + yield saveStreamAsync(path, zipReader.getInputStream(entryName), file); 1.152 + } 1.153 + catch (e) { 1.154 + dump("extractFilesAsync: failed to extract file " + path + "\n"); 1.155 + throw e; 1.156 + } 1.157 + } 1.158 + } 1.159 + 1.160 + zipReader.close(); 1.161 + }).then(null, (e) => { 1.162 + zipReader.close(); 1.163 + throw e; 1.164 + }); 1.165 + }, 1.166 + 1.167 + /** 1.168 + * Extracts files from a ZIP file into a directory. 1.169 + * 1.170 + * @param aZipFile 1.171 + * The source ZIP file that contains the add-on. 1.172 + * @param aDir 1.173 + * The nsIFile to extract to. 1.174 + */ 1.175 + extractFiles: function ZipUtils_extractFiles(aZipFile, aDir) { 1.176 + function getTargetFile(aDir, entry) { 1.177 + let target = aDir.clone(); 1.178 + entry.split("/").forEach(function(aPart) { 1.179 + target.append(aPart); 1.180 + }); 1.181 + return target; 1.182 + } 1.183 + 1.184 + let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]. 1.185 + createInstance(Ci.nsIZipReader); 1.186 + zipReader.open(aZipFile); 1.187 + 1.188 + try { 1.189 + // create directories first 1.190 + let entries = zipReader.findEntries("*/"); 1.191 + while (entries.hasMore()) { 1.192 + let entryName = entries.getNext(); 1.193 + let target = getTargetFile(aDir, entryName); 1.194 + if (!target.exists()) { 1.195 + try { 1.196 + target.create(Ci.nsIFile.DIRECTORY_TYPE, 1.197 + FileUtils.PERMS_DIRECTORY); 1.198 + } 1.199 + catch (e) { 1.200 + dump("extractFiles: failed to create target directory for extraction file = " + target.path + "\n"); 1.201 + } 1.202 + } 1.203 + } 1.204 + 1.205 + entries = zipReader.findEntries(null); 1.206 + while (entries.hasMore()) { 1.207 + let entryName = entries.getNext(); 1.208 + let target = getTargetFile(aDir, entryName); 1.209 + if (target.exists()) 1.210 + continue; 1.211 + 1.212 + zipReader.extract(entryName, target); 1.213 + try { 1.214 + target.permissions |= FileUtils.PERMS_FILE; 1.215 + } 1.216 + catch (e) { 1.217 + dump("Failed to set permissions " + aPermissions.toString(8) + " on " + target.path + "\n"); 1.218 + } 1.219 + } 1.220 + } 1.221 + finally { 1.222 + zipReader.close(); 1.223 + } 1.224 + } 1.225 + 1.226 +};