merge fx-team to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-04-23 15:28:22 +02:00
commit 2744d40536
68 changed files with 1638 additions and 708 deletions

View File

@ -22,8 +22,7 @@
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
# don't change CLOBBER for WebIDL changes any more.
Bug 1155718: Merge Bluetooth v1/v2 files for all simple cases
Bug 1038068: Check add-on signatures and refuse to install unsigned or broken add-ons
This patch set renames source files. This requires updating the
build system's dependency information from scratch. The issue has
been reported in bug 1154232.
Not sure why this needs a clobber but tests perma-failed when they don't on
try.

View File

@ -69,6 +69,9 @@ pref("extensions.hotfix.certs.1.sha1Fingerprint", "91:53:98:0C:C1:86:DF:47:8F:35
// See the SCOPE constants in AddonManager.jsm for values to use here.
pref("extensions.autoDisableScopes", 15);
// Don't require signed add-ons by default
pref("xpinstall.signatures.required", false);
// Dictionary download preference
pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");

View File

@ -147,8 +147,8 @@ let gTests = [
Assert.ok(tabOpened);
Assert.equal(tokenData.code, "code1");
Assert.equal(tokenData.state, "state");
Assert.equal(keys.kAr, "kAr");
Assert.equal(keys.kBr, "kBr");
Assert.deepEqual(keys.kAr, {k: "kAr"});
Assert.deepEqual(keys.kBr, {k: "kBr"});
resolve();
};

View File

@ -16,7 +16,9 @@
state: "state",
code: "code1",
closeWindow: "signin",
keys: { kAr: 'kAr', kBr: 'kBr' },
// Keys normally contain more information, but this is enough
// to keep Loop's tests happy.
keys: { kAr: { k: 'kAr' }, kBr: { k: 'kBr' }},
},
},
},

View File

@ -202,10 +202,10 @@ let LoopRoomsInternal = {
* information.
*/
promiseEncryptRoomData: Task.async(function* (roomData) {
// For now, disable encryption/context if context is disabled, or if
// FxA is turned on.
if (!MozLoopService.getLoopPref("contextInConverations.enabled") ||
this.sessionType == LOOP_SESSION_TYPE.FXA) {
// XXX We should only return unencrypted data whilst we're still working
// on context. Once bug 1115340 is fixed, this function should no longer be
// here.
function getUnencryptedData() {
var serverRoomData = extend({}, roomData);
delete serverRoomData.decryptedContext;
@ -218,6 +218,11 @@ let LoopRoomsInternal = {
};
}
// For now, disable encryption/context if context is disabled
if (!MozLoopService.getLoopPref("contextInConverations.enabled")) {
return getUnencryptedData();
}
var newRoomData = extend({}, roomData);
if (!newRoomData.context) {
@ -227,7 +232,17 @@ let LoopRoomsInternal = {
// First get the room key.
let key = yield this.promiseGetOrCreateRoomKey(newRoomData);
newRoomData.context.wrappedKey = yield this.promiseEncryptedRoomKey(key);
try {
newRoomData.context.wrappedKey = yield this.promiseEncryptedRoomKey(key);
}
catch (ex) {
// XXX Bug 1153788 should remove this, then we can remove the whole
// try/catch.
if (ex.message == "FxA re-register not implemented") {
return getUnencryptedData();
}
return Promise.reject(ex);
}
// Now encrypt the actual data.
newRoomData.context.value = yield loopCrypto.encryptBytes(key,
@ -654,8 +669,7 @@ let LoopRoomsInternal = {
};
// If we're not encrypting currently, then only send the roomName.
if (!Services.prefs.getBoolPref("loop.contextInConverations.enabled") ||
this.sessionType == LOOP_SESSION_TYPE.FXA) {
if (!Services.prefs.getBoolPref("loop.contextInConverations.enabled")) {
sendData = {
roomName: newRoomName
};

View File

@ -959,6 +959,9 @@ let MozLoopServiceInternal = {
gFxAOAuthClientPromise = this.promiseFxAOAuthParameters().then(
parameters => {
// Add the fact that we want keys to the parameters.
parameters.keys = true;
try {
gFxAOAuthClient = new FxAccountsOAuthClient({
parameters: parameters,
@ -1031,7 +1034,10 @@ let MozLoopServiceInternal = {
* @param {Deferred} deferred used to resolve the gFxAOAuthClientPromise
* @param {Object} result (with code and state)
*/
_fxAOAuthComplete: function(deferred, result) {
_fxAOAuthComplete: function(deferred, result, keys) {
if (keys.kBr) {
Services.prefs.setCharPref("loop.key.fxa", keys.kBr.k);
}
gFxAOAuthClientPromise = null;
// Note: The state was already verified in FxAccountsOAuthClient.
deferred.resolve(result);
@ -1331,8 +1337,14 @@ this.MozLoopService = {
return new Promise((resolve, reject) => {
if (this.userProfile) {
// We're an FxA user.
// XXX Bug 1153788 will implement this for FxA.
reject(new Error("unimplemented"));
if (Services.prefs.prefHasUserValue("loop.key.fxa")) {
resolve(MozLoopService.getLoopPref("key.fxa"));
return;
}
// XXX If we don't have a key for FxA yet, then simply reject for now.
// We'll create some sign-in/sign-out UX in bug 1153788.
reject(new Error("FxA re-register not implemented"));
return;
}

View File

@ -240,6 +240,18 @@ loop.shared.mixins = (function() {
rootObject.removeEventListener("resize", this.updateVideoContainer);
},
/**
* Resets the dimensions cache, e.g. for when the session is ended, and
* before a new session, so that we always ensure we see an update when a
* new session is started.
*/
resetDimensionsCache: function() {
this._videoDimensionsCache = {
local: {},
remote: {}
};
},
/**
* Whenever the dimensions change of a video stream, this function is called
* by `updateVideoDimensions` to store the new values and notifies the callee

View File

@ -383,6 +383,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.updateVideoContainer();
}
if (nextState.roomState === ROOM_STATES.INIT ||
nextState.roomState === ROOM_STATES.GATHER ||
nextState.roomState === ROOM_STATES.READY) {
this.resetDimensionsCache();
}
// When screen sharing stops.
if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
// Remove the custom screenshare styles on the remote camera.

View File

@ -383,6 +383,12 @@ loop.standaloneRoomViews = (function(mozL10n) {
this.updateVideoContainer();
}
if (nextState.roomState === ROOM_STATES.INIT ||
nextState.roomState === ROOM_STATES.GATHER ||
nextState.roomState === ROOM_STATES.READY) {
this.resetDimensionsCache();
}
// When screen sharing stops.
if (this.state.receivingScreenShare && !nextState.receivingScreenShare) {
// Remove the custom screenshare styles on the remote camera.

View File

@ -7,7 +7,7 @@ support-files =
google_service.sjs
head.js
loop_fxa.sjs
../../../../base/content/test/general/browser_fxa_oauth.html
../../../../base/content/test/general/browser_fxa_oauth_with_keys.html
[browser_CardDavImporter.js]
[browser_fxa_login.js]

View File

@ -134,7 +134,7 @@ function params(request, response) {
*/
function oauth_authorization(request, response) {
response.setStatusLine(request.httpVersion, 302, "Found");
response.setHeader("Location", "browser_fxa_oauth.html");
response.setHeader("Location", "browser_fxa_oauth_with_keys.html");
}
/**

View File

@ -214,6 +214,19 @@ describe("loop.standaloneRoomViews", function() {
sinon.assert.calledOnce(view.updateVideoContainer);
});
it("should reset the video dimensions cache when the gather state is entered", function() {
activeRoomStore.setStoreState({roomState: ROOM_STATES.SESSION_CONNECTED});
var view = mountTestComponent();
activeRoomStore.setStoreState({roomState: ROOM_STATES.GATHER});
expect(view._videoDimensionsCache).eql({
local: {},
remote: {}
});
})
});
describe("#publishStream", function() {

View File

@ -3,6 +3,7 @@
/* global Services, Assert */
const kGuestKeyPref = "loop.key";
const kFxAKeyPref = "loop.key.fxa";
do_register_cleanup(function() {
Services.prefs.clearUserPref(kGuestKeyPref);
@ -33,12 +34,25 @@ add_task(function* test_guestGetKey() {
Assert.equal(key, kFakeKey, "should return existing key");
});
add_task(function* test_fxaGetKey() {
// Set the userProfile to look like we're logged into FxA.
add_task(function* test_fxaGetKnownKey() {
const kFakeKey = "75312468";
// Set the userProfile to look like we're logged into FxA with a key stored.
MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
Services.prefs.setCharPref(kFxAKeyPref, kFakeKey);
let key = yield MozLoopService.promiseProfileEncryptionKey();
Assert.equal(key, kFakeKey, "should return existing key");
});
add_task(function* test_fxaGetKey() {
// Set the userProfile to look like we're logged into FxA without a key stored.
MozLoopServiceInternal.fxAOAuthTokenData = { token_type: "bearer" };
MozLoopServiceInternal.fxAOAuthProfile = { email: "fake@invalid.com" };
Services.prefs.clearUserPref(kFxAKeyPref);
// Currently unimplemented, add a test when we implement the code.
yield Assert.rejects(MozLoopService.promiseProfileEncryptionKey(),
/unimplemented/, "should reject as unimplemented");
/not implemented/, "should reject as unimplemented");
});

View File

@ -69,20 +69,22 @@ addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-on
addonInstallRestartButton=Restart Now
addonInstallRestartButton.accesskey=R
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4, addonError-5):
# #1 is the add-on name, #2 is the host name, #3 is the application name
# #4 is the application version
addonError-1=The add-on could not be downloaded because of a connection failure on #2.
addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
addonError-4=#1 could not be installed because #3 cannot modify the needed file.
addonError-5=#3 has prevented this site from installing an unverified add-on.
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonLocalError-5, addonErrorIncompatible, addonErrorBlocklisted):
# #1 is the add-on name, #3 is the application name, #4 is the application version
addonLocalError-1=This add-on could not be installed because of a filesystem error.
addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
addonLocalError-5=This add-on could not be installed because it has not been verified.
addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.

View File

@ -50,7 +50,6 @@ UNIFIED_SOURCES += [
'src/base/pickle.cc',
'src/base/rand_util.cc',
'src/base/revocable_store.cc',
'src/base/scoped_temp_dir.cc',
'src/base/string_piece.cc',
'src/base/string_util.cc',
'src/base/thread.cc',

View File

@ -139,12 +139,6 @@ void AppendToPath(std::wstring* path, const std::wstring& new_ending) {
path->push_back(FilePath::kSeparators[0]);
path->append(new_ending);
}
bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path,
bool recursive) {
return CopyDirectory(FilePath::FromWStringHack(from_path),
FilePath::FromWStringHack(to_path),
recursive);
}
bool CopyFile(const std::wstring& from_path, const std::wstring& to_path) {
return CopyFile(FilePath::FromWStringHack(from_path),
FilePath::FromWStringHack(to_path));
@ -172,8 +166,8 @@ bool CreateTemporaryFileName(std::wstring* temp_file) {
*temp_file = temp_file_path.ToWStringHack();
return true;
}
bool Delete(const std::wstring& path, bool recursive) {
return Delete(FilePath::FromWStringHack(path), recursive);
bool Delete(const std::wstring& path) {
return Delete(FilePath::FromWStringHack(path));
}
bool DirectoryExists(const std::wstring& path) {
return DirectoryExists(FilePath::FromWStringHack(path));

View File

@ -16,7 +16,6 @@
#include <sys/stat.h>
#elif defined(OS_POSIX)
#include <sys/types.h>
#include <fts.h>
#include <sys/stat.h>
#endif
@ -87,34 +86,17 @@ void ReplaceExtension(std::wstring* file_name, const std::wstring& extension);
// Deletes the given path, whether it's a file or a directory.
// If it's a directory, it's perfectly happy to delete all of the
// directory's contents. Passing true to recursive deletes
// subdirectories and their contents as well.
// directory's contents.
// Returns true if successful, false otherwise.
//
// WARNING: USING THIS WITH recursive==true IS EQUIVALENT
// TO "rm -rf", SO USE WITH CAUTION.
bool Delete(const FilePath& path, bool recursive);
bool Delete(const FilePath& path);
// Deprecated temporary compatibility function.
bool Delete(const std::wstring& path, bool recursive);
bool Delete(const std::wstring& path);
// Copies a single file. Use CopyDirectory to copy directories.
bool CopyFile(const FilePath& from_path, const FilePath& to_path);
// Deprecated temporary compatibility function.
bool CopyFile(const std::wstring& from_path, const std::wstring& to_path);
// Copies the given path, and optionally all subdirectories and their contents
// as well.
// If there are files existing under to_path, always overwrite.
// Returns true if successful, false otherwise.
// Dont't use wildcards on the names, it may stop working without notice.
//
// If you only need to copy a file use CopyFile, it's faster.
bool CopyDirectory(const FilePath& from_path, const FilePath& to_path,
bool recursive);
// Deprecated temporary compatibility function.
bool CopyDirectory(const std::wstring& from_path, const std::wstring& to_path,
bool recursive);
// Returns true if the given path exists on the local filesystem,
// false otherwise.
bool PathExists(const FilePath& path);

View File

@ -8,13 +8,10 @@
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#ifndef ANDROID
#include <fts.h>
#endif
#include <libgen.h>
#include <stdio.h>
#include <string.h>
#include <sys/errno.h>
#include <errno.h>
#include <sys/mman.h>
#define _DARWIN_USE_64_BIT_INODE // Use 64-bit inode data structures
#include <sys/stat.h>
@ -53,7 +50,7 @@ bool AbsolutePath(FilePath* path) {
// which works both with and without the recursive flag. I'm not sure we need
// that functionality. If not, remove from file_util_win.cc, otherwise add it
// here.
bool Delete(const FilePath& path, bool recursive) {
bool Delete(const FilePath& path) {
const char* path_str = path.value().c_str();
struct stat file_info;
int test = stat(path_str, &file_info);
@ -64,174 +61,8 @@ bool Delete(const FilePath& path, bool recursive) {
}
if (!S_ISDIR(file_info.st_mode))
return (unlink(path_str) == 0);
if (!recursive)
return (rmdir(path_str) == 0);
#ifdef ANDROID
// XXX Need ftsless impl for bionic
return false;
#else
bool success = true;
int ftsflags = FTS_PHYSICAL | FTS_NOSTAT;
char top_dir[PATH_MAX];
if (base::strlcpy(top_dir, path_str,
arraysize(top_dir)) >= arraysize(top_dir)) {
return false;
}
char* dir_list[2] = { top_dir, NULL };
FTS* fts = fts_open(dir_list, ftsflags, NULL);
if (fts) {
FTSENT* fts_ent = fts_read(fts);
while (success && fts_ent != NULL) {
switch (fts_ent->fts_info) {
case FTS_DNR:
case FTS_ERR:
// log error
success = false;
continue;
break;
case FTS_DP:
success = (rmdir(fts_ent->fts_accpath) == 0);
break;
case FTS_D:
break;
case FTS_NSOK:
case FTS_F:
case FTS_SL:
case FTS_SLNONE:
success = (unlink(fts_ent->fts_accpath) == 0);
break;
default:
DCHECK(false);
break;
}
fts_ent = fts_read(fts);
}
fts_close(fts);
}
return success;
#endif
}
bool Move(const FilePath& from_path, const FilePath& to_path) {
if (rename(from_path.value().c_str(), to_path.value().c_str()) == 0)
return true;
if (!CopyDirectory(from_path, to_path, true))
return false;
Delete(from_path, true);
return true;
}
bool CopyDirectory(const FilePath& from_path,
const FilePath& to_path,
bool recursive) {
// Some old callers of CopyDirectory want it to support wildcards.
// After some discussion, we decided to fix those callers.
// Break loudly here if anyone tries to do this.
// TODO(evanm): remove this once we're sure it's ok.
DCHECK(to_path.value().find('*') == std::string::npos);
DCHECK(from_path.value().find('*') == std::string::npos);
char top_dir[PATH_MAX];
if (base::strlcpy(top_dir, from_path.value().c_str(),
arraysize(top_dir)) >= arraysize(top_dir)) {
return false;
}
#ifdef ANDROID
// XXX Need ftsless impl for bionic
return false;
#else
char* dir_list[] = { top_dir, NULL };
FTS* fts = fts_open(dir_list, FTS_PHYSICAL | FTS_NOSTAT, NULL);
if (!fts) {
CHROMIUM_LOG(ERROR) << "fts_open failed: " << strerror(errno);
return false;
}
int error = 0;
FTSENT* ent;
while (!error && (ent = fts_read(fts)) != NULL) {
// ent->fts_path is the source path, including from_path, so paste
// the suffix after from_path onto to_path to create the target_path.
std::string suffix(&ent->fts_path[from_path.value().size()]);
// Strip the leading '/' (if any).
if (!suffix.empty()) {
DCHECK_EQ('/', suffix[0]);
suffix.erase(0, 1);
}
const FilePath target_path = to_path.Append(suffix);
switch (ent->fts_info) {
case FTS_D: // Preorder directory.
// If we encounter a subdirectory in a non-recursive copy, prune it
// from the traversal.
if (!recursive && ent->fts_level > 0) {
if (fts_set(fts, ent, FTS_SKIP) != 0)
error = errno;
continue;
}
// Try creating the target dir, continuing on it if it exists already.
// Rely on the user's umask to produce correct permissions.
if (mkdir(target_path.value().c_str(), 0777) != 0) {
if (errno != EEXIST)
error = errno;
}
break;
case FTS_F: // Regular file.
case FTS_NSOK: // File, no stat info requested.
errno = 0;
if (!CopyFile(FilePath(ent->fts_path), target_path))
error = errno ? errno : EINVAL;
break;
case FTS_DP: // Postorder directory.
case FTS_DOT: // "." or ".."
// Skip it.
continue;
case FTS_DC: // Directory causing a cycle.
// Skip this branch.
if (fts_set(fts, ent, FTS_SKIP) != 0)
error = errno;
break;
case FTS_DNR: // Directory cannot be read.
case FTS_ERR: // Error.
case FTS_NS: // Stat failed.
// Abort with the error.
error = ent->fts_errno;
break;
case FTS_SL: // Symlink.
case FTS_SLNONE: // Symlink with broken target.
CHROMIUM_LOG(WARNING) << "CopyDirectory() skipping symbolic link: " <<
ent->fts_path;
continue;
case FTS_DEFAULT: // Some other sort of file.
CHROMIUM_LOG(WARNING) << "CopyDirectory() skipping file of unknown type: " <<
ent->fts_path;
continue;
default:
NOTREACHED();
continue; // Hope for the best!
}
}
// fts_read may have returned NULL and set errno to indicate an error.
if (!error && errno != 0)
error = errno;
if (!fts_close(fts)) {
// If we already have an error, let's use that error instead of the error
// fts_close set.
if (!error)
error = errno;
}
if (error) {
CHROMIUM_LOG(ERROR) << "CopyDirectory(): " << strerror(error);
return false;
}
return true;
#endif
return (rmdir(path_str) == 0);
}
bool PathExists(const FilePath& path) {

View File

@ -27,14 +27,14 @@ bool AbsolutePath(FilePath* path) {
return true;
}
bool Delete(const FilePath& path, bool recursive) {
bool Delete(const FilePath& path) {
if (path.value().length() >= MAX_PATH)
return false;
// If we're not recursing use DeleteFile; it should be faster. DeleteFile
// Use DeleteFile; it should be faster. DeleteFile
// fails if passed a directory though, which is why we fall through on
// failure to the SHFileOperation.
if (!recursive && DeleteFile(path.value().c_str()) != 0)
if (DeleteFile(path.value().c_str()) != 0)
return true;
// SHFILEOPSTRUCT wants the path to be terminated with two NULLs,
@ -48,8 +48,7 @@ bool Delete(const FilePath& path, bool recursive) {
file_operation.wFunc = FO_DELETE;
file_operation.pFrom = double_terminated_path;
file_operation.fFlags = FOF_NOERRORUI | FOF_SILENT | FOF_NOCONFIRMATION;
if (!recursive)
file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
file_operation.fFlags |= FOF_NORECURSION | FOF_FILESONLY;
int err = SHFileOperation(&file_operation);
// Some versions of Windows return ERROR_FILE_NOT_FOUND when
// deleting an empty directory.
@ -98,26 +97,6 @@ bool ShellCopy(const FilePath& from_path, const FilePath& to_path,
return (SHFileOperation(&file_operation) == 0);
}
bool CopyDirectory(const FilePath& from_path, const FilePath& to_path,
bool recursive) {
if (recursive)
return ShellCopy(from_path, to_path, true);
// Instead of creating a new directory, we copy the old one to include the
// security information of the folder as part of the copy.
if (!PathExists(to_path)) {
// Except that Vista fails to do that, and instead do a recursive copy if
// the target directory doesn't exist.
if (win_util::GetWinVersion() >= win_util::WINVERSION_VISTA)
CreateDirectory(to_path);
else
ShellCopy(from_path, to_path, false);
}
FilePath directory = from_path.Append(L"*.*");
return ShellCopy(directory, to_path, false);
}
bool PathExists(const FilePath& path) {
return (GetFileAttributes(path.value().c_str()) != INVALID_FILE_ATTRIBUTES);
}

View File

@ -1,47 +0,0 @@
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/scoped_temp_dir.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "base/string_util.h"
ScopedTempDir::ScopedTempDir() {
}
ScopedTempDir::~ScopedTempDir() {
if (!path_.empty() && !file_util::Delete(path_, true))
CHROMIUM_LOG(ERROR) << "ScopedTempDir unable to delete " << path_.value();
}
bool ScopedTempDir::CreateUniqueTempDir() {
// This "scoped_dir" prefix is only used on Windows and serves as a template
// for the unique name.
if (!file_util::CreateNewTempDirectory(FILE_PATH_LITERAL("scoped_dir"),
&path_))
return false;
return true;
}
bool ScopedTempDir::Set(const FilePath& path) {
DCHECK(path_.empty());
if (!file_util::DirectoryExists(path) &&
!file_util::CreateDirectory(path)) {
return false;
}
path_ = path;
return true;
}
FilePath ScopedTempDir::Take() {
FilePath ret = path_;
path_ = FilePath();
return ret;
}
bool ScopedTempDir::IsValid() const {
return !path_.empty() && file_util::DirectoryExists(path_);
}

View File

@ -1,47 +0,0 @@
// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_SCOPED_TEMP_DIR_H_
#define BASE_SCOPED_TEMP_DIR_H_
// An object representing a temporary / scratch directory that should be cleaned
// up (recursively) when this object goes out of scope. Note that since
// deletion occurs during the destructor, no further error handling is possible
// if the directory fails to be deleted. As a result, deletion is not
// guaranteed by this class.
#include "base/file_path.h"
class ScopedTempDir {
public:
// No directory is owned/created initially.
ScopedTempDir();
// Recursively delete path_
~ScopedTempDir();
// Creates a unique directory in TempPath, and takes ownership of it.
// See file_util::CreateNewTemporaryDirectory.
bool CreateUniqueTempDir();
// Takes ownership of directory at |path|, creating it if necessary.
// Don't call multiple times unless Take() has been called first.
bool Set(const FilePath& path);
// Caller takes ownership of the temporary directory so it won't be destroyed
// when this object goes out of scope.
FilePath Take();
const FilePath& path() const { return path_; }
// Returns true if path_ is non-empty and exists.
bool IsValid() const;
private:
FilePath path_;
DISALLOW_COPY_AND_ASSIGN(ScopedTempDir);
};
#endif // BASE_SCOPED_TEMP_DIR_H_

View File

@ -93,7 +93,7 @@ bool SharedMemory::Delete(const std::wstring& name) {
FilePath path(WideToUTF8(mem_filename));
if (file_util::PathExists(path)) {
return file_util::Delete(path, false);
return file_util::Delete(path);
}
// Doesn't exist, so success.
@ -170,7 +170,7 @@ bool SharedMemory::CreateOrOpen(const std::wstring &name,
// Deleting the file prevents anyone else from mapping it in
// (making it private), and prevents the need for cleanup (once
// the last fd is closed, it is truly freed).
file_util::Delete(path, false);
file_util::Delete(path);
} else {
std::wstring mem_filename;
if (FilenameForMemoryName(name, &mem_filename) == false)

View File

@ -223,6 +223,8 @@ class RefTest(object):
# And for about:newtab content fetch and pings.
prefs['browser.newtabpage.directory.source'] = 'data:application/json,{"reftest":1}'
prefs['browser.newtabpage.directory.ping'] = ''
# Allow unsigned add-ons
prefs['xpinstall.signatures.required'] = False
#Don't use auto-enabled e10s
prefs['browser.tabs.remote.autostart.1'] = False

View File

@ -198,20 +198,15 @@
<!ENTITY tab_queue_prompt_tip_text "you can change this later in Settings">
<!ENTITY tab_queue_prompt_positive_action_button "Enable">
<!ENTITY tab_queue_prompt_negative_action_button "Not now">
<!-- Localization note (tab_queue_notification_text_plural) : The
<!ENTITY tab_queue_notification_title "&brandShortName;">
<!-- Localization note (tab_queue_notification_text_plural2) : The
formatD is replaced with the number of tabs queued. The
number of tabs queued is always more than one. We can't use
Android plural forms, sadly. See Bug #753859. -->
<!ENTITY tab_queue_notification_text_plural "&formatD; tabs queued">
<!-- Localization note (tab_queue_notification_title_plural) : This is the
title of a notification; we expect more than one tab queued. -->
<!ENTITY tab_queue_notification_title_plural "Tabs Queued">
<!-- Localization note (tab_queue_notification_title_singular) : This is the
title of a notification; we expect only one tab queued. -->
<!ENTITY tab_queue_notification_title_singular "Tab Queued">
<!-- Localization note (tab_queue_notification_text_singular) : This is the
<!ENTITY tab_queue_notification_text_plural2 "&formatD; tabs waiting">
<!-- Localization note (tab_queue_notification_text_singular2) : This is the
text of a notification; we expect only one tab queued. -->
<!ENTITY tab_queue_notification_text_singular "1 tab queued">
<!ENTITY tab_queue_notification_text_singular2 "1 tab waiting">
<!ENTITY pref_char_encoding "Character encoding">
<!ENTITY pref_char_encoding_on "Show menu">

View File

@ -137,6 +137,8 @@ OnSharedPreferenceChangeListener
public static final String PREFS_SUGGESTED_SITES = NON_PREF_PREFIX + "home_suggested_sites";
public static final String PREFS_TAB_QUEUE = NON_PREF_PREFIX + "tab_queue";
public static final String PREFS_CUSTOMIZE_SCREEN = NON_PREF_PREFIX + "customize_screen";
public static final String PREFS_TAB_QUEUE_LAST_SITE = NON_PREF_PREFIX + "last_site";
public static final String PREFS_TAB_QUEUE_LAST_TIME = NON_PREF_PREFIX + "last_time";
// These values are chosen to be distinct from other Activity constants.
private static final int REQUEST_CODE_PREF_SCREEN = 5;

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/toast"
style="@style/Toast">
<TextView
android:id="@+id/toast_message"
style="@style/ToastMessage"
tools:text="Tab queued in firefox" />
<View
android:id="@+id/toast_divider"
style="@style/ToastDivider" />
<Button
android:id="@+id/toast_button"
style="@style/ToastButton"
android:drawableLeft="@drawable/switch_button_icon"
tools:text="Open" />
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<resources>
<style name="Toast" parent="ToastBase">
<item name="android:layout_width">400dp</item>
</style>
</resources>

View File

@ -247,10 +247,9 @@
<string name="tab_queue_prompt_negative_action_button">&tab_queue_prompt_negative_action_button;</string>
<string name="tab_queue_toast_message">&tab_queue_toast_message2;</string>
<string name="tab_queue_toast_action">&tab_queue_toast_action;</string>
<string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular;</string>
<string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural;</string>
<string name="tab_queue_notification_title_singular">&tab_queue_notification_title_singular;</string>
<string name="tab_queue_notification_title_plural">&tab_queue_notification_title_plural;</string>
<string name="tab_queue_notification_text_singular">&tab_queue_notification_text_singular2;</string>
<string name="tab_queue_notification_text_plural">&tab_queue_notification_text_plural2;</string>
<string name="tab_queue_notification_title">&tab_queue_notification_title;</string>
<string name="pref_about_firefox">&pref_about_firefox;</string>
<string name="pref_vendor_faqs">&pref_vendor_faqs;</string>

View File

@ -21,6 +21,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
@ -104,6 +105,45 @@ public class TabQueueHelper {
return jsonArray.length();
}
/**
* Remove a url from the file, if it exists.
* If the url exists multiple times, all instances of it will be removed.
* This should not be run on the UI thread.
*
* @param context
* @param urlToRemove URL to remove
* @param filename filename to remove URL from
* @return the number of queued urls
*/
public static int removeURLFromFile(final Context context, final String urlToRemove, final String filename) {
ThreadUtils.assertNotOnUiThread();
final GeckoProfile profile = GeckoProfile.get(context);
JSONArray jsonArray = profile.readJSONArrayFromFile(filename);
JSONArray newArray = new JSONArray();
String url;
// Since JSONArray.remove was only added in API 19, we have to use two arrays in order to remove.
for (int i = 0; i < jsonArray.length(); i++) {
try {
url = jsonArray.getString(i);
} catch (JSONException e) {
url = "";
}
if(!TextUtils.isEmpty(url) && !urlToRemove.equals(url)) {
newArray.put(url);
}
}
profile.writeFile(filename, newArray.toString());
final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
prefs.edit().putInt(PREF_TAB_QUEUE_COUNT, newArray.length()).apply();
return newArray.length();
}
/**
* Displays a notification showing the total number of tabs queue. If there is already a notification displayed, it
* will be replaced.
@ -119,19 +159,17 @@ public class TabQueueHelper {
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
String title, text;
final String text;
final Resources resources = context.getResources();
if (tabsQueued == 1) {
title = resources.getString(R.string.tab_queue_notification_title_singular);
text = resources.getString(R.string.tab_queue_notification_text_singular);
} else {
title = resources.getString(R.string.tab_queue_notification_title_plural);
text = resources.getString(R.string.tab_queue_notification_text_plural, tabsQueued);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(R.drawable.ic_status_logo)
.setContentTitle(title)
.setContentTitle(resources.getString(R.string.tab_queue_notification_title))
.setContentText(text)
.setContentIntent(pendingIntent);

View File

@ -5,6 +5,13 @@
package org.mozilla.gecko.tabqueue;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.mozglue.ContextUtils;
import org.mozilla.gecko.preferences.GeckoPreferences;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@ -14,6 +21,7 @@ import android.graphics.PixelFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
@ -21,12 +29,6 @@ import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import org.mozilla.gecko.BrowserApp;
import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.GeckoSharedPrefs;
import org.mozilla.gecko.R;
import org.mozilla.gecko.mozglue.ContextUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -56,6 +58,7 @@ public class TabQueueService extends Service {
private static final String LOGTAG = "Gecko" + TabQueueService.class.getSimpleName();
private static final long TOAST_TIMEOUT = 3000;
private static final long TOAST_DOUBLE_TAP_TIMEOUT_MILLIS = 6000;
private WindowManager windowManager;
private View toastLayout;
@ -84,7 +87,7 @@ public class TabQueueService extends Service {
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
LayoutInflater layoutInflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
toastLayout = layoutInflater.inflate(R.layout.button_toast, null);
toastLayout = layoutInflater.inflate(R.layout.tab_queue_toast, null);
final Resources resources = getResources();
@ -95,7 +98,7 @@ public class TabQueueService extends Service {
openNowButton.setText(resources.getText(R.string.tab_queue_toast_action));
toastLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_PHONE,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |
@ -108,6 +111,50 @@ public class TabQueueService extends Service {
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
// If this is a redelivery then lets bypass the entire double tap to open now code as that's a big can of worms,
// we also don't expect redeliveries because of the short time window associated with this feature.
if (flags != START_FLAG_REDELIVERY) {
final Context applicationContext = getApplicationContext();
final SharedPreferences sharedPreferences = GeckoSharedPrefs.forApp(applicationContext);
final String lastUrl = sharedPreferences.getString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, "");
final ContextUtils.SafeIntent safeIntent = new ContextUtils.SafeIntent(intent);
final String intentUrl = safeIntent.getDataString();
final long lastRunTime = sharedPreferences.getLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, 0);
final boolean isWithinDoubleTapTimeLimit = System.currentTimeMillis() - lastRunTime < TOAST_DOUBLE_TAP_TIMEOUT_MILLIS;
if (!TextUtils.isEmpty(lastUrl) && lastUrl.equals(intentUrl) && isWithinDoubleTapTimeLimit) {
// Background thread because we could do some file IO if we have to remove a url from the list.
tabQueueHandler.post(new Runnable() {
@Override
public void run() {
// If there is a runnable around, that means that the previous process hasn't yet completed, so
// we will need to prevent it from running and remove the view from the window manager.
// If there is no runnable around then the url has already been added to the list, so we'll
// need to remove it before proceeding or that url will open multiple times.
if (stopServiceRunnable != null) {
tabQueueHandler.removeCallbacks(stopServiceRunnable);
stopSelfResult(stopServiceRunnable.getStartId());
stopServiceRunnable = null;
removeView();
} else {
TabQueueHelper.removeURLFromFile(applicationContext, intentUrl, TabQueueHelper.FILE_NAME);
}
openNow(safeIntent.getUnsafe());
stopSelfResult(startId);
}
});
return START_REDELIVER_INTENT;
}
sharedPreferences.edit().putString(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE, intentUrl)
.putLong(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME, System.currentTimeMillis())
.apply();
}
if (stopServiceRunnable != null) {
// If we're already displaying a toast, keep displaying it but store the previous url.
// The open button will refer to the most recently opened link.
@ -130,14 +177,8 @@ public class TabQueueService extends Service {
public void onClick(final View view) {
tabQueueHandler.removeCallbacks(stopServiceRunnable);
stopServiceRunnable = null;
Intent forwardIntent = new Intent(intent);
forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(forwardIntent);
removeView();
openNow(intent);
stopSelfResult(startId);
}
});
@ -147,6 +188,17 @@ public class TabQueueService extends Service {
return START_REDELIVER_INTENT;
}
private void openNow(Intent intent) {
Intent forwardIntent = new Intent(intent);
forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(forwardIntent);
GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
.remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
.apply();
}
private void removeView() {
windowManager.removeView(toastLayout);
}
@ -198,18 +250,22 @@ public class TabQueueService extends Service {
this.startId = startId;
}
public void run(final boolean shouldStopService) {
public void run() {
run(true);
}
public void run(final boolean shouldRemoveView) {
onRun();
if (shouldStopService) {
if (shouldRemoveView) {
removeView();
}
stopSelfResult(startId);
}
public void run() {
run(true);
public int getStartId() {
return startId;
}
public abstract void onRun();

View File

@ -4108,6 +4108,8 @@ pref("browser.history.maxStateObjectSize", 655360);
// XPInstall prefs
pref("xpinstall.whitelist.required", true);
// Only Firefox requires add-on signatures
pref("xpinstall.signatures.required", false);
pref("extensions.alwaysUnpack", false);
pref("extensions.minCompatiblePlatformVersion", "2.0");

View File

@ -23,6 +23,9 @@
// Trusted Hosted Apps Certificates
#include "manifest-signing-root.inc"
#include "manifest-signing-test-root.inc"
// Add-on signing Certificates
#include "addons-public.inc"
#include "addons-stage.inc"
using namespace mozilla::pkix;
@ -93,6 +96,16 @@ AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot)
trustedDER.len = mozilla::ArrayLength(trustedAppTestRoot);
break;
case nsIX509CertDB::AddonsPublicRoot:
trustedDER.data = const_cast<uint8_t*>(addonsPublicRoot);
trustedDER.len = mozilla::ArrayLength(addonsPublicRoot);
break;
case nsIX509CertDB::AddonsStageRoot:
trustedDER.data = const_cast<uint8_t*>(addonsStageRoot);
trustedDER.len = mozilla::ArrayLength(addonsStageRoot);
break;
default:
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
return SECFailure;

Binary file not shown.

Binary file not shown.

View File

@ -35,6 +35,8 @@ array_names = [
'trustedAppPublicRoot',
'trustedAppTestRoot',
'xpcshellRoot',
'addonsPublicRoot',
'addonsStageRoot',
]
for n in array_names:

View File

@ -34,6 +34,8 @@ headers_arrays_certs = [
('manifest-signing-root.inc', 'trustedAppPublicRoot', 'trusted-app-public.der'),
('manifest-signing-test-root.inc', 'trustedAppTestRoot', test_ssl_path + '/test_signed_manifest/trusted_ca1.der'),
('xpcshell.inc', 'xpcshellRoot', test_ssl_path + '/test_signed_apps/trusted_ca1.der'),
('addons-public.inc', 'addonsPublicRoot', 'addons-public.crt'),
('addons-stage.inc', 'addonsStageRoot', 'addons-stage.crt'),
]
for header, array_name, cert in headers_arrays_certs:

View File

@ -39,7 +39,7 @@ interface nsIVerifySignedManifestCallback : nsISupports
* This represents a service to access and manipulate
* X.509 certificates stored in a database.
*/
[scriptable, uuid(8b01c2af-3a44-44d3-8ea5-51c2455e6c4b)]
[scriptable, uuid(560bc9ac-3e71-472e-9b08-2270d0c71878)]
interface nsIX509CertDB : nsISupports {
/**
@ -311,6 +311,8 @@ interface nsIX509CertDB : nsISupports {
const AppTrustedRoot AppXPCShellRoot = 6;
const AppTrustedRoot TrustedHostedAppPublicRoot = 7;
const AppTrustedRoot TrustedHostedAppTestRoot = 8;
const AppTrustedRoot AddonsPublicRoot = 9;
const AppTrustedRoot AddonsStageRoot = 10;
void openSignedAppFileAsync(in AppTrustedRoot trustedRoot,
in nsIFile aJarFile,
in nsIOpenSignedAppFileCallback callback);

View File

@ -76,6 +76,8 @@ user_pref("extensions.getAddons.cache.enabled", false);
user_pref("extensions.installDistroAddons", false);
// XPI extensions are required for test harnesses to load
user_pref("extensions.defaultProviders.enabled", true);
// Disable signature requirements where possible
user_pref("xpinstall.signatures.required", false);
user_pref("geo.wifi.uri", "http://%(server)s/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs");
user_pref("geo.wifi.timeToWaitBeforeSending", 2000);

View File

@ -3446,50 +3446,59 @@
"description": "PLACES: Days from last maintenance"
},
"UPDATE_CHECK_NO_UPDATE_EXTERNAL" : {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of no updates were found for a background update check (externally initiated)"
},
"UPDATE_CHECK_NO_UPDATE_NOTIFY" : {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of no updates were found for a background update check (timer initiated)"
},
"UPDATE_CHECK_CODE_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 50,
"description": "Update: background update check result code except for no updates found (externally initiated)"
},
"UPDATE_CHECK_CODE_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 50,
"description": "Update: background update check result code except for no updates found (timer initiated)"
},
"UPDATE_CHECK_EXTENDED_ERROR_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"keyed": true,
"description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (externally initiated)"
},
"UPDATE_CHECK_EXTENDED_ERROR_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"keyed": true,
"description": "Update: keyed count (key names are prefixed with AUS_CHECK_EX_ERR_) of background update check extended error code (timer initiated)"
},
"UPDATE_INVALID_LASTUPDATETIME_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that have a last update time greater than the current time (externally initiated)"
},
"UPDATE_INVALID_LASTUPDATETIME_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that have a last update time greater than the current time (timer initiated)"
},
"UPDATE_LAST_NOTIFY_INTERVAL_DAYS_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "exponential",
"n_buckets": 60,
@ -3497,6 +3506,7 @@
"description": "Update: interval in days since the last background update check (externally initiated)"
},
"UPDATE_LAST_NOTIFY_INTERVAL_DAYS_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "exponential",
"n_buckets": 30,
@ -3504,214 +3514,253 @@
"description": "Update: interval in days since the last background update check (timer initiated)"
},
"UPDATE_PING_COUNT_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems for this ping for comparison with other pings (externally initiated)"
},
"UPDATE_PING_COUNT_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems for this ping for comparison with other pings (timer initiated)"
},
"UPDATE_SERVICE_INSTALLED_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "boolean",
"description": "Update: whether the service is installed (externally initiated)"
},
"UPDATE_SERVICE_INSTALLED_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "boolean",
"description": "Update: whether the service is installed (timer initiated)"
},
"UPDATE_SERVICE_MANUALLY_UNINSTALLED_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that manually uninstalled the service (externally initiated)"
},
"UPDATE_SERVICE_MANUALLY_UNINSTALLED_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that manually uninstalled the service (timer initiated)"
},
"UPDATE_UNABLE_TO_APPLY_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that cannot apply updates (externally initiated)"
},
"UPDATE_UNABLE_TO_APPLY_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that cannot apply updates (timer initiated)"
},
"UPDATE_CANNOT_STAGE_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that cannot stage updates (externally initiated)"
},
"UPDATE_CANNOT_STAGE_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that cannot stage updates (timer initiated)"
},
"UPDATE_HAS_PREF_URL_OVERRIDE_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that have an app.update.url.override preference (externally initiated)"
},
"UPDATE_HAS_PREF_URL_OVERRIDE_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of systems that have an app.update.url.override preference (timer initiated)"
},
"UPDATE_PREF_UPDATE_CANCELATIONS_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: number of sequential update elevation request cancelations greater than 0 (externally initiated)"
},
"UPDATE_PREF_UPDATE_CANCELATIONS_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: number of sequential update elevation request cancelations greater than 0 (timer initiated)"
},
"UPDATE_PREF_SERVICE_ERRORS_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 30,
"description": "Update: number of sequential update service errors greater than 0 (externally initiated)"
},
"UPDATE_PREF_SERVICE_ERRORS_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 30,
"description": "Update: number of sequential update service errors greater than 0 (timer initiated)"
},
"UPDATE_NOT_PREF_UPDATE_AUTO_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.auto boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_AUTO_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.auto boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_ENABLED_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.enabled boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_ENABLED_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.enabled boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.staging.enabled boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_STAGING_ENABLED_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.staging.enabled boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_EXTERNAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.service.enabled boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_NOT_PREF_UPDATE_SERVICE_ENABLED_NOTIFY": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "count",
"description": "Update: count of when the app.update.service.enabled boolean preference is not the default value of true (true values are not submitted)"
},
"UPDATE_DOWNLOAD_CODE_COMPLETE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 50,
"description": "Update: complete patch download result code"
},
"UPDATE_DOWNLOAD_CODE_PARTIAL": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 50,
"description": "Update: complete patch download result code"
},
"UPDATE_STATE_CODE_COMPLETE_STARTUP": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "Update: the state of a complete update from update.status on startup"
},
"UPDATE_STATE_CODE_PARTIAL_STARTUP": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "Update: the state of a partial patch update from update.status on startup"
},
"UPDATE_STATE_CODE_UNKNOWN_STARTUP": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "Update: the state of an unknown patch update from update.status on startup"
},
"UPDATE_STATE_CODE_COMPLETE_STAGE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "Update: the state of a complete patch update from update.status after staging"
},
"UPDATE_STATE_CODE_PARTIAL_STAGE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "Update: the state of a partial patch update from update.status after staging"
},
"UPDATE_STATE_CODE_UNKNOWN_STAGE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 20,
"description": "Update: the state of an unknown patch update from update.status after staging"
},
"UPDATE_STATUS_ERROR_CODE_COMPLETE_STARTUP": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: the status error code for a failed complete patch update from update.status on startup"
},
"UPDATE_STATUS_ERROR_CODE_PARTIAL_STARTUP": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: the status error code for a failed partial patch update from update.status on startup"
},
"UPDATE_STATUS_ERROR_CODE_UNKNOWN_STARTUP": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: the status error code for a failed unknown patch update from update.status on startup"
},
"UPDATE_STATUS_ERROR_CODE_COMPLETE_STAGE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: the status error code for a failed complete patch update from update.status after staging"
},
"UPDATE_STATUS_ERROR_CODE_PARTIAL_STAGE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: the status error code for a failed partial patch update from update.status after staging"
},
"UPDATE_STATUS_ERROR_CODE_UNKNOWN_STAGE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 100,
"description": "Update: the status error code for a failed unknown patch update from update.status after staging"
},
"UPDATE_WIZ_LAST_PAGE_CODE": {
"alert_emails": ["application-update-telemetry-alerts@mozilla.com"],
"expires_in_version": "never",
"kind": "enumerated",
"n_values": 30,

View File

@ -2672,6 +2672,8 @@ this.AddonManager = {
ERROR_CORRUPT_FILE: -3,
// An error occured trying to write to the filesystem.
ERROR_FILE_ACCESS: -4,
// The add-on must be signed and isn't.
ERROR_SIGNEDSTATE_REQUIRED: -5,
// These must be kept in sync with AddonUpdateChecker.
// No error was encountered.
@ -2807,6 +2809,20 @@ this.AddonManager = {
// add-ons that were pending being enabled the last time the application ran.
STARTUP_CHANGE_ENABLED: "enabled",
// Constants for Addon.signedState. Any states that should cause an add-on
// to be unusable in builds that require signing should have negative values.
// Add-on is signed but signature verification has failed.
SIGNEDSTATE_BROKEN: -2,
// Add-on may be signed but by an certificate that doesn't chain to our
// our trusted certificate.
SIGNEDSTATE_UNKNOWN: -1,
// Add-on is unsigned.
SIGNEDSTATE_MISSING: 0,
// Add-on is preliminarily reviewed.
SIGNEDSTATE_PRELIMINARY: 1,
// Add-on is fully reviewed.
SIGNEDSTATE_SIGNED: 2,
// Constants for the Addon.userDisabled property
// Indicates that the userDisabled state of this add-on is currently
// ask-to-activate. That is, it can be conditionally enabled on a

View File

@ -78,6 +78,9 @@ const PREF_XPI_ENABLED = "xpinstall.enabled";
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
// xpinstall.signatures.required only supported in dev builds
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root";
const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
const PREF_XPI_UNPACK = "extensions.alwaysUnpack";
const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
@ -100,7 +103,6 @@ const STRING_TYPE_NAME = "type.%ID%.name";
const DIR_EXTENSIONS = "extensions";
const DIR_STAGE = "staged";
const DIR_XPI_STAGE = "staged-xpis";
const DIR_TRASH = "trash";
const FILE_DATABASE = "extensions.json";
@ -190,6 +192,19 @@ const RESTARTLESS_TYPES = new Set([
"locale",
]);
const SIGNED_TYPES = new Set([
"extension",
"experiment",
]);
// Whether add-on signing is required.
function mustSign(aType) {
if (!SIGNED_TYPES.has(aType))
return false;
return REQUIRE_SIGNING || Preferences.get(PREF_XPI_SIGNATURES_REQUIRED, false);
}
// Keep track of where we are in startup for telemetry
// event happened during XPIDatabase.startup()
const XPI_STARTING = "XPIStarting";
@ -630,6 +645,9 @@ function isUsableAddon(aAddon) {
if (aAddon.type == "theme" && aAddon.internalName == XPIProvider.defaultSkin)
return true;
if (mustSign(aAddon.type) && aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING)
return false;
if (aAddon.blocklistState == Blocklist.STATE_BLOCKED)
return false;
@ -999,7 +1017,7 @@ function loadManifestFromRDF(aUri, aStream) {
* @return an AddonInternal object
* @throws if the directory does not contain a valid install manifest
*/
function loadManifestFromDir(aDir) {
let loadManifestFromDir = Task.async(function* loadManifestFromDir(aDir) {
function getFileSize(aFile) {
if (aFile.isSymlink())
return 0;
@ -1040,6 +1058,11 @@ function loadManifestFromDir(aDir) {
addon.hasBinaryComponents = ChromeManifestParser.hasType(chromeManifest,
"binary-component");
if (SIGNED_TYPES.has(addon.type))
addon.signedState = yield verifyDirSignedState(aDir, addon.id);
else
addon.signedState = AddonManager.SIGNEDSTATE_MISSING;
addon.appDisabled = !isUsableAddon(addon);
return addon;
}
@ -1047,7 +1070,7 @@ function loadManifestFromDir(aDir) {
bis.close();
fis.close();
}
}
});
/**
* Loads an AddonInternal object from an nsIZipReader for an add-on.
@ -1057,7 +1080,7 @@ function loadManifestFromDir(aDir) {
* @return an AddonInternal object
* @throws if the XPI file does not contain a valid install manifest
*/
function loadManifestFromZipReader(aZipReader) {
let loadManifestFromZipReader = Task.async(function* loadManifestFromZipReader(aZipReader) {
let zis = aZipReader.getInputStream(FILE_INSTALL_MANIFEST);
let bis = Cc["@mozilla.org/network/buffered-input-stream;1"].
createInstance(Ci.nsIBufferedInputStream);
@ -1083,6 +1106,11 @@ function loadManifestFromZipReader(aZipReader) {
addon.hasBinaryComponents = false;
}
if (SIGNED_TYPES.has(addon.type))
addon.signedState = yield verifyZipSignedState(aZipReader.file, addon.id, addon.version);
else
addon.signedState = AddonManager.SIGNEDSTATE_MISSING;
addon.appDisabled = !isUsableAddon(addon);
return addon;
}
@ -1090,7 +1118,7 @@ function loadManifestFromZipReader(aZipReader) {
bis.close();
zis.close();
}
}
});
/**
* Loads an AddonInternal object from an add-on in an XPI file.
@ -1100,18 +1128,22 @@ function loadManifestFromZipReader(aZipReader) {
* @return an AddonInternal object
* @throws if the XPI file does not contain a valid install manifest
*/
function loadManifestFromZipFile(aXPIFile) {
let loadManifestFromZipFile = Task.async(function* loadManifestFromZipFile(aXPIFile) {
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
try {
zipReader.open(aXPIFile);
return loadManifestFromZipReader(zipReader);
// Can't return this promise because that will make us close the zip reader
// before it has finished loading the manifest. Wait for the result and then
// return.
let manifest = yield loadManifestFromZipReader(zipReader);
return manifest;
}
finally {
zipReader.close();
}
}
});
function loadManifestFromFile(aFile) {
if (aFile.isFile())
@ -1120,6 +1152,32 @@ function loadManifestFromFile(aFile) {
return loadManifestFromDir(aFile);
}
/**
* A synchronous method for loading an add-on's manifest. This should only ever
* be used during startup or a sync load of the add-ons DB
*/
function syncLoadManifestFromFile(aFile) {
let success = undefined;
let result = null;
loadManifestFromFile(aFile).then(val => {
success = true;
result = val;
}, val => {
success = false;
result = val
});
let thread = Services.tm.currentThread;
while (success === undefined)
thread.processNextEvent(true);
if (!success)
throw result;
return result;
}
/**
* Gets an nsIURI for a file within another file, either a directory or an XPI
* file. If aFile is a directory then this will return a file: URI, if it is an
@ -1226,6 +1284,81 @@ function verifyZipSigning(aZip, aCertificate) {
return aZip.manifestEntriesCount == count;
}
/**
* Returns the signedState for a given return code and certificate by verifying
* it against the expected ID.
*/
function getSignedStatus(aRv, aCert, aExpectedID) {
switch (aRv) {
case Cr.NS_OK:
if (aExpectedID != aCert.commonName)
return AddonManager.SIGNEDSTATE_BROKEN;
return /preliminary/i.test(aCert.organizationalUnit)
? AddonManager.SIGNEDSTATE_PRELIMINARY
: AddonManager.SIGNEDSTATE_SIGNED;
break;
case Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED:
return AddonManager.SIGNEDSTATE_MISSING;
break;
case Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID:
case Cr.NS_ERROR_SIGNED_JAR_ENTRY_INVALID:
case Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING:
case Cr.NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE:
case Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY:
case Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY:
return AddonManager.SIGNEDSTATE_BROKEN;
break;
default:
// Any other error indicates that either the add-on isn't signed or it
// is signed by a signature that doesn't chain to the trusted root.
return AddonManager.SIGNEDSTATE_UNKNOWN;
break;
}
}
/**
* Verifies that a zip file's contents are all correctly signed by an
* AMO-issued certificate
*
* @param aFile
* the xpi file to check
* @param aExpectedID
* the expected ID of the signature
* @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
*/
function verifyZipSignedState(aFile, aExpectedID, aVersion) {
let certDB = Cc["@mozilla.org/security/x509certdb;1"]
.getService(Ci.nsIX509CertDB);
let root = Ci.nsIX509CertDB.AddonsPublicRoot;
if (!REQUIRE_SIGNING && Preferences.get(PREF_XPI_SIGNATURES_DEV_ROOT, false))
root = Ci.nsIX509CertDB.AddonsStageRoot;
return new Promise(resolve => {
certDB.openSignedAppFileAsync(root, aFile, (aRv, aZipReader, aCert) => {
if (aZipReader)
aZipReader.close();
resolve(getSignedStatus(aRv, aCert, aExpectedID));
});
});
}
/**
* Verifies that a directory's contents are all correctly signed by an
* AMO-issued certificate
*
* @param aDir
* the directory to check
* @param aExpectedID
* the expected ID of the signature
* @return a Promise that resolves to an AddonManager.SIGNEDSTATE_* constant.
*/
function verifyDirSignedState(aDir, aExpectedID) {
// TODO: Get the certificate for an unpacked add-on (bug 1038072)
return Promise.resolve(AddonManager.SIGNEDSTATE_MISSING);
}
/**
* Replaces %...% strings in an addon url (update and updateInfo) with
* appropriate values.
@ -2081,6 +2214,8 @@ this.XPIProvider = {
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_APP_VERSION, this, false);
Services.prefs.addObserver(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, this, false);
if (!REQUIRE_SIGNING)
Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this, false);
Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS, false);
if (Cu.isModuleLoaded("resource:///modules/devtools/ToolboxProcess.jsm")) {
// If BrowserToolboxProcess is already loaded, set the boolean to true
@ -2424,95 +2559,8 @@ this.XPIProvider = {
if (aLocation.locked)
return;
let stagedXPIDir = aLocation.getXPIStagingDir();
let stagingDir = aLocation.getStagingDir();
if (stagedXPIDir.exists() && stagedXPIDir.isDirectory()) {
let entries = stagedXPIDir.directoryEntries
.QueryInterface(Ci.nsIDirectoryEnumerator);
while (entries.hasMoreElements()) {
let stageDirEntry = entries.nextFile;
if (!stageDirEntry.isDirectory()) {
logger.warn("Ignoring file in XPI staging directory: " + stageDirEntry.path);
continue;
}
// Find the last added XPI file in the directory
let stagedXPI = null;
var xpiEntries = stageDirEntry.directoryEntries
.QueryInterface(Ci.nsIDirectoryEnumerator);
while (xpiEntries.hasMoreElements()) {
let file = xpiEntries.nextFile;
if (file.isDirectory())
continue;
let extension = file.leafName;
extension = extension.substring(extension.length - 4);
if (extension != ".xpi" && extension != ".jar")
continue;
stagedXPI = file;
}
xpiEntries.close();
if (!stagedXPI)
continue;
let addon = null;
try {
addon = loadManifestFromZipFile(stagedXPI);
}
catch (e) {
logger.error("Unable to read add-on manifest from " + stagedXPI.path, e);
continue;
}
logger.debug("Migrating staged install of " + addon.id + " in " + aLocation.name);
if (addon.unpack || Preferences.get(PREF_XPI_UNPACK, false)) {
let targetDir = stagingDir.clone();
targetDir.append(addon.id);
try {
targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
}
catch (e) {
logger.error("Failed to create staging directory for add-on " + addon.id, e);
continue;
}
try {
ZipUtils.extractFiles(stagedXPI, targetDir);
}
catch (e) {
logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
aLocation.name, e);
}
}
else {
try {
stagedXPI.moveTo(stagingDir, addon.id + ".xpi");
}
catch (e) {
logger.error("Failed to move staged XPI for add-on " + addon.id + " in " +
aLocation.name, e);
}
}
}
entries.close();
}
if (stagedXPIDir.exists()) {
try {
recursiveRemove(stagedXPIDir);
}
catch (e) {
// Non-critical, just saves some perf on startup if we clean this up.
logger.debug("Error removing XPI staging dir " + stagedXPIDir.path, e);
}
}
try {
if (!stagingDir || !stagingDir.exists() || !stagingDir.isDirectory())
return;
@ -2593,7 +2641,7 @@ this.XPIProvider = {
jsonfile.append(id + ".json");
try {
aManifests[aLocation.name][id] = loadManifestFromFile(stageDirEntry);
aManifests[aLocation.name][id] = syncLoadManifestFromFile(stageDirEntry);
}
catch (e) {
logger.error("Unable to read add-on manifest from " + stageDirEntry.path, e);
@ -2603,6 +2651,15 @@ this.XPIProvider = {
continue;
}
let addon = aManifests[aLocation.name][id];
if ((addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) && mustSign(addon.type)) {
logger.warn("Refusing to install staged add-on " + id + " with signed state " + addon.signedState);
seenFiles.push(stageDirEntry.leafName);
seenFiles.push(jsonfile.leafName);
continue;
}
// Check for a cached metadata for this add-on, it may contain updated
// compatibility information
if (jsonfile.exists()) {
@ -2615,7 +2672,7 @@ this.XPIProvider = {
try {
fis.init(jsonfile, -1, 0, 0);
let metadata = json.decodeFromStream(fis, jsonfile.fileSize);
aManifests[aLocation.name][id].importMetadata(metadata);
addon.importMetadata(metadata);
}
catch (e) {
// If some data can't be recovered from the cached metadata then it
@ -2629,7 +2686,7 @@ this.XPIProvider = {
}
seenFiles.push(jsonfile.leafName);
existingAddonID = aManifests[aLocation.name][id].existingAddonID || id;
existingAddonID = addon.existingAddonID || id;
var oldBootstrap = null;
logger.debug("Processing install of " + id + " in " + aLocation.name);
@ -2642,7 +2699,7 @@ this.XPIProvider = {
// We'll be replacing a currently active bootstrapped add-on so
// call its uninstall method
let newVersion = aManifests[aLocation.name][id].version;
let newVersion = addon.version;
let oldVersion = oldBootstrap.version;
let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
BOOTSTRAP_REASONS.ADDON_UPGRADE :
@ -2660,17 +2717,15 @@ this.XPIProvider = {
}
try {
var addonInstallLocation = aLocation.installAddon(id, stageDirEntry,
existingAddonID);
if (aManifests[aLocation.name][id])
aManifests[aLocation.name][id]._sourceBundle = addonInstallLocation;
addon._sourceBundle = aLocation.installAddon(id, stageDirEntry,
existingAddonID);
}
catch (e) {
logger.error("Failed to install staged add-on " + id + " in " + aLocation.name,
e);
// Re-create the staged install
AddonInstall.createStagedInstall(aLocation, stageDirEntry,
aManifests[aLocation.name][id]);
addon);
// Make sure not to delete the cached manifest json file
seenFiles.pop();
@ -2756,7 +2811,7 @@ this.XPIProvider = {
let addon;
try {
addon = loadManifestFromFile(entry);
addon = syncLoadManifestFromFile(entry);
}
catch (e) {
logger.warn("File entry " + entry.path + " contains an invalid add-on", e);
@ -2779,7 +2834,7 @@ this.XPIProvider = {
if (existingEntry) {
let existingAddon;
try {
existingAddon = loadManifestFromFile(existingEntry);
existingAddon = syncLoadManifestFromFile(existingEntry);
if (Services.vc.compare(addon.version, existingAddon.version) <= 0)
continue;
@ -2874,7 +2929,7 @@ this.XPIProvider = {
// If not load it
if (!newAddon) {
let file = aInstallLocation.getLocationForID(aOldAddon.id);
newAddon = loadManifestFromFile(file);
newAddon = syncLoadManifestFromFile(file);
applyBlocklistChanges(aOldAddon, newAddon);
// Carry over any pendingUninstall state to add-ons modified directly
@ -2917,6 +2972,11 @@ this.XPIProvider = {
// Remember add-ons that were changed during startup
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_CHANGED,
newDBAddon.id);
if (aOldAddon.active == newDBAddon.disabled) {
let change = aOldAddon.active ? AddonManager.STARTUP_CHANGE_DISABLED
: AddonManager.STARTUP_CHANGE_ENABLED;
AddonManagerPrivate.addStartupChange(change, newDBAddon.id);
}
// If this was the active theme and it is now disabled then enable the
// default theme
@ -3041,7 +3101,16 @@ this.XPIProvider = {
let wasAppDisabled = aOldAddon.appDisabled;
let wasUserDisabled = aOldAddon.userDisabled;
let wasSoftDisabled = aOldAddon.softDisabled;
let updateDB = false;
// If updating from a version of the app that didn't support signedState
// then fetch that property now
if (aOldAddon.signedState === undefined) {
let file = aInstallLocation.getLocationForID(aOldAddon.id);
let manifest = syncLoadManifestFromFile(file);
aOldAddon.signedState = manifest.signedState;
updateDB = true;
}
// This updates the addon's JSON cached data in place
applyBlocklistChanges(aOldAddon, aOldAddon, aOldAppVersion,
aOldPlatformVersion);
@ -3050,7 +3119,7 @@ this.XPIProvider = {
let isDisabled = aOldAddon.disabled;
// If either property has changed update the database.
if (wasAppDisabled != aOldAddon.appDisabled ||
if (updateDB || wasAppDisabled != aOldAddon.appDisabled ||
wasUserDisabled != aOldAddon.userDisabled ||
wasSoftDisabled != aOldAddon.softDisabled) {
logger.debug("Add-on " + aOldAddon.id + " changed appDisabled state to " +
@ -3171,7 +3240,7 @@ this.XPIProvider = {
if (!newAddon) {
// Load the manifest from the add-on.
let file = aInstallLocation.getLocationForID(aId);
newAddon = loadManifestFromFile(file);
newAddon = syncLoadManifestFromFile(file);
}
// The add-on in the manifest should match the add-on ID.
if (newAddon.id != aId) {
@ -4094,13 +4163,18 @@ this.XPIProvider = {
if (aTopic == "nsPref:changed") {
switch (aData) {
case PREF_EM_MIN_COMPAT_APP_VERSION:
case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
this.minCompatibleAppVersion = Preferences.get(PREF_EM_MIN_COMPAT_APP_VERSION,
null);
this.updateAddonAppDisabledStates();
break;
case PREF_EM_MIN_COMPAT_PLATFORM_VERSION:
this.minCompatiblePlatformVersion = Preferences.get(PREF_EM_MIN_COMPAT_PLATFORM_VERSION,
null);
this.updateAddonAppDisabledStates();
break;
case PREF_XPI_SIGNATURES_REQUIRED:
this.updateAddonAppDisabledStates();
break;
}
}
},
@ -4914,49 +4988,45 @@ AddonInstall.prototype = {
}
}
try {
let self = this;
this.loadManifest(function initLocalInstall_loadManifest() {
XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) {
self.existingAddon = aAddon;
if (aAddon)
applyBlocklistChanges(aAddon, self.addon);
self.addon.updateDate = Date.now();
self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate;
let self = this;
this.loadManifest().then(() => {
XPIDatabase.getVisibleAddonForID(self.addon.id, function initLocalInstall_getVisibleAddon(aAddon) {
self.existingAddon = aAddon;
if (aAddon)
applyBlocklistChanges(aAddon, self.addon);
self.addon.updateDate = Date.now();
self.addon.installDate = aAddon ? aAddon.installDate : self.addon.updateDate;
if (!self.addon.isCompatible) {
// TODO Should we send some event here?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) {
self.state = AddonManager.STATE_DOWNLOADED;
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
if (!self.addon.isCompatible) {
// TODO Should we send some event here?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function updateChecker_onUpdateFinished(aAddon) {
self.state = AddonManager.STATE_DOWNLOADED;
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
aCallback(self);
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
else {
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
aCallback(self);
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
else {
XPIProvider.installs.push(self);
AddonManagerPrivate.callInstallListeners("onNewInstall",
self.listeners,
self.wrapper);
aCallback(self);
}
});
aCallback(self);
}
});
}
catch (e) {
logger.warn("Invalid XPI", e);
}, ([error, message]) => {
logger.warn("Invalid XPI", message);
this.state = AddonManager.STATE_DOWNLOAD_FAILED;
this.error = AddonManager.ERROR_CORRUPT_FILE;
this.error = error;
aCallback(this);
return;
}
});
},
/**
@ -5145,8 +5215,7 @@ AddonInstall.prototype = {
* loaded. Because this loadMultipackageManifests is an internal API
* we don't exception-wrap this callback
*/
_loadMultipackageManifests: function AI_loadMultipackageManifests(aZipReader,
aCallback) {
_loadMultipackageManifests: Task.async(function* AI_loadMultipackageManifests(aZipReader) {
let files = [];
let entries = aZipReader.findEntries("(*.[Xx][Pp][Ii]|*.[Jj][Aa][Rr])");
while (entries.hasMore()) {
@ -5179,7 +5248,7 @@ AddonInstall.prototype = {
this.file = files.shift();
this.ownsTempFile = true;
try {
addon = loadManifestFromZipFile(this.file);
addon = yield loadManifestFromZipFile(this.file);
break;
}
catch (e) {
@ -5190,7 +5259,6 @@ AddonInstall.prototype = {
if (!addon) {
// No valid add-on was found
aCallback();
return;
}
@ -5212,36 +5280,28 @@ AddonInstall.prototype = {
// Create new AddonInstall instances for every remaining file
if (files.length > 0) {
this.linkedInstalls = [];
let count = 0;
let self = this;
files.forEach(function(file) {
AddonInstall.createInstall(function loadMultipackageManifests_createInstall(aInstall) {
// Ignore bad add-ons (createInstall will have logged the error)
if (aInstall.state == AddonManager.STATE_DOWNLOAD_FAILED) {
// Manually remove the temporary file
file.remove(true);
}
else {
// Make the new install own its temporary file
aInstall.ownsTempFile = true;
for (let file of files) {
let install = yield new Promise(resolve => AddonInstall.createInstall(resolve, file));
self.linkedInstalls.push(aInstall)
// Ignore bad add-ons (createInstall will have logged the error)
if (install.state == AddonManager.STATE_DOWNLOAD_FAILED) {
// Manually remove the temporary file
file.remove(true);
}
else {
// Make the new install own its temporary file
install.ownsTempFile = true;
aInstall.sourceURI = self.sourceURI;
aInstall.releaseNotesURI = self.releaseNotesURI;
aInstall.updateAddonURIs();
}
self.linkedInstalls.push(install)
count++;
if (count == files.length)
aCallback();
}, file);
}, this);
install.sourceURI = self.sourceURI;
install.releaseNotesURI = self.releaseNotesURI;
install.updateAddonURIs();
}
}
}
else {
aCallback();
}
},
}),
/**
* Called after the add-on is a local file and the signature and install
@ -5252,36 +5312,7 @@ AddonInstall.prototype = {
* @throws if the add-on does not contain a valid install manifest or the
* XPI is incorrectly signed
*/
loadManifest: function AI_loadManifest(aCallback) {
aCallback = makeSafe(aCallback);
let self = this;
function addRepositoryData(aAddon) {
// Try to load from the existing cache first
AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
if (aRepoAddon) {
aAddon._repositoryAddon = aRepoAddon;
self.name = self.name || aAddon._repositoryAddon.name;
aAddon.compatibilityOverrides = aRepoAddon.compatibilityOverrides;
aAddon.appDisabled = !isUsableAddon(aAddon);
aCallback();
return;
}
// It wasn't there so try to re-download it
AddonRepository.cacheAddons([aAddon.id], function loadManifest_cacheAddons() {
AddonRepository.getCachedAddonByID(aAddon.id, function loadManifest_getCachedAddonByID(aRepoAddon) {
aAddon._repositoryAddon = aRepoAddon;
self.name = self.name || aAddon._repositoryAddon.name;
aAddon.compatibilityOverrides = aRepoAddon ?
aRepoAddon.compatibilityOverrides :
null;
aAddon.appDisabled = !isUsableAddon(aAddon);
aCallback();
});
});
});
}
loadManifest: Task.async(function* AI_loadManifest() {
let zipreader = Cc["@mozilla.org/libjar/zip-reader;1"].
createInstance(Ci.nsIZipReader);
try {
@ -5289,39 +5320,56 @@ AddonInstall.prototype = {
}
catch (e) {
zipreader.close();
throw e;
}
let x509 = zipreader.getSigningCert(null);
if (x509) {
logger.debug("Verifying XPI signature");
if (verifyZipSigning(zipreader, x509)) {
this.certificate = x509;
if (this.certificate.commonName.length > 0) {
this.certName = this.certificate.commonName;
} else {
this.certName = this.certificate.organization;
}
} else {
zipreader.close();
throw new Error("XPI is incorrectly signed");
}
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
}
try {
this.addon = loadManifestFromZipReader(zipreader);
// loadManifestFromZipReader performs the certificate verification for us
this.addon = yield loadManifestFromZipReader(zipreader);
}
catch (e) {
zipreader.close();
throw e;
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
}
if (this.addon.type == "multipackage") {
this._loadMultipackageManifests(zipreader, function loadManifest_loadMultipackageManifests() {
addRepositoryData(self.addon);
});
return;
if (mustSign(this.addon.type)) {
if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
// This add-on isn't properly signed by a signature that chains to the
// trusted root.
let state = this.addon.signedState;
this.addon = null;
zipreader.close();
if (state == AddonManager.SIGNEDSTATE_MISSING)
return Promise.reject([AddonManager.ERROR_SIGNEDSTATE_REQUIRED,
"signature is required but missing"])
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
"signature verification failed"])
}
}
else if (this.addon.signedState == AddonManager.SIGNEDSTATE_UNKNOWN) {
// Check object signing certificate, if any
let x509 = zipreader.getSigningCert(null);
if (x509) {
logger.debug("Verifying XPI signature");
if (verifyZipSigning(zipreader, x509)) {
this.certificate = x509;
if (this.certificate.commonName.length > 0) {
this.certName = this.certificate.commonName;
} else {
this.certName = this.certificate.organization;
}
} else {
zipreader.close();
return Promise.reject([AddonManager.ERROR_CORRUPT_FILE,
"XPI is incorrectly signed"]);
}
}
}
if (this.addon.type == "multipackage")
return this._loadMultipackageManifests(zipreader);
zipreader.close();
@ -5338,8 +5386,22 @@ AddonInstall.prototype = {
//if (newIcon)
// this.iconURL = newIcon;
addRepositoryData(this.addon);
},
// Try to load from the existing cache first
let repoAddon = yield new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
// It wasn't there so try to re-download it
if (!repoAddon) {
yield new Promise(resolve => AddonRepository.cacheAddons([this.addon.id], resolve));
repoAddon = yield new Promise(resolve => AddonRepository.getCachedAddonByID(this.addon.id, resolve));
}
this.addon._repositoryAddon = repoAddon;
this.name = this.name || this.addon._repositoryAddon.name;
this.addon.compatibilityOverrides = repoAddon ?
repoAddon.compatibilityOverrides :
null;
this.addon.appDisabled = !isUsableAddon(this.addon);
}),
observe: function AI_observe(aSubject, aTopic, aData) {
// Network is going offline
@ -5575,26 +5637,24 @@ AddonInstall.prototype = {
") did not match provided hash (" + this.hash.data + ")");
return;
}
try {
let self = this;
this.loadManifest(function onStopRequest_loadManifest() {
if (self.addon.isCompatible) {
self.downloadCompleted();
}
else {
// TODO Should we send some event here (bug 557716)?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) {
self.downloadCompleted();
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
});
}
catch (e) {
this.downloadFailed(AddonManager.ERROR_CORRUPT_FILE, e);
}
let self = this;
this.loadManifest().then(() => {
if (self.addon.isCompatible) {
self.downloadCompleted();
}
else {
// TODO Should we send some event here (bug 557716)?
self.state = AddonManager.STATE_CHECKING;
new UpdateChecker(self.addon, {
onUpdateFinished: function onStopRequest_onUpdateFinished(aAddon) {
self.downloadCompleted();
}
}, AddonManager.UPDATE_WHEN_ADDON_INSTALLED);
}
}, ([error, message]) => {
this.downloadFailed(error, message);
});
}
else {
if (aRequest instanceof Ci.nsIHttpChannel)
@ -6619,7 +6679,7 @@ function AddonWrapper(aAddon) {
"providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
"softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
"strictCompatibility", "compatibilityOverrides", "updateURL",
"getDataDirectory", "multiprocessCompatible"].forEach(function(aProp) {
"getDataDirectory", "multiprocessCompatible", "signedState"].forEach(function(aProp) {
this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]);
}, this);
@ -7191,7 +7251,7 @@ DirectoryInstallLocation.prototype = {
for (let entry of entries) {
let id = entry.leafName;
if (id == DIR_STAGE || id == DIR_XPI_STAGE || id == DIR_TRASH)
if (id == DIR_STAGE || id == DIR_TRASH)
continue;
let directLoad = false;
@ -7334,18 +7394,6 @@ DirectoryInstallLocation.prototype = {
}
},
/**
* Gets the directory used by old versions for staging XPI and JAR files ready
* to be installed.
*
* @return an nsIFile
*/
getXPIStagingDir: function DirInstallLocation_getXPIStagingDir() {
let dir = this._directory.clone();
dir.append(DIR_XPI_STAGE);
return dir;
},
/**
* Returns a directory that is normally on the same filesystem as the rest of
* the install location and can be used for temporarily storing files during
@ -7722,6 +7770,19 @@ WinRegInstallLocation.prototype = {
};
#endif
// Make this a non-changable property so it can't be manipulated from other
// code in the app.
Object.defineProperty(this, "REQUIRE_SIGNING", {
configurable: false,
enumerable: false,
writable: false,
#ifdef MOZ_REQUIRE_SIGNING
value: true,
#else
value: false,
#endif
});
let addonTypes = [
new AddonManagerPrivate.AddonType("extension", URI_EXTENSION_STRINGS,
STRING_TYPE_NAME,

View File

@ -70,7 +70,7 @@ const PROP_JSON_FIELDS = ["id", "syncGUID", "location", "version", "type",
"skinnable", "size", "sourceURI", "releaseNotesURI",
"softDisabled", "foreignInstall", "hasBinaryComponents",
"strictCompatibility", "locales", "targetApplications",
"targetPlatforms", "multiprocessCompatible"];
"targetPlatforms", "multiprocessCompatible", "signedState"];
// Time to wait before async save of XPI JSON database, in milliseconds
const ASYNC_SAVE_DELAY_MS = 20;

View File

@ -28,7 +28,7 @@ EXTRA_PP_JS_MODULES.addons += [
# This is used in multiple places, so is defined here to avoid it getting
# out of sync.
DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 16
DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 17
# Additional debugging info is exposed in debug builds
if CONFIG['MOZ_EM_DEBUG']:

View File

@ -0,0 +1,29 @@
Components.utils.import("resource://gre/modules/Services.jsm");
const VERSION = 1;
// Test steps chain from pref observers on *_reason,
// so always set that last
function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
}
function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 0);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData -->
<em:name>Test Add-on</em:name>
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>2</em:minVersion>
<em:maxVersion>5</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1 @@
This test file can be altered to break signing checks.

View File

@ -0,0 +1,29 @@
Components.utils.import("resource://gre/modules/Services.jsm");
const VERSION = 2;
// Test steps chain from pref observers on *_reason,
// so always set that last
function install(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
}
function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 0);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<em:bootstrap>true</em:bootstrap>
<!-- Front End MetaData -->
<em:name>Test Add-on</em:name>
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>4</em:minVersion>
<em:maxVersion>6</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1 @@
This test file can be altered to break signing checks.

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test@tests.mozilla.org</em:id>
<em:version>1.0</em:version>
<!-- Front End MetaData -->
<em:name>Test Add-on</em:name>
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>2</em:minVersion>
<em:maxVersion>5</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1 @@
This test file can be altered to break signing checks.

View File

@ -0,0 +1,23 @@
<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>test@tests.mozilla.org</em:id>
<em:version>2.0</em:version>
<!-- Front End MetaData -->
<em:name>Test Add-on</em:name>
<em:updateURL>http://localhost:4444/update.rdf</em:updateURL>
<em:targetApplication>
<Description>
<em:id>xpcshell@tests.mozilla.org</em:id>
<em:minVersion>4</em:minVersion>
<em:maxVersion>6</em:maxVersion>
</Description>
</em:targetApplication>
</Description>
</RDF>

View File

@ -0,0 +1 @@
This test file can be altered to break signing checks.

View File

@ -14,6 +14,7 @@ const PREF_EM_MIN_COMPAT_APP_VERSION = "extensions.minCompatibleAppVersion"
const PREF_EM_MIN_COMPAT_PLATFORM_VERSION = "extensions.minCompatiblePlatformVersion";
const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url";
const PREF_GETADDONS_BYIDS_PERFORMANCE = "extensions.getAddons.getWithPerformance.url";
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
// Forcibly end the test if it runs longer than 15 minutes
const TIMEOUT_MS = 900000;
@ -1453,6 +1454,9 @@ Services.prefs.setCharPref("extensions.hotfix.id", "");
Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_APP_VERSION, "0");
Services.prefs.setCharPref(PREF_EM_MIN_COMPAT_PLATFORM_VERSION, "0");
// Disable signature checks for most tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
// Register a temporary directory for the tests.
const gTmpD = gProfD.clone();
gTmpD.append("temp");
@ -1530,9 +1534,6 @@ do_register_cleanup(function addon_cleanup() {
testDir.leafName = "staged";
pathShouldntExist(testDir);
testDir.leafName = "staged-xpis";
pathShouldntExist(testDir);
shutdownManager();
// Clear commonly set prefs.

View File

@ -197,35 +197,11 @@ function run_test() {
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
do_check_false(a5.hasBinaryComponents);
// addon6 should be installed and compatible and packed unless unpacking is
// forced
do_check_neq(a6, null);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_true(a6.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
do_check_eq(a6.getResourceURI("install.rdf").scheme, "file");
else
do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar");
do_check_false(a6.hasBinaryComponents);
// addon7 should be installed and compatible and unpacked
do_check_neq(a7, null);
do_check_false(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_true(a7.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a7.id));
do_check_eq(a7.getResourceURI("install.rdf").scheme, "file");
do_check_false(a7.hasBinaryComponents);
// addon8 should be installed and compatible and have binary components
do_check_neq(a8, null);
do_check_false(a8.userDisabled);
do_check_false(a8.appDisabled);
do_check_true(a8.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a8.id));
do_check_true(a8.hasBinaryComponents);
// addon6, addon7 and addon8 will have been lost as they were staged in the
// pre-Firefox 4.0 directory
do_check_eq(a6, null);
do_check_eq(a7, null);
do_check_eq(a8, null);
// Theme 1 was previously enabled
do_check_neq(t1, null);
@ -243,8 +219,6 @@ function run_test() {
do_check_false(isThemeInAddonsList(profileDir, t2.id));
do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE));
do_check_false(stagedXPIs.exists());
do_execute_soon(do_test_finished);
});
}

View File

@ -196,25 +196,10 @@ function run_test() {
do_check_false(a5.isActive);
do_check_false(isExtensionInAddonsList(profileDir, a5.id));
// addon6 should be installed and compatible and packed unless unpacking is
// forced
do_check_neq(a6, null);
do_check_false(a6.userDisabled);
do_check_false(a6.appDisabled);
do_check_true(a6.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a6.id));
if (Services.prefs.getBoolPref("extensions.alwaysUnpack"))
do_check_eq(a6.getResourceURI("install.rdf").scheme, "file");
else
do_check_eq(a6.getResourceURI("install.rdf").scheme, "jar");
// addon7 should be installed and compatible and unpacked
do_check_neq(a7, null);
do_check_false(a7.userDisabled);
do_check_false(a7.appDisabled);
do_check_true(a7.isActive);
do_check_true(isExtensionInAddonsList(profileDir, a7.id));
do_check_eq(a7.getResourceURI("install.rdf").scheme, "file");
// addon6 and addon7 will have been lost as they were staged in the
// pre-Firefox 4.0 directory
do_check_eq(a6, null);
do_check_eq(a7, null);
// Theme 1 was previously disabled
do_check_neq(t1, null);
@ -232,8 +217,6 @@ function run_test() {
do_check_false(isThemeInAddonsList(profileDir, t2.id));
do_check_true(hasFlag(t2.permissions, AddonManager.PERM_CAN_ENABLE));
do_check_false(stagedXPIs.exists());
do_execute_soon(do_test_finished);
});
}

View File

@ -0,0 +1,320 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
const DATA = "data/signing_checks/";
const ADDONS = {
bootstrap: {
unsigned: "unsigned_bootstrap_2.xpi",
badid: "signed_bootstrap_badid_2.xpi",
signed: "signed_bootstrap_2.xpi",
},
nonbootstrap: {
unsigned: "unsigned_nonbootstrap_2.xpi",
badid: "signed_nonbootstrap_badid_2.xpi",
signed: "signed_nonbootstrap_2.xpi",
}
};
const ID = "test@tests.mozilla.org";
const profileDir = gProfD.clone();
profileDir.append("extensions");
// Deletes a file from the test add-on in the profile
function breakAddon(file) {
if (TEST_UNPACKED) {
file.append("test.txt");
file.remove(true);
}
else {
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
createInstance(AM_Ci.nsIZipWriter);
zipW.open(file, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
zipW.removeEntry("test.txt", false);
zipW.close();
}
}
function resetPrefs() {
Services.prefs.setIntPref("bootstraptest.active_version", -1);
Services.prefs.setIntPref("bootstraptest.installed_version", -1);
Services.prefs.setIntPref("bootstraptest.startup_reason", -1);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1);
Services.prefs.setIntPref("bootstraptest.install_reason", -1);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1);
Services.prefs.setIntPref("bootstraptest.install_oldversion", -1);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1);
}
function getActiveVersion() {
return Services.prefs.getIntPref("bootstraptest.active_version");
}
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
// Start and stop the manager to initialise everything in the profile before
// actual testing
startupManager();
shutdownManager();
resetPrefs();
run_next_test();
}
// Injecting into profile (bootstrap)
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.unsigned), profileDir, ID);
startupManager();
// Currently we leave the sideloaded add-on there but just don't run it
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
do_check_eq(getActiveVersion(), -1);
addon.uninstall();
yield promiseShutdownManager();
resetPrefs();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
breakAddon(getFileForAddon(profileDir, ID));
startupManager();
// Currently we leave the sideloaded add-on there but just don't run it
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_eq(getActiveVersion(), -1);
addon.uninstall();
yield promiseShutdownManager();
resetPrefs();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.badid), profileDir, ID);
startupManager();
// Currently we leave the sideloaded add-on there but just don't run it
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_eq(getActiveVersion(), -1);
addon.uninstall();
yield promiseShutdownManager();
resetPrefs();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
// Installs a signed add-on then modifies it in place breaking its signing
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), profileDir, ID);
// Make it appear to come from the past so when we modify it later it is
// detected during startup. Obviously malware can bypass this method of
// detection but the periodic scan will catch that
yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 600000);
startupManager();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
do_check_eq(getActiveVersion(), 2);
yield promiseShutdownManager();
do_check_eq(getActiveVersion(), 0);
breakAddon(getFileForAddon(profileDir, ID));
resetPrefs();
startupManager();
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_eq(getActiveVersion(), -1);
let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
do_check_eq(ids.length, 1);
do_check_eq(ids[0], ID);
addon.uninstall();
yield promiseShutdownManager();
resetPrefs();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
// Injecting into profile (non-bootstrap)
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.unsigned), profileDir, ID);
startupManager();
// Currently we leave the sideloaded add-on there but just don't run it
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
do_check_false(isExtensionInAddonsList(profileDir, ID));
addon.uninstall();
yield promiseRestartManager();
yield promiseShutdownManager();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
breakAddon(getFileForAddon(profileDir, ID));
startupManager();
// Currently we leave the sideloaded add-on there but just don't run it
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_false(isExtensionInAddonsList(profileDir, ID));
addon.uninstall();
yield promiseRestartManager();
yield promiseShutdownManager();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.badid), profileDir, ID);
startupManager();
// Currently we leave the sideloaded add-on there but just don't run it
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_false(isExtensionInAddonsList(profileDir, ID));
addon.uninstall();
yield promiseRestartManager();
yield promiseShutdownManager();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
// Installs a signed add-on then modifies it in place breaking its signing
add_task(function*() {
manuallyInstall(do_get_file(DATA + ADDONS.nonbootstrap.signed), profileDir, ID);
// Make it appear to come from the past so when we modify it later it is
// detected during startup. Obviously malware can bypass this method of
// detection but the periodic scan will catch that
yield promiseSetExtensionModifiedTime(getFileForAddon(profileDir, ID).path, Date.now() - 60000);
startupManager();
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
do_check_true(isExtensionInAddonsList(profileDir, ID));
yield promiseShutdownManager();
breakAddon(getFileForAddon(profileDir, ID));
startupManager();
addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
do_check_false(isExtensionInAddonsList(profileDir, ID));
let ids = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
do_check_eq(ids.length, 1);
do_check_eq(ids[0], ID);
addon.uninstall();
yield promiseRestartManager();
yield promiseShutdownManager();
do_check_false(getFileForAddon(profileDir, ID).exists());
});
// Stage install then modify before startup (non-bootstrap)
add_task(function*() {
startupManager();
yield promiseInstallAllFiles([do_get_file(DATA + ADDONS.nonbootstrap.signed)]);
yield promiseShutdownManager();
let staged = profileDir.clone();
staged.append("staged");
staged.append(do_get_expected_addon_name(ID));
do_check_true(staged.exists());
breakAddon(staged);
startupManager();
// Should have refused to install the broken staged version
let addon = yield promiseAddonByID(ID);
do_check_eq(addon, null);
let install = getFileForAddon(profileDir, ID);
do_check_false(install.exists());
yield promiseShutdownManager();
});
// Manufacture staged install (bootstrap)
add_task(function*() {
let stage = profileDir.clone();
stage.append("staged");
let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.signed), stage, ID);
breakAddon(file);
startupManager();
// Should have refused to install the broken staged version
let addon = yield promiseAddonByID(ID);
do_check_eq(addon, null);
do_check_eq(getActiveVersion(), -1);
let install = getFileForAddon(profileDir, ID);
do_check_false(install.exists());
yield promiseShutdownManager();
resetPrefs();
});

View File

@ -0,0 +1,265 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
const DATA = "data/signing_checks/";
const ADDONS = {
bootstrap: {
unsigned: "unsigned_bootstrap_2.xpi",
badid: "signed_bootstrap_badid_2.xpi",
preliminary: "preliminary_bootstrap_2.xpi",
signed: "signed_bootstrap_2.xpi",
},
};
const WORKING = "signed_bootstrap_1.xpi";
const ID = "test@tests.mozilla.org";
Components.utils.import("resource://testing-common/httpd.js");
var gServer = new HttpServer();
gServer.start(4444);
// Creates an add-on with a broken signature by changing an existing file
function createBrokenAddonModify(file) {
let brokenFile = gTmpD.clone();
brokenFile.append("broken.xpi");
file.copyTo(brokenFile.parent, brokenFile.leafName);
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(AM_Ci.nsIStringInputStream);
stream.setData("FOOBAR", -1);
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
createInstance(AM_Ci.nsIZipWriter);
zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
zipW.removeEntry("test.txt", false);
zipW.addEntryStream("test.txt", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
stream, false);
zipW.close();
return brokenFile;
}
// Creates an add-on with a broken signature by adding a new file
function createBrokenAddonAdd(file) {
let brokenFile = gTmpD.clone();
brokenFile.append("broken.xpi");
file.copyTo(brokenFile.parent, brokenFile.leafName);
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(AM_Ci.nsIStringInputStream);
stream.setData("FOOBAR", -1);
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
createInstance(AM_Ci.nsIZipWriter);
zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
zipW.addEntryStream("test2.txt", 0, AM_Ci.nsIZipWriter.COMPRESSION_NONE,
stream, false);
zipW.close();
return brokenFile;
}
// Creates an add-on with a broken signature by removing an existing file
function createBrokenAddonRemove(file) {
let brokenFile = gTmpD.clone();
brokenFile.append("broken.xpi");
file.copyTo(brokenFile.parent, brokenFile.leafName);
var stream = AM_Cc["@mozilla.org/io/string-input-stream;1"].
createInstance(AM_Ci.nsIStringInputStream);
stream.setData("FOOBAR", -1);
var zipW = AM_Cc["@mozilla.org/zipwriter;1"].
createInstance(AM_Ci.nsIZipWriter);
zipW.open(brokenFile, FileUtils.MODE_RDWR | FileUtils.MODE_APPEND);
zipW.removeEntry("test.txt", false);
zipW.close();
return brokenFile;
}
function createInstall(url) {
return new Promise(resolve => {
AddonManager.getInstallForURL(url, resolve, "application/x-xpinstall");
});
}
function serveUpdateRDF(leafName) {
gServer.registerPathHandler("/update.rdf", function(request, response) {
let updateData = {};
updateData[ID] = [{
version: "2.0",
targetApplications: [{
id: "xpcshell@tests.mozilla.org",
minVersion: "4",
maxVersion: "6",
updateLink: "http://localhost:4444/" + leafName
}]
}];
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(createUpdateRDF(updateData));
});
}
function* test_install_broken(file, expectedError) {
gServer.registerFile("/" + file.leafName, file);
let install = yield createInstall("http://localhost:4444/" + file.leafName);
yield promiseCompleteAllInstalls([install]);
do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
do_check_eq(install.error, expectedError);
do_check_eq(install.addon, null);
gServer.registerFile("/" + file.leafName, null);
}
function* test_install_working(file, expectedSignedState) {
gServer.registerFile("/" + file.leafName, file);
let install = yield createInstall("http://localhost:4444/" + file.leafName);
yield promiseCompleteAllInstalls([install]);
do_check_eq(install.state, AddonManager.STATE_INSTALLED);
do_check_neq(install.addon, null);
do_check_eq(install.addon.signedState, expectedSignedState);
gServer.registerFile("/" + file.leafName, null);
install.addon.uninstall();
}
function* test_update_broken(file, expectedError) {
// First install the older version
yield promiseInstallAllFiles([do_get_file(DATA + WORKING)]);
gServer.registerFile("/" + file.leafName, file);
serveUpdateRDF(file.leafName);
let addon = yield promiseAddonByID(ID);
let install = yield promiseFindAddonUpdates(addon);
yield promiseCompleteAllInstalls([install]);
do_check_eq(install.state, AddonManager.STATE_DOWNLOAD_FAILED);
do_check_eq(install.error, expectedError);
do_check_eq(install.addon, null);
gServer.registerFile("/" + file.leafName, null);
gServer.registerPathHandler("/update.rdf", null);
addon.uninstall();
}
function* test_update_working(file, expectedSignedState) {
// First install the older version
yield promiseInstallAllFiles([do_get_file(DATA + WORKING)]);
gServer.registerFile("/" + file.leafName, file);
serveUpdateRDF(file.leafName);
let addon = yield promiseAddonByID(ID);
let install = yield promiseFindAddonUpdates(addon);
yield promiseCompleteAllInstalls([install]);
do_check_eq(install.state, AddonManager.STATE_INSTALLED);
do_check_neq(install.addon, null);
do_check_eq(install.addon.signedState, expectedSignedState);
gServer.registerFile("/" + file.leafName, null);
gServer.registerPathHandler("/update.rdf", null);
install.addon.uninstall();
}
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
startupManager();
run_next_test();
}
// Try to install a broken add-on
add_task(function*() {
let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.bootstrap.signed));
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
file.remove(true);
});
add_task(function*() {
let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.bootstrap.signed));
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
file.remove(true);
});
add_task(function*() {
let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.bootstrap.signed));
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
file.remove(true);
});
// Try to install an add-on with an incorrect ID
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.badid);
yield test_install_broken(file, AddonManager.ERROR_CORRUPT_FILE);
});
// Try to install an unsigned add-on
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.unsigned);
yield test_install_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED);
});
// Try to install a preliminarily reviewed add-on
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.preliminary);
yield test_install_working(file, AddonManager.SIGNEDSTATE_PRELIMINARY);
});
// Try to install a signed add-on
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.signed);
yield test_install_working(file, AddonManager.SIGNEDSTATE_SIGNED);
});
// Try to update to a broken add-on
add_task(function*() {
let file = createBrokenAddonModify(do_get_file(DATA + ADDONS.bootstrap.signed));
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
file.remove(true);
});
add_task(function*() {
let file = createBrokenAddonAdd(do_get_file(DATA + ADDONS.bootstrap.signed));
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
file.remove(true);
});
add_task(function*() {
let file = createBrokenAddonRemove(do_get_file(DATA + ADDONS.bootstrap.signed));
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
file.remove(true);
});
// Try to update to an add-on with an incorrect ID
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.badid);
yield test_update_broken(file, AddonManager.ERROR_CORRUPT_FILE);
});
// Try to update to an unsigned add-on
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.unsigned);
yield test_update_broken(file, AddonManager.ERROR_SIGNEDSTATE_REQUIRED);
});
// Try to update to a preliminarily reviewed add-on
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.preliminary);
yield test_update_working(file, AddonManager.SIGNEDSTATE_PRELIMINARY);
});
// Try to update to a signed add-on
add_task(function*() {
let file = do_get_file(DATA + ADDONS.bootstrap.signed);
yield test_update_working(file, AddonManager.SIGNEDSTATE_SIGNED);
});

View File

@ -0,0 +1,164 @@
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
// Disable update security
Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
const DATA = "data/signing_checks/";
const ADDONS = {
bootstrap: {
unsigned: "unsigned_bootstrap_2.xpi",
badid: "signed_bootstrap_badid_2.xpi",
signed: "signed_bootstrap_2.xpi",
},
nonbootstrap: {
unsigned: "unsigned_nonbootstrap_2.xpi",
badid: "signed_nonbootstrap_badid_2.xpi",
signed: "signed_nonbootstrap_2.xpi",
}
};
const ID = "test@tests.mozilla.org";
const profileDir = gProfD.clone();
profileDir.append("extensions");
function resetPrefs() {
Services.prefs.setIntPref("bootstraptest.active_version", -1);
Services.prefs.setIntPref("bootstraptest.installed_version", -1);
Services.prefs.setIntPref("bootstraptest.startup_reason", -1);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", -1);
Services.prefs.setIntPref("bootstraptest.install_reason", -1);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", -1);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", -1);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", -1);
Services.prefs.setIntPref("bootstraptest.install_oldversion", -1);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", -1);
}
function getActiveVersion() {
return Services.prefs.getIntPref("bootstraptest.active_version");
}
function run_test() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "4", "4");
// Start and stop the manager to initialise everything in the profile before
// actual testing
startupManager();
shutdownManager();
resetPrefs();
run_next_test();
}
// Removes the signedState field from add-ons in the json database to make it
// look like the database was written with an older version of the application
function stripDB() {
let jData = loadJSON(gExtensionsJSON);
jData.schemaVersion--;
for (let addon of jData.addons)
delete addon.signedState;
saveJSON(jData, gExtensionsJSON);
}
function* test_breaking_migrate(addons, test, expectedSignedState) {
// Startup as the old version
gAppInfo.version = "4";
startupManager(true);
// Install the signed add-on
yield promiseInstallAllFiles([do_get_file(DATA + addons.signed)]);
// Restart to let non-restartless add-ons install fully
yield promiseRestartManager();
yield promiseShutdownManager();
resetPrefs();
stripDB();
// Now replace it with the version to test. Doing this so quickly shouldn't
// trigger the file modification code to detect the change by itself.
manuallyUninstall(profileDir, ID);
manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID);
// Update the application
gAppInfo.version = "5";
startupManager(true);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_true(addon.appDisabled);
do_check_false(addon.isActive);
do_check_eq(addon.signedState, expectedSignedState);
// Add-on shouldn't be active
if (addons == ADDONS.bootstrap)
do_check_eq(getActiveVersion(), -1);
else
do_check_false(isExtensionInAddonsList(profileDir, ID));
// Should have flagged the change during startup
let changes = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_DISABLED);
do_check_eq(changes.length, 1);
do_check_eq(changes[0], ID);
addon.uninstall();
// Restart to let non-restartless add-ons uninstall fully
yield promiseRestartManager();
yield shutdownManager();
resetPrefs();
}
function* test_working_migrate(addons, test, expectedSignedState) {
// Startup as the old version
gAppInfo.version = "4";
startupManager(true);
// Install the signed add-on
yield promiseInstallAllFiles([do_get_file(DATA + addons.signed)]);
// Restart to let non-restartless add-ons install fully
yield promiseRestartManager();
yield promiseShutdownManager();
resetPrefs();
stripDB();
// Now replace it with the version to test. Doing this so quickly shouldn't
// trigger the file modification code to detect the change by itself.
manuallyUninstall(profileDir, ID);
manuallyInstall(do_get_file(DATA + addons[test]), profileDir, ID);
// Update the application
gAppInfo.version = "5";
startupManager(true);
let addon = yield promiseAddonByID(ID);
do_check_neq(addon, null);
do_check_false(addon.appDisabled);
do_check_true(addon.isActive);
do_check_eq(addon.signedState, expectedSignedState);
if (addons == ADDONS.bootstrap)
do_check_eq(getActiveVersion(), 2);
else
do_check_true(isExtensionInAddonsList(profileDir, ID));
addon.uninstall();
// Restart to let non-restartless add-ons uninstall fully
yield promiseRestartManager();
yield shutdownManager();
resetPrefs();
}
add_task(function*() {
yield test_breaking_migrate(ADDONS.bootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING);
yield test_breaking_migrate(ADDONS.nonbootstrap, "unsigned", AddonManager.SIGNEDSTATE_MISSING);
});
add_task(function*() {
yield test_breaking_migrate(ADDONS.bootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN);
yield test_breaking_migrate(ADDONS.nonbootstrap, "badid", AddonManager.SIGNEDSTATE_BROKEN);
});
add_task(function*() {
yield test_working_migrate(ADDONS.bootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED);
yield test_working_migrate(ADDONS.nonbootstrap, "signed", AddonManager.SIGNEDSTATE_SIGNED);
});

View File

@ -22,6 +22,11 @@ skip-if = appname != "firefox"
[test_provider_unsafe_access_shutdown.js]
[test_provider_unsafe_access_startup.js]
[test_shutdown.js]
[test_signed_inject.js]
skip-if = true
[test_signed_install.js]
run-sequentially = Uses hardcoded ports in xpi files.
[test_signed_migrate.js]
[test_XPIcancel.js]
[test_XPIStates.js]