gecko-dev/toolkit/modules/ZipUtils.jsm

222 строки
6.2 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/. */
var EXPORTED_SYMBOLS = ["ZipUtils"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"FileUtils",
"resource://gre/modules/FileUtils.jsm"
);
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.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, 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);
});
}
var 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 names = Array.from(zipReader.findEntries(null));
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();
})().catch(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
for (let entryName of zipReader.findEntries("*/")) {
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"
);
}
}
}
for (let entryName of zipReader.findEntries(null)) {
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();
}
},
};