gecko-dev/toolkit/modules/ZipUtils.jsm

213 lines
6.5 KiB
JavaScript

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