зеркало из https://github.com/mozilla/gecko-dev.git
Bug 974498 - Refactor XPIProvider's private zip utilities into a ZipUtils.jsm. r=Mossop
This commit is contained in:
Родитель
e0cac894b2
Коммит
f44d6472e8
|
@ -0,0 +1,223 @@
|
|||
/* 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");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.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) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// 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);
|
||||
|
||||
let data = Uint8Array(EXTRACTION_BUFFER);
|
||||
|
||||
function readFailed(error) {
|
||||
try {
|
||||
aStream.close();
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to close JAR stream for " + aPath);
|
||||
}
|
||||
|
||||
aFile.close().then(function() {
|
||||
deferred.reject(error);
|
||||
}, function(e) {
|
||||
logger.error("Failed to close file for " + aPath);
|
||||
deferred.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function readData() {
|
||||
try {
|
||||
let count = Math.min(source.available(), data.byteLength);
|
||||
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) {
|
||||
deferred.resolve(aFile.close());
|
||||
}
|
||||
catch (e) {
|
||||
readFailed(e);
|
||||
}
|
||||
}
|
||||
|
||||
input.asyncWait(readData, 0, 0, Services.tm.currentThread);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
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 Task.spawn(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 {
|
||||
yield 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 = yield OS.File.open(path, { truncate: true }, options);
|
||||
if (zipentry.realSize == 0)
|
||||
yield file.close();
|
||||
else
|
||||
yield 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 " + aPermissions.toString(8) + " on " + target.path + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
zipReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
|
@ -51,6 +51,7 @@ EXTRA_JS_MODULES += [
|
|||
'Task.jsm',
|
||||
'TelemetryTimestamps.jsm',
|
||||
'Timer.jsm',
|
||||
'ZipUtils.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const ARCHIVE = "zips/zen.zip";
|
||||
const SUBDIR = "zen";
|
||||
const ENTRIES = ["beyond.txt", "waterwood.txt"];
|
||||
|
||||
Components.utils.import("resource://gre/modules/ZipUtils.jsm");
|
||||
Components.utils.import("resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
|
||||
const archive = do_get_file(ARCHIVE, false);
|
||||
const dir = do_get_profile().clone();
|
||||
dir.append("test_ZipUtils");
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function ensureExtracted(target) {
|
||||
target.append(SUBDIR);
|
||||
do_check_true(target.exists());
|
||||
|
||||
for (let i = 0; i < ENTRIES.length; i++) {
|
||||
let entry = target.clone();
|
||||
entry.append(ENTRIES[i]);
|
||||
do_print("ENTRY " + entry.path);
|
||||
do_check_true(entry.exists());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
add_task(function test_extractFiles() {
|
||||
let target = dir.clone();
|
||||
target.append("test_extractFiles");
|
||||
|
||||
try {
|
||||
ZipUtils.extractFiles(archive, target);
|
||||
} catch(e) {
|
||||
do_throw("Failed to extract synchronously!");
|
||||
}
|
||||
|
||||
ensureExtracted(target);
|
||||
});
|
||||
|
||||
add_task(function test_extractFilesAsync() {
|
||||
let target = dir.clone();
|
||||
target.append("test_extractFilesAsync");
|
||||
target.create(Components.interfaces.nsIFile.DIRECTORY_TYPE,
|
||||
FileUtils.PERMS_DIRECTORY);
|
||||
|
||||
yield ZipUtils.extractFilesAsync(archive, target).then(
|
||||
function success() {
|
||||
do_print("SUCCESS");
|
||||
ensureExtracted(target);
|
||||
},
|
||||
function failure() {
|
||||
do_print("FAILURE");
|
||||
do_throw("Failed to extract asynchronously!");
|
||||
}
|
||||
);
|
||||
});
|
|
@ -5,6 +5,7 @@ support-files =
|
|||
propertyLists/bug710259_propertyListBinary.plist
|
||||
propertyLists/bug710259_propertyListXML.plist
|
||||
chromeappsstore.sqlite
|
||||
zips/zen.zip
|
||||
|
||||
[test_AsyncShutdown.js]
|
||||
[test_DeferredTask.js]
|
||||
|
@ -22,4 +23,4 @@ support-files =
|
|||
[test_task.js]
|
||||
[test_TelemetryTimestamps.js]
|
||||
[test_timer.js]
|
||||
|
||||
[test_ZipUtils.js]
|
||||
|
|
Двоичный файл не отображается.
|
@ -23,6 +23,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
|
|||
"resource://gre/modules/LightweightThemeManager.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
|
||||
"resource://gre/modules/ZipUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
|
||||
|
@ -112,9 +114,6 @@ const PREFIX_NS_EM = "http://www.mozilla.org/2004/em-rdf#";
|
|||
|
||||
const TOOLKIT_ID = "toolkit@mozilla.org";
|
||||
|
||||
// The maximum amount of file data to buffer at a time during file extraction
|
||||
const EXTRACTION_BUFFER = 1024 * 512;
|
||||
|
||||
// The value for this is in Makefile.in
|
||||
#expand const DB_SCHEMA = __MOZ_EXTENSIONS_DB_SCHEMA__;
|
||||
|
||||
|
@ -1128,199 +1127,6 @@ function getTemporaryFile() {
|
|||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// 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);
|
||||
|
||||
let data = Uint8Array(EXTRACTION_BUFFER);
|
||||
|
||||
function readFailed(error) {
|
||||
try {
|
||||
aStream.close();
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to close JAR stream for " + aPath);
|
||||
}
|
||||
|
||||
aFile.close().then(function() {
|
||||
deferred.reject(error);
|
||||
}, function(e) {
|
||||
logger.error("Failed to close file for " + aPath);
|
||||
deferred.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
function readData() {
|
||||
try {
|
||||
let count = Math.min(source.available(), data.byteLength);
|
||||
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) {
|
||||
deferred.resolve(aFile.close());
|
||||
}
|
||||
catch (e) {
|
||||
readFailed(e);
|
||||
}
|
||||
}
|
||||
|
||||
input.asyncWait(readData, 0, 0, Services.tm.currentThread);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
function 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 Task.spawn(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 {
|
||||
yield OS.File.makeDir(path);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("extractFilesAsync: failed to create directory " + path, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
|
||||
try {
|
||||
let file = yield OS.File.open(path, { truncate: true }, options);
|
||||
if (zipentry.realSize == 0)
|
||||
yield file.close();
|
||||
else
|
||||
yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("extractFilesAsync: failed to extract file " + path, e);
|
||||
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.
|
||||
*/
|
||||
function 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()) {
|
||||
var entryName = entries.getNext();
|
||||
let target = getTargetFile(aDir, entryName);
|
||||
if (!target.exists()) {
|
||||
try {
|
||||
target.create(Ci.nsIFile.DIRECTORY_TYPE,
|
||||
FileUtils.PERMS_DIRECTORY);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("extractFiles: failed to create target directory for " +
|
||||
"extraction file = " + target.path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
|
||||
target.path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
zipReader.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a zip file's contents are all signed by the same principal.
|
||||
* Directory entries and anything in the META-INF directory are not checked.
|
||||
|
@ -2425,7 +2231,7 @@ var XPIProvider = {
|
|||
}
|
||||
|
||||
try {
|
||||
extractFiles(stagedXPI, targetDir);
|
||||
ZipUtils.extractFiles(stagedXPI, targetDir);
|
||||
}
|
||||
catch (e) {
|
||||
logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
|
||||
|
@ -5551,7 +5357,7 @@ AddonInstall.prototype = {
|
|||
stagedAddon.append(this.addon.id);
|
||||
yield removeAsync(stagedAddon);
|
||||
yield OS.File.makeDir(stagedAddon.path);
|
||||
yield extractFilesAsync(this.file, stagedAddon);
|
||||
yield ZipUtils.extractFilesAsync(this.file, stagedAddon);
|
||||
installedUnpacked = 1;
|
||||
}
|
||||
else {
|
||||
|
|
Загрузка…
Ссылка в новой задаче