Bug 974498 - Refactor XPIProvider's private zip utilities into a ZipUtils.jsm. r=Mossop

This commit is contained in:
Jan Keromnes 2014-03-11 17:01:29 -04:00
Родитель e0cac894b2
Коммит f44d6472e8
6 изменённых файлов: 294 добавлений и 199 удалений

Просмотреть файл

@ -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]

Двоичные данные
toolkit/modules/tests/xpcshell/zips/zen.zip Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -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 {