mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 13:45:27 +00:00
Merge fx-sync to mozilla-central.
This commit is contained in:
commit
21e0aa0006
224
services/crypto/IWeaveCrypto.idl
Normal file
224
services/crypto/IWeaveCrypto.idl
Normal file
@ -0,0 +1,224 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave code.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Mozilla Corporation
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com> (original author)
|
||||
* Honza Bambas <honzab@allpeers.com>
|
||||
* Justin Dolske <dolske@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(f4463043-315e-41f3-b779-82e900e6fffa)]
|
||||
interface IWeaveCrypto : nsISupports
|
||||
{
|
||||
/**
|
||||
* Shortcuts for some algorithm SEC OIDs. Full list available here:
|
||||
* http://lxr.mozilla.org/seamonkey/source/security/nss/lib/util/secoidt.h
|
||||
*/
|
||||
|
||||
const unsigned long DES_EDE3_CBC = 156;
|
||||
const unsigned long AES_128_CBC = 184;
|
||||
const unsigned long AES_192_CBC = 186;
|
||||
const unsigned long AES_256_CBC = 188;
|
||||
|
||||
/**
|
||||
* One of the above constants. Used as the mechanism for encrypting bulk
|
||||
* data and wrapping keys.
|
||||
*
|
||||
* Default is AES_256_CBC.
|
||||
*/
|
||||
attribute unsigned long algorithm;
|
||||
|
||||
/**
|
||||
* The size of the RSA key to create with generateKeypair().
|
||||
*
|
||||
* Default is 2048.
|
||||
*/
|
||||
attribute unsigned long keypairBits;
|
||||
|
||||
/**
|
||||
* Encrypt data using a symmetric key.
|
||||
* The algorithm attribute specifies how the encryption is performed.
|
||||
*
|
||||
* @param clearText
|
||||
* The data to be encrypted (not base64 encoded).
|
||||
* @param symmetricKey
|
||||
* A base64-encoded symmetric key (eg, one from generateRandomKey).
|
||||
* @param iv
|
||||
* A base64-encoded initialization vector
|
||||
* @returns Encrypted data, base64 encoded
|
||||
*/
|
||||
ACString encrypt(in AUTF8String clearText,
|
||||
in ACString symmetricKey, in ACString iv);
|
||||
|
||||
/**
|
||||
* Encrypt data using a symmetric key.
|
||||
* The algorithm attribute specifies how the encryption is performed.
|
||||
*
|
||||
* @param cipherText
|
||||
* The base64-encoded data to be decrypted
|
||||
* @param symmetricKey
|
||||
* A base64-encoded symmetric key (eg, one from unwrapSymmetricKey)
|
||||
* @param iv
|
||||
* A base64-encoded initialization vector
|
||||
* @returns Decrypted data (not base64-encoded)
|
||||
*/
|
||||
AUTF8String decrypt(in ACString cipherText,
|
||||
in ACString symmetricKey, in ACString iv);
|
||||
|
||||
/**
|
||||
* Generate a RSA public/private keypair.
|
||||
*
|
||||
* @param aPassphrase
|
||||
* User's passphrase. Used with PKCS#5 to generate a symmetric key
|
||||
* for wrapping the private key.
|
||||
* @param aSalt
|
||||
* Salt for the user's passphrase.
|
||||
* @param aIV
|
||||
* Random IV, used when wrapping the private key.
|
||||
* @param aEncodedPublicKey
|
||||
* The public key, base-64 encoded.
|
||||
* @param aWrappedPrivateKey
|
||||
* The public key, encrypted with the user's passphrase, and base-64 encoded.
|
||||
*/
|
||||
void generateKeypair(in ACString aPassphrase, in ACString aSalt, in ACString aIV,
|
||||
out ACString aEncodedPublicKey, out ACString aWrappedPrivateKey);
|
||||
|
||||
/*
|
||||
* Generate a random symmetric key.
|
||||
*
|
||||
* @returns The random key, base64 encoded
|
||||
*/
|
||||
ACString generateRandomKey();
|
||||
|
||||
/*
|
||||
* Generate a random IV.
|
||||
*
|
||||
* The IV will be sized for the algorithm specified in the algorithm
|
||||
* attribute of IWeaveCrypto.
|
||||
*
|
||||
* @returns The random IV, base64 encoded
|
||||
*/
|
||||
ACString generateRandomIV();
|
||||
|
||||
/*
|
||||
* Generate random data.
|
||||
*
|
||||
* @param aByteCount
|
||||
* The number of bytes of random data to generate.
|
||||
* @returns The random bytes, base64-encoded
|
||||
*/
|
||||
ACString generateRandomBytes(in unsigned long aByteCount);
|
||||
|
||||
|
||||
/**
|
||||
* Encrypts a symmetric key with a user's public key.
|
||||
*
|
||||
* @param aSymmetricKey
|
||||
* The base64 encoded string holding a symmetric key.
|
||||
* @param aEncodedPublicKey
|
||||
* The base64 encoded string holding a public key.
|
||||
* @returns The wrapped symmetric key, base64 encoded
|
||||
*
|
||||
* For RSA, the unencoded public key is a PKCS#1 object.
|
||||
*/
|
||||
ACString wrapSymmetricKey(in ACString aSymmetricKey,
|
||||
in ACString aEncodedPublicKey);
|
||||
|
||||
/**
|
||||
* Decrypts a symmetric key with a user's private key.
|
||||
*
|
||||
* @param aWrappedSymmetricKey
|
||||
* The base64 encoded string holding an encrypted symmetric key.
|
||||
* @param aWrappedPrivateKey
|
||||
* The base64 encoded string holdering an encrypted private key.
|
||||
* @param aPassphrase
|
||||
* The passphrase to decrypt the private key.
|
||||
* @param aSalt
|
||||
* The salt for the passphrase.
|
||||
* @param aIV
|
||||
* The random IV used when unwrapping the private key.
|
||||
* @returns The unwrapped symmetric key, base64 encoded
|
||||
*
|
||||
* For RSA, the unencoded, decrypted key is a PKCS#1 object.
|
||||
*/
|
||||
ACString unwrapSymmetricKey(in ACString aWrappedSymmetricKey,
|
||||
in ACString aWrappedPrivateKey,
|
||||
in ACString aPassphrase,
|
||||
in ACString aSalt,
|
||||
in ACString aIV);
|
||||
|
||||
/**
|
||||
* Rewrap a private key with a new user passphrase.
|
||||
*
|
||||
* @param aWrappedPrivateKey
|
||||
* The base64 encoded string holding an encrypted private key.
|
||||
* @param aPassphrase
|
||||
* The passphrase to decrypt the private key.
|
||||
* @param aSalt
|
||||
* The salt for the passphrase.
|
||||
* @param aIV
|
||||
* The random IV used when unwrapping the private key.
|
||||
* @param aNewPassphrase
|
||||
* The new passphrase to wrap the private key with.
|
||||
* @returns The (re)wrapped private key, base64 encoded
|
||||
*
|
||||
*/
|
||||
ACString rewrapPrivateKey(in ACString aWrappedPrivateKey,
|
||||
in ACString aPassphrase,
|
||||
in ACString aSalt,
|
||||
in ACString aIV,
|
||||
in ACString aNewPassphrase);
|
||||
|
||||
/**
|
||||
* Verify a user's passphrase against a private key.
|
||||
*
|
||||
* @param aWrappedPrivateKey
|
||||
* The base64 encoded string holding an encrypted private key.
|
||||
* @param aPassphrase
|
||||
* The passphrase to decrypt the private key.
|
||||
* @param aSalt
|
||||
* The salt for the passphrase.
|
||||
* @param aIV
|
||||
* The random IV used when unwrapping the private key.
|
||||
* @returns Boolean true if the passphrase decrypted the key correctly.
|
||||
*
|
||||
*/
|
||||
boolean verifyPassphrase(in ACString aWrappedPrivateKey,
|
||||
in ACString aPassphrase,
|
||||
in ACString aSalt,
|
||||
in ACString aIV);
|
||||
};
|
||||
|
1131
services/crypto/WeaveCrypto.js
Normal file
1131
services/crypto/WeaveCrypto.js
Normal file
File diff suppressed because it is too large
Load Diff
59
services/sync/FormNotifier.js
Normal file
59
services/sync/FormNotifier.js
Normal file
@ -0,0 +1,59 @@
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function FormNotifier() {
|
||||
let formClass = Components.classesByID["{a2059c0e-5a58-4c55-ab7c-26f0557546ef}"] ||
|
||||
Components.classesByID["{0c1bb408-71a2-403f-854a-3a0659829ded}"];
|
||||
let baseForm = formClass.getService(Ci.nsIFormHistory2);
|
||||
let obs = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
|
||||
for (let keyval in Iterator(baseForm)) {
|
||||
// Make a local copy of these values
|
||||
let [key, val] = keyval;
|
||||
|
||||
// Don't overwrite something we already have
|
||||
if (key in this)
|
||||
continue;
|
||||
|
||||
// Make a getter to grab non-functions
|
||||
if (typeof val != "function") {
|
||||
this.__defineGetter__(key, function() baseForm[key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// XXX Bug 568707 Make use of "key" to prevent it from disappearing
|
||||
(function(){})(key);
|
||||
|
||||
// Wrap the function with notifications
|
||||
this[key] = function() {
|
||||
let args = Array.slice(arguments);
|
||||
let notify = function(type) {
|
||||
obs.notifyObservers(null, "form-notifier", JSON.stringify({
|
||||
args: args,
|
||||
func: key,
|
||||
type: type
|
||||
}));
|
||||
};
|
||||
|
||||
notify("before");
|
||||
try {
|
||||
return val.apply(this, arguments);
|
||||
}
|
||||
finally {
|
||||
notify("after");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
FormNotifier.prototype = {
|
||||
classDescription: "Form Notifier Wrapper",
|
||||
contractID: "@mozilla.org/satchel/form-history;1",
|
||||
classID: Components.ID("{be5a097b-6ee6-4c6a-8eca-6bce87d570e9}"),
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFormHistory2]),
|
||||
};
|
||||
|
||||
let components = [FormNotifier];
|
||||
function NSGetModule(compMgr, fileSpec) XPCOMUtils.generateModule(components);
|
158
services/sync/Weave.js
Normal file
158
services/sync/Weave.js
Normal file
@ -0,0 +1,158 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function WeaveService() {
|
||||
this.wrappedJSObject = this;
|
||||
}
|
||||
WeaveService.prototype = {
|
||||
classDescription: "Weave Service",
|
||||
contractID: "@mozilla.org/weave/service;1",
|
||||
classID: Components.ID("{74b89fb0-f200-4ae8-a3ec-dd164117f6de}"),
|
||||
_xpcom_categories: [{ category: "app-startup", service: true }],
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
observe: function BSS__observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "app-startup":
|
||||
let os = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
os.addObserver(this, "final-ui-startup", true);
|
||||
this.addResourceAlias();
|
||||
break;
|
||||
|
||||
case "final-ui-startup":
|
||||
// Force Weave service to load if it hasn't triggered from overlays
|
||||
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this.timer.initWithCallback({
|
||||
notify: function() {
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
}
|
||||
}, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
addResourceAlias: function() {
|
||||
let ioService = Cc["@mozilla.org/network/io-service;1"]
|
||||
.getService(Ci.nsIIOService);
|
||||
let resProt = ioService.getProtocolHandler("resource")
|
||||
.QueryInterface(Ci.nsIResProtocolHandler);
|
||||
|
||||
// Only create alias if resource://services-sync doesn't already exist.
|
||||
if (resProt.hasSubstitution("services-sync"))
|
||||
return;
|
||||
|
||||
let uri = ioService.newURI("resource://gre/modules/services-sync",
|
||||
null, null);
|
||||
let file = uri.QueryInterface(Ci.nsIFileURL)
|
||||
.file.QueryInterface(Ci.nsILocalFile);
|
||||
|
||||
let aliasURI = ioService.newFileURI(file);
|
||||
resProt.setSubstitution("services-sync", aliasURI);
|
||||
}
|
||||
};
|
||||
|
||||
function AboutWeaveLog() {}
|
||||
AboutWeaveLog.prototype = {
|
||||
classDescription: "about:sync-log",
|
||||
contractID: "@mozilla.org/network/protocol/about;1?what=sync-log",
|
||||
classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
getURIFlags: function(aURI) {
|
||||
return 0;
|
||||
},
|
||||
|
||||
newChannel: function(aURI) {
|
||||
let dir = Cc["@mozilla.org/file/directory_service;1"].
|
||||
getService(Ci.nsIProperties);
|
||||
let file = dir.get("ProfD", Ci.nsILocalFile);
|
||||
file.append("weave");
|
||||
file.append("logs");
|
||||
file.append("verbose-log.txt");
|
||||
let ios = Cc["@mozilla.org/network/io-service;1"].
|
||||
getService(Ci.nsIIOService);
|
||||
let ch = ios.newChannel(ios.newFileURI(file).spec, null, null);
|
||||
ch.originalURI = aURI;
|
||||
return ch;
|
||||
}
|
||||
};
|
||||
|
||||
function AboutWeaveLog1() {}
|
||||
AboutWeaveLog1.prototype = {
|
||||
classDescription: "about:sync-log.1",
|
||||
contractID: "@mozilla.org/network/protocol/about;1?what=sync-log.1",
|
||||
classID: Components.ID("{a08ee179-df50-48e0-9c87-79e4dd5caeb1}"),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule,
|
||||
Ci.nsISupportsWeakReference]),
|
||||
|
||||
getURIFlags: function(aURI) {
|
||||
return 0;
|
||||
},
|
||||
|
||||
newChannel: function(aURI) {
|
||||
let dir = Cc["@mozilla.org/file/directory_service;1"].
|
||||
getService(Ci.nsIProperties);
|
||||
let file = dir.get("ProfD", Ci.nsILocalFile);
|
||||
file.append("weave");
|
||||
file.append("logs");
|
||||
file.append("verbose-log.txt.1");
|
||||
let ios = Cc["@mozilla.org/network/io-service;1"].
|
||||
getService(Ci.nsIIOService);
|
||||
let ch = ios.newChannel(ios.newFileURI(file).spec, null, null);
|
||||
ch.originalURI = aURI;
|
||||
return ch;
|
||||
}
|
||||
};
|
||||
|
||||
function NSGetModule(compMgr, fileSpec) {
|
||||
return XPCOMUtils.generateModule([
|
||||
WeaveService,
|
||||
AboutWeaveLog,
|
||||
AboutWeaveLog1
|
||||
]);
|
||||
}
|
26
services/sync/locales/en-US/errors.properties
Normal file
26
services/sync/locales/en-US/errors.properties
Normal file
@ -0,0 +1,26 @@
|
||||
error.login.reason.network = Failed to connect to the server
|
||||
error.login.reason.passphrase = Wrong secret phrase
|
||||
error.login.reason.password = Incorrect username or password
|
||||
error.login.reason.no_password= No saved password to use
|
||||
error.login.reason.no_passphrase= No saved secret phrase to use
|
||||
error.login.reason.server = Server incorrectly configured
|
||||
|
||||
error.sync.failed_partial = One or more data types could not be synced
|
||||
|
||||
invalid-captcha = Incorrect words, try again
|
||||
weak-password = Use a stronger password
|
||||
|
||||
# this is the fallback, if we hit an error we didn't bother to localize
|
||||
error.reason.unknown = Unknown error
|
||||
|
||||
change.passphrase.ppSameAsPassphrase = The secret phrase cannot be the same as your current secret phrase
|
||||
change.passphrase.ppSameAsPassword = The secret phrase cannot be the same as your password
|
||||
change.passphrase.ppSameAsUsername = The secret phrase cannot be the same as your user name
|
||||
change.passphrase.mismatch = The phrases entered do not match
|
||||
change.passphrase.tooShort = The secret phrase entered is too short
|
||||
|
||||
change.password.pwSameAsPassphrase = Password can't match secret phrase
|
||||
change.password.pwSameAsPassword = Password can't match current password
|
||||
change.password.pwSameAsUsername = Password can't match your user name
|
||||
change.password.mismatch = The passwords entered do not match
|
||||
change.password.tooShort = The password entered is too short
|
37
services/sync/locales/en-US/sync.properties
Normal file
37
services/sync/locales/en-US/sync.properties
Normal file
@ -0,0 +1,37 @@
|
||||
# %1: the user name (Ed), %2: the app name (Firefox), %3: the operating system (Android)
|
||||
client.name2 = %1$S's %2$S on %3$S
|
||||
|
||||
# %S is the date and time at which the last sync successfully completed
|
||||
lastSync.label = Last Update: %S
|
||||
lastSyncInProgress.label = Last Update: in progress…
|
||||
|
||||
# %S is the username logged in
|
||||
connected.label = Connected: %S
|
||||
disconnected.label = Disconnected
|
||||
connecting.label = Connecting…
|
||||
|
||||
mobile.label = Mobile Bookmarks
|
||||
|
||||
remote.pending.label = Remote tabs are being synced…
|
||||
remote.missing.label = Sync your other computers again to access their tabs
|
||||
remote.opened.label = All remote tabs are already open
|
||||
remote.notification.label = Recent desktop tabs will be available once they sync
|
||||
|
||||
error.login.title = Error While Signing In
|
||||
error.login.description = Sync encountered an error while connecting: %1$S. Please try again.
|
||||
error.login.prefs.label = Preferences…
|
||||
error.login.prefs.accesskey = P
|
||||
# should decide if we're going to show this
|
||||
error.logout.title = Error While Signing Out
|
||||
error.logout.description = Sync encountered an error while connecting. It's probably ok, and you don't have to do anything about it.
|
||||
error.sync.title = Error While Syncing
|
||||
error.sync.description = Sync encountered an error while syncing: %1$S. Sync will automatically retry this action.
|
||||
error.sync.no_node_found = The Sync server is a little busy right now, but you don't need to do anything about it. We'll start syncing your data as soon as we can!
|
||||
error.sync.no_node_found.title = Sync Delay
|
||||
error.sync.needUpdate.description = You need to update Firefox Sync to continue syncing your data.
|
||||
error.sync.needUpdate.label = Update Firefox Sync
|
||||
error.sync.needUpdate.accesskey = U
|
||||
error.sync.tryAgainButton.label = Sync Now
|
||||
error.sync.tryAgainButton.accesskey = S
|
||||
|
||||
tabs.fromOtherComputers.label = Tabs From Other Computers
|
87
services/sync/modules/auth.js
Normal file
87
services/sync/modules/auth.js
Normal file
@ -0,0 +1,87 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Auth', 'BasicAuthenticator', 'NoOpAuthenticator'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
Utils.lazy(this, 'Auth', AuthMgr);
|
||||
|
||||
// XXX: the authenticator api will probably need to be changed to support
|
||||
// other methods (digest, oauth, etc)
|
||||
|
||||
function NoOpAuthenticator() {}
|
||||
NoOpAuthenticator.prototype = {
|
||||
onRequest: function NoOpAuth_onRequest(headers) {
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
|
||||
function BasicAuthenticator(identity) {
|
||||
this._id = identity;
|
||||
}
|
||||
BasicAuthenticator.prototype = {
|
||||
onRequest: function BasicAuth_onRequest(headers) {
|
||||
headers['Authorization'] = 'Basic ' +
|
||||
btoa(this._id.username + ':' + this._id.password);
|
||||
return headers;
|
||||
}
|
||||
};
|
||||
|
||||
function AuthMgr() {
|
||||
this._authenticators = {};
|
||||
this.defaultAuthenticator = new NoOpAuthenticator();
|
||||
}
|
||||
AuthMgr.prototype = {
|
||||
defaultAuthenticator: null,
|
||||
|
||||
registerAuthenticator: function AuthMgr_register(match, authenticator) {
|
||||
this._authenticators[match] = authenticator;
|
||||
},
|
||||
|
||||
lookupAuthenticator: function AuthMgr_lookup(uri) {
|
||||
for (let match in this._authenticators) {
|
||||
if (uri.match(match))
|
||||
return this._authenticators[match];
|
||||
}
|
||||
return this.defaultAuthenticator;
|
||||
}
|
||||
};
|
159
services/sync/modules/base_records/collection.js
Normal file
159
services/sync/modules/base_records/collection.js
Normal file
@ -0,0 +1,159 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Collection'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function Collection(uri, recordObj) {
|
||||
Resource.call(this, uri);
|
||||
this._recordObj = recordObj;
|
||||
|
||||
this._full = false;
|
||||
this._ids = null;
|
||||
this._limit = 0;
|
||||
this._older = 0;
|
||||
this._newer = 0;
|
||||
this._data = [];
|
||||
}
|
||||
Collection.prototype = {
|
||||
__proto__: Resource.prototype,
|
||||
_logName: "Collection",
|
||||
|
||||
_rebuildURL: function Coll__rebuildURL() {
|
||||
// XXX should consider what happens if it's not a URL...
|
||||
this.uri.QueryInterface(Ci.nsIURL);
|
||||
|
||||
let args = [];
|
||||
if (this.older)
|
||||
args.push('older=' + this.older);
|
||||
else if (this.newer) {
|
||||
args.push('newer=' + this.newer);
|
||||
}
|
||||
if (this.full)
|
||||
args.push('full=1');
|
||||
if (this.sort)
|
||||
args.push('sort=' + this.sort);
|
||||
if (this.ids != null)
|
||||
args.push("ids=" + this.ids);
|
||||
if (this.limit > 0 && this.limit != Infinity)
|
||||
args.push("limit=" + this.limit);
|
||||
|
||||
this.uri.query = (args.length > 0)? '?' + args.join('&') : '';
|
||||
},
|
||||
|
||||
// get full items
|
||||
get full() { return this._full; },
|
||||
set full(value) {
|
||||
this._full = value;
|
||||
this._rebuildURL();
|
||||
},
|
||||
|
||||
// Apply the action to a certain set of ids
|
||||
get ids() this._ids,
|
||||
set ids(value) {
|
||||
this._ids = value;
|
||||
this._rebuildURL();
|
||||
},
|
||||
|
||||
// Limit how many records to get
|
||||
get limit() this._limit,
|
||||
set limit(value) {
|
||||
this._limit = value;
|
||||
this._rebuildURL();
|
||||
},
|
||||
|
||||
// get only items modified before some date
|
||||
get older() { return this._older; },
|
||||
set older(value) {
|
||||
this._older = value;
|
||||
this._rebuildURL();
|
||||
},
|
||||
|
||||
// get only items modified since some date
|
||||
get newer() { return this._newer; },
|
||||
set newer(value) {
|
||||
this._newer = value;
|
||||
this._rebuildURL();
|
||||
},
|
||||
|
||||
// get items sorted by some criteria. valid values:
|
||||
// oldest (oldest first)
|
||||
// newest (newest first)
|
||||
// index
|
||||
get sort() { return this._sort; },
|
||||
set sort(value) {
|
||||
this._sort = value;
|
||||
this._rebuildURL();
|
||||
},
|
||||
|
||||
pushData: function Coll_pushData(data) {
|
||||
this._data.push(data);
|
||||
},
|
||||
|
||||
clearRecords: function Coll_clearRecords() {
|
||||
this._data = [];
|
||||
},
|
||||
|
||||
set recordHandler(onRecord) {
|
||||
// Save this because onProgress is called with this as the ChannelListener
|
||||
let coll = this;
|
||||
|
||||
// Switch to newline separated records for incremental parsing
|
||||
coll.setHeader("Accept", "application/newlines");
|
||||
|
||||
this._onProgress = function() {
|
||||
let newline;
|
||||
while ((newline = this._data.indexOf("\n")) > 0) {
|
||||
// Split the json record from the rest of the data
|
||||
let json = this._data.slice(0, newline);
|
||||
this._data = this._data.slice(newline + 1);
|
||||
|
||||
// Deserialize a record from json and give it to the callback
|
||||
let record = new coll._recordObj();
|
||||
record.deserialize(json);
|
||||
record.baseUri = coll.uri;
|
||||
onRecord(record);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
201
services/sync/modules/base_records/crypto.js
Normal file
201
services/sync/modules/base_records/crypto.js
Normal file
@ -0,0 +1,201 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['CryptoWrapper', 'CryptoMeta', 'CryptoMetas'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function CryptoWrapper(uri) {
|
||||
this.cleartext = {};
|
||||
WBORecord.call(this, uri);
|
||||
this.encryption = "";
|
||||
this.ciphertext = null;
|
||||
}
|
||||
CryptoWrapper.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.CryptoWrapper",
|
||||
|
||||
encrypt: function CryptoWrapper_encrypt(passphrase) {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
|
||||
let meta = CryptoMetas.get(this.encryption);
|
||||
let symkey = meta.getKey(privkey, passphrase);
|
||||
|
||||
this.IV = Svc.Crypto.generateRandomIV();
|
||||
this.ciphertext = Svc.Crypto.encrypt(JSON.stringify(this.cleartext),
|
||||
symkey, this.IV);
|
||||
this.hmac = Utils.sha256HMAC(this.ciphertext, symkey.hmacKey);
|
||||
this.cleartext = null;
|
||||
},
|
||||
|
||||
decrypt: function CryptoWrapper_decrypt(passphrase) {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
|
||||
let meta = CryptoMetas.get(this.encryption);
|
||||
let symkey = meta.getKey(privkey, passphrase);
|
||||
|
||||
// Authenticate the encrypted blob with the expected HMAC
|
||||
if (Utils.sha256HMAC(this.ciphertext, symkey.hmacKey) != this.hmac)
|
||||
throw "Record SHA256 HMAC mismatch: " + this.hmac;
|
||||
|
||||
this.cleartext = JSON.parse(Svc.Crypto.decrypt(this.ciphertext, symkey,
|
||||
this.IV));
|
||||
this.ciphertext = null;
|
||||
|
||||
// Verify that the encrypted id matches the requested record's id
|
||||
if (this.cleartext.id != this.id)
|
||||
throw "Record id mismatch: " + [this.cleartext.id, this.id];
|
||||
|
||||
return this.cleartext;
|
||||
},
|
||||
|
||||
toString: function CryptoWrap_toString() "{ " + [
|
||||
"id: " + this.id,
|
||||
"index: " + this.sortindex,
|
||||
"modified: " + this.modified,
|
||||
"payload: " + (this.deleted ? "DELETED" : JSON.stringify(this.cleartext))
|
||||
].join("\n ") + " }",
|
||||
|
||||
// The custom setter below masks the parent's getter, so explicitly call it :(
|
||||
get id() WBORecord.prototype.__lookupGetter__("id").call(this),
|
||||
|
||||
// Keep both plaintext and encrypted versions of the id to verify integrity
|
||||
set id(val) {
|
||||
WBORecord.prototype.__lookupSetter__("id").call(this, val);
|
||||
return this.cleartext.id = val;
|
||||
},
|
||||
};
|
||||
|
||||
Utils.deferGetSet(CryptoWrapper, "payload", ["ciphertext", "encryption", "IV",
|
||||
"hmac"]);
|
||||
Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted");
|
||||
|
||||
function CryptoMeta(uri) {
|
||||
WBORecord.call(this, uri);
|
||||
this.keyring = {};
|
||||
}
|
||||
CryptoMeta.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.CryptoMeta",
|
||||
|
||||
getKey: function CryptoMeta_getKey(privkey, passphrase) {
|
||||
// get the uri to our public key
|
||||
let pubkeyUri = privkey.publicKeyUri.spec;
|
||||
|
||||
// each hash key is a relative uri, resolve those and match against ours
|
||||
let wrapped_key;
|
||||
for (let relUri in this.keyring) {
|
||||
if (pubkeyUri == this.baseUri.resolve(relUri))
|
||||
wrapped_key = this.keyring[relUri];
|
||||
}
|
||||
if (!wrapped_key)
|
||||
throw "keyring doesn't contain a key for " + pubkeyUri;
|
||||
|
||||
// Make sure the wrapped key hasn't been tampered with
|
||||
let localHMAC = Utils.sha256HMAC(wrapped_key.wrapped, this.hmacKey);
|
||||
if (localHMAC != wrapped_key.hmac)
|
||||
throw "Key SHA256 HMAC mismatch: " + wrapped_key.hmac;
|
||||
|
||||
// Decrypt the symmetric key and make it a String object to add properties
|
||||
let unwrappedKey = new String(
|
||||
Svc.Crypto.unwrapSymmetricKey(
|
||||
wrapped_key.wrapped,
|
||||
privkey.keyData,
|
||||
passphrase.password,
|
||||
privkey.salt,
|
||||
privkey.iv
|
||||
)
|
||||
);
|
||||
|
||||
unwrappedKey.hmacKey = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC,
|
||||
unwrappedKey);
|
||||
|
||||
// Cache the result after the first get and just return it
|
||||
return (this.getKey = function() unwrappedKey)();
|
||||
},
|
||||
|
||||
addKey: function CryptoMeta_addKey(new_pubkey, privkey, passphrase) {
|
||||
let symkey = this.getKey(privkey, passphrase);
|
||||
this.addUnwrappedKey(new_pubkey, symkey);
|
||||
},
|
||||
|
||||
addUnwrappedKey: function CryptoMeta_addUnwrappedKey(new_pubkey, symkey) {
|
||||
// get the new public key
|
||||
if (typeof new_pubkey == "string")
|
||||
new_pubkey = PubKeys.get(new_pubkey);
|
||||
|
||||
// each hash key is a relative uri, resolve those and
|
||||
// if we find the one we're about to add, remove it
|
||||
for (let relUri in this.keyring) {
|
||||
if (pubkeyUri == this.uri.resolve(relUri))
|
||||
delete this.keyring[relUri];
|
||||
}
|
||||
|
||||
// Wrap the symmetric key and generate a HMAC for it
|
||||
let wrapped = Svc.Crypto.wrapSymmetricKey(symkey, new_pubkey.keyData);
|
||||
this.keyring[new_pubkey.uri.spec] = {
|
||||
wrapped: wrapped,
|
||||
hmac: Utils.sha256HMAC(wrapped, this.hmacKey)
|
||||
};
|
||||
},
|
||||
|
||||
get hmacKey() {
|
||||
let passphrase = ID.get("WeaveCryptoID").password;
|
||||
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, passphrase);
|
||||
}
|
||||
};
|
||||
|
||||
Utils.deferGetSet(CryptoMeta, "payload", "keyring");
|
||||
|
||||
Utils.lazy(this, 'CryptoMetas', CryptoRecordManager);
|
||||
|
||||
function CryptoRecordManager() {
|
||||
RecordManager.call(this);
|
||||
}
|
||||
CryptoRecordManager.prototype = {
|
||||
__proto__: RecordManager.prototype,
|
||||
_recordType: CryptoMeta
|
||||
};
|
178
services/sync/modules/base_records/keys.js
Normal file
178
services/sync/modules/base_records/keys.js
Normal file
@ -0,0 +1,178 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['PubKey', 'PrivKey',
|
||||
'PubKeys', 'PrivKeys'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PubKey(uri) {
|
||||
WBORecord.call(this, uri);
|
||||
this.type = "pubkey";
|
||||
this.keyData = null;
|
||||
this.privateKeyUri = null;
|
||||
}
|
||||
PubKey.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.PubKey",
|
||||
|
||||
get privateKeyUri() {
|
||||
if (!this.data)
|
||||
return null;
|
||||
|
||||
// Use the uri if it resolves, otherwise return raw (uri type unresolvable)
|
||||
let key = this.payload.privateKeyUri;
|
||||
return Utils.makeURI(this.uri.resolve(key) || key);
|
||||
},
|
||||
|
||||
get publicKeyUri() {
|
||||
throw "attempted to get public key url from a public key!";
|
||||
}
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PubKey, "payload", ["keyData", "privateKeyUri", "type"]);
|
||||
|
||||
function PrivKey(uri) {
|
||||
WBORecord.call(this, uri);
|
||||
this.type = "privkey";
|
||||
this.salt = null;
|
||||
this.iv = null;
|
||||
this.keyData = null;
|
||||
this.publicKeyUri = null;
|
||||
}
|
||||
PrivKey.prototype = {
|
||||
__proto__: WBORecord.prototype,
|
||||
_logName: "Record.PrivKey",
|
||||
|
||||
get publicKeyUri() {
|
||||
if (!this.data)
|
||||
return null;
|
||||
|
||||
// Use the uri if it resolves, otherwise return raw (uri type unresolvable)
|
||||
let key = this.payload.publicKeyUri;
|
||||
return Utils.makeURI(this.uri.resolve(key) || key);
|
||||
},
|
||||
|
||||
get privateKeyUri() {
|
||||
throw "attempted to get private key url from a private key!";
|
||||
}
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PrivKey, "payload", ["salt", "iv", "keyData", "publicKeyUri", "type"]);
|
||||
|
||||
// XXX unused/unfinished
|
||||
function SymKey(keyData, wrapped) {
|
||||
this._data = keyData;
|
||||
this._wrapped = wrapped;
|
||||
}
|
||||
SymKey.prototype = {
|
||||
get wrapped() {
|
||||
return this._wrapped;
|
||||
},
|
||||
|
||||
unwrap: function SymKey_unwrap(privkey, passphrase, meta_record) {
|
||||
this._data =
|
||||
Svc.Crypto.unwrapSymmetricKey(this._data, privkey.keyData, passphrase,
|
||||
privkey.salt, privkey.iv);
|
||||
}
|
||||
};
|
||||
|
||||
Utils.lazy(this, 'PubKeys', PubKeyManager);
|
||||
|
||||
function PubKeyManager() {
|
||||
RecordManager.call(this);
|
||||
}
|
||||
PubKeyManager.prototype = {
|
||||
__proto__: RecordManager.prototype,
|
||||
_recordType: PubKey,
|
||||
_logName: "PubKeyManager",
|
||||
|
||||
get defaultKeyUri() this._defaultKeyUri,
|
||||
set defaultKeyUri(value) { this._defaultKeyUri = value; },
|
||||
|
||||
getDefaultKey: function PubKeyManager_getDefaultKey() {
|
||||
return this.get(this.defaultKeyUri);
|
||||
},
|
||||
|
||||
createKeypair: function KeyMgr_createKeypair(passphrase, pubkeyUri, privkeyUri) {
|
||||
this._log.debug("Generating RSA keypair");
|
||||
let pubkey = new PubKey();
|
||||
let privkey = new PrivKey();
|
||||
privkey.salt = Svc.Crypto.generateRandomBytes(16);
|
||||
privkey.iv = Svc.Crypto.generateRandomIV();
|
||||
|
||||
let pub = {}, priv = {};
|
||||
Svc.Crypto.generateKeypair(passphrase.password, privkey.salt, privkey.iv, pub, priv);
|
||||
[pubkey.keyData, privkey.keyData] = [pub.value, priv.value];
|
||||
|
||||
if (pubkeyUri) {
|
||||
pubkey.uri = pubkeyUri;
|
||||
privkey.publicKeyUri = pubkeyUri;
|
||||
}
|
||||
if (privkeyUri) {
|
||||
privkey.uri = privkeyUri;
|
||||
pubkey.privateKeyUri = privkeyUri;
|
||||
}
|
||||
|
||||
this._log.debug("Generating RSA keypair... done");
|
||||
return {pubkey: pubkey, privkey: privkey};
|
||||
},
|
||||
|
||||
uploadKeypair: function PubKeyManager_uploadKeypair(keys) {
|
||||
for each (let key in keys)
|
||||
new Resource(key.uri).put(key);
|
||||
}
|
||||
};
|
||||
|
||||
Utils.lazy(this, 'PrivKeys', PrivKeyManager);
|
||||
|
||||
function PrivKeyManager() {
|
||||
PubKeyManager.call(this);
|
||||
}
|
||||
PrivKeyManager.prototype = {
|
||||
__proto__: PubKeyManager.prototype,
|
||||
_recordType: PrivKey,
|
||||
_logName: "PrivKeyManager"
|
||||
};
|
163
services/sync/modules/base_records/wbo.js
Normal file
163
services/sync/modules/base_records/wbo.js
Normal file
@ -0,0 +1,163 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['WBORecord', 'RecordManager', 'Records'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function WBORecord(uri) {
|
||||
this.data = {};
|
||||
this.payload = {};
|
||||
if (uri)
|
||||
this.uri = uri;
|
||||
}
|
||||
WBORecord.prototype = {
|
||||
_logName: "Record.WBO",
|
||||
|
||||
// NOTE: baseUri must have a trailing slash, or baseUri.resolve() will omit
|
||||
// the collection name
|
||||
get uri() {
|
||||
return Utils.makeURI(this.baseUri.resolve(encodeURI(this.id)));
|
||||
},
|
||||
set uri(value) {
|
||||
if (typeof(value) != "string")
|
||||
value = value.spec;
|
||||
let foo = value.split('/');
|
||||
this.id = foo.pop();
|
||||
this.baseUri = Utils.makeURI(foo.join('/') + '/');
|
||||
},
|
||||
|
||||
get sortindex() {
|
||||
if (this.data.sortindex)
|
||||
return this.data.sortindex;
|
||||
return 0;
|
||||
},
|
||||
|
||||
deserialize: function deserialize(json) {
|
||||
this.data = json.constructor.toString() == String ? JSON.parse(json) : json;
|
||||
|
||||
try {
|
||||
// The payload is likely to be JSON, but if not, keep it as a string
|
||||
this.payload = JSON.parse(this.payload);
|
||||
}
|
||||
catch(ex) {}
|
||||
},
|
||||
|
||||
toJSON: function toJSON() {
|
||||
// Copy fields from data to be stringified, making sure payload is a string
|
||||
let obj = {};
|
||||
for (let [key, val] in Iterator(this.data))
|
||||
obj[key] = key == "payload" ? JSON.stringify(val) : val;
|
||||
return obj;
|
||||
},
|
||||
|
||||
toString: function WBORec_toString() "{ " + [
|
||||
"id: " + this.id,
|
||||
"index: " + this.sortindex,
|
||||
"modified: " + this.modified,
|
||||
"payload: " + JSON.stringify(this.payload)
|
||||
].join("\n ") + " }",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(WBORecord, "data", ["id", "modified", "sortindex", "payload"]);
|
||||
|
||||
Utils.lazy(this, 'Records', RecordManager);
|
||||
|
||||
function RecordManager() {
|
||||
this._log = Log4Moz.repository.getLogger(this._logName);
|
||||
this._records = {};
|
||||
}
|
||||
RecordManager.prototype = {
|
||||
_recordType: WBORecord,
|
||||
_logName: "RecordMgr",
|
||||
|
||||
import: function RecordMgr_import(url) {
|
||||
this._log.trace("Importing record: " + (url.spec ? url.spec : url));
|
||||
try {
|
||||
// Clear out the last response with empty object if GET fails
|
||||
this.response = {};
|
||||
this.response = new Resource(url).get();
|
||||
|
||||
// Don't parse and save the record on failure
|
||||
if (!this.response.success)
|
||||
return null;
|
||||
|
||||
let record = new this._recordType();
|
||||
record.deserialize(this.response);
|
||||
record.uri = url;
|
||||
|
||||
return this.set(url, record);
|
||||
}
|
||||
catch(ex) {
|
||||
this._log.debug("Failed to import record: " + Utils.exceptionStr(ex));
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
get: function RecordMgr_get(url) {
|
||||
// Use a url string as the key to the hash
|
||||
let spec = url.spec ? url.spec : url;
|
||||
if (spec in this._records)
|
||||
return this._records[spec];
|
||||
return this.import(url);
|
||||
},
|
||||
|
||||
set: function RegordMgr_set(url, record) {
|
||||
let spec = url.spec ? url.spec : url;
|
||||
return this._records[spec] = record;
|
||||
},
|
||||
|
||||
contains: function RegordMgr_contains(url) {
|
||||
if ((url.spec || url) in this._records)
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
clearCache: function recordMgr_clearCache() {
|
||||
this._records = {};
|
||||
},
|
||||
|
||||
del: function RegordMgr_del(url) {
|
||||
delete this._records[url];
|
||||
}
|
||||
};
|
145
services/sync/modules/constants.js
Normal file
145
services/sync/modules/constants.js
Normal file
@ -0,0 +1,145 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
// Process each item in the "constants hash" to add to "global" and give a name
|
||||
let EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({
|
||||
|
||||
WEAVE_CHANNEL: "@xpi_type@",
|
||||
WEAVE_VERSION: "@weave_version@",
|
||||
WEAVE_ID: "@weave_id@",
|
||||
|
||||
// Version of the data format this client supports. The data format describes
|
||||
// how records are packaged; this is separate from the Server API version and
|
||||
// the per-engine cleartext formats.
|
||||
STORAGE_VERSION: 2,
|
||||
|
||||
UPDATED_DEV_URL: "https://services.mozilla.com/sync/updated/?version=@weave_version@&channel=@xpi_type@",
|
||||
UPDATED_REL_URL: "http://www.mozilla.com/firefox/sync/updated.html",
|
||||
|
||||
PREFS_BRANCH: "services.sync.",
|
||||
|
||||
// Host "key" to access Weave Identity in the password manager
|
||||
PWDMGR_HOST: "chrome://weave",
|
||||
PWDMGR_PASSWORD_REALM: "Mozilla Services Password",
|
||||
PWDMGR_PASSPHRASE_REALM: "Mozilla Services Encryption Passphrase",
|
||||
|
||||
// Sync intervals for various clients configurations
|
||||
SINGLE_USER_SYNC: 24 * 60 * 60 * 1000, // 1 day
|
||||
MULTI_DESKTOP_SYNC: 60 * 60 * 1000, // 1 hour
|
||||
MULTI_MOBILE_SYNC: 5 * 60 * 1000, // 5 minutes
|
||||
PARTIAL_DATA_SYNC: 60 * 1000, // 1 minute
|
||||
|
||||
// score thresholds for early syncs
|
||||
SINGLE_USER_THRESHOLD: 1000,
|
||||
MULTI_DESKTOP_THRESHOLD: 500,
|
||||
MULTI_MOBILE_THRESHOLD: 100,
|
||||
|
||||
// File IO Flags
|
||||
MODE_RDONLY: 0x01,
|
||||
MODE_WRONLY: 0x02,
|
||||
MODE_CREATE: 0x08,
|
||||
MODE_APPEND: 0x10,
|
||||
MODE_TRUNCATE: 0x20,
|
||||
|
||||
// File Permission flags
|
||||
PERMS_FILE: 0644,
|
||||
PERMS_PASSFILE: 0600,
|
||||
PERMS_DIRECTORY: 0755,
|
||||
|
||||
// Number of records to upload in a single POST (multiple POSTS if exceeded)
|
||||
// FIXME: Record size limit is 256k (new cluster), so this can be quite large!
|
||||
// (Bug 569295)
|
||||
MAX_UPLOAD_RECORDS: 100,
|
||||
|
||||
// Top-level statuses:
|
||||
STATUS_OK: "success.status_ok",
|
||||
SYNC_FAILED: "error.sync.failed",
|
||||
LOGIN_FAILED: "error.login.failed",
|
||||
SYNC_FAILED_PARTIAL: "error.sync.failed_partial",
|
||||
CLIENT_NOT_CONFIGURED: "service.client_not_configured",
|
||||
STATUS_DISABLED: "service.disabled",
|
||||
|
||||
// success states
|
||||
LOGIN_SUCCEEDED: "success.login",
|
||||
SYNC_SUCCEEDED: "success.sync",
|
||||
ENGINE_SUCCEEDED: "success.engine",
|
||||
|
||||
// login failure status codes:
|
||||
LOGIN_FAILED_NO_USERNAME: "error.login.reason.no_username",
|
||||
LOGIN_FAILED_NO_PASSWORD: "error.login.reason.no_password",
|
||||
LOGIN_FAILED_NO_PASSPHRASE: "error.login.reason.no_passphrase",
|
||||
LOGIN_FAILED_NETWORK_ERROR: "error.login.reason.network",
|
||||
LOGIN_FAILED_SERVER_ERROR: "error.login.reason.server",
|
||||
LOGIN_FAILED_INVALID_PASSPHRASE: "error.login.reason.passphrase",
|
||||
LOGIN_FAILED_LOGIN_REJECTED: "error.login.reason.password",
|
||||
|
||||
// sync failure status codes
|
||||
METARECORD_DOWNLOAD_FAIL: "error.sync.reason.metarecord_download_fail",
|
||||
VERSION_OUT_OF_DATE: "error.sync.reason.version_out_of_date",
|
||||
DESKTOP_VERSION_OUT_OF_DATE: "error.sync.reason.desktop_version_out_of_date",
|
||||
KEYS_DOWNLOAD_FAIL: "error.sync.reason.keys_download_fail",
|
||||
NO_KEYS_NO_KEYGEN: "error.sync.reason.no_keys_no_keygen",
|
||||
KEYS_UPLOAD_FAIL: "error.sync.reason.keys_upload_fail",
|
||||
SETUP_FAILED_NO_PASSPHRASE: "error.sync.reason.setup_failed_no_passphrase",
|
||||
CREDENTIALS_CHANGED: "error.sync.reason.credentials_changed",
|
||||
ABORT_SYNC_COMMAND: "aborting sync, process commands said so",
|
||||
NO_SYNC_NODE_FOUND: "error.sync.reason.no_node_found",
|
||||
|
||||
// engine failure status codes
|
||||
ENGINE_UPLOAD_FAIL: "error.engine.reason.record_upload_fail",
|
||||
ENGINE_DOWNLOAD_FAIL: "error.engine.reason.record_download_fail",
|
||||
ENGINE_UNKNOWN_FAIL: "error.engine.reason.unknown_fail",
|
||||
ENGINE_METARECORD_DOWNLOAD_FAIL: "error.engine.reason.metarecord_download_fail",
|
||||
ENGINE_METARECORD_UPLOAD_FAIL: "error.engine.reason.metarecord_upload_fail",
|
||||
|
||||
// Ways that a sync can be disabled (messages only to be printed in debug log)
|
||||
kSyncWeaveDisabled: "Weave is disabled",
|
||||
kSyncNotLoggedIn: "User is not logged in",
|
||||
kSyncNetworkOffline: "Network is offline",
|
||||
kSyncInPrivateBrowsing: "Private browsing is enabled",
|
||||
kSyncBackoffNotMet: "Trying to sync before the server said it's okay",
|
||||
kFirstSyncChoiceNotMade: "User has not selected an action for first sync",
|
||||
|
||||
// Application IDs
|
||||
FIREFOX_ID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
|
||||
FENNEC_ID: "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
|
||||
SEAMONKEY_ID: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
|
||||
TEST_HARNESS_ID: "xuth@mozilla.org",
|
||||
|
||||
MIN_PP_LENGTH: 12,
|
||||
MIN_PASS_LENGTH: 8
|
||||
|
||||
}))];
|
768
services/sync/modules/engines.js
Normal file
768
services/sync/modules/engines.js
Normal file
@ -0,0 +1,768 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Engines', 'Engine', 'SyncEngine'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/collection.js");
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/ext/Sync.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// Singleton service, holds registered engines
|
||||
|
||||
Utils.lazy(this, 'Engines', EngineManagerSvc);
|
||||
|
||||
function EngineManagerSvc() {
|
||||
this._engines = {};
|
||||
this._log = Log4Moz.repository.getLogger("Service.Engines");
|
||||
this._log.level = Log4Moz.Level[Svc.Prefs.get(
|
||||
"log.logger.service.engines", "Debug")];
|
||||
}
|
||||
EngineManagerSvc.prototype = {
|
||||
get: function EngMgr_get(name) {
|
||||
// Return an array of engines if we have an array of names
|
||||
if (Utils.isArray(name)) {
|
||||
let engines = [];
|
||||
name.forEach(function(name) {
|
||||
let engine = this.get(name);
|
||||
if (engine)
|
||||
engines.push(engine);
|
||||
}, this);
|
||||
return engines;
|
||||
}
|
||||
|
||||
let engine = this._engines[name];
|
||||
if (!engine)
|
||||
this._log.debug("Could not get engine: " + name);
|
||||
return engine;
|
||||
},
|
||||
getAll: function EngMgr_getAll() {
|
||||
return [engine for ([name, engine] in Iterator(Engines._engines))];
|
||||
},
|
||||
getEnabled: function EngMgr_getEnabled() {
|
||||
return this.getAll().filter(function(engine) engine.enabled);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register an Engine to the service. Alternatively, give an array of engine
|
||||
* objects to register.
|
||||
*
|
||||
* @param engineObject
|
||||
* Engine object used to get an instance of the engine
|
||||
* @return The engine object if anything failed
|
||||
*/
|
||||
register: function EngMgr_register(engineObject) {
|
||||
if (Utils.isArray(engineObject))
|
||||
return engineObject.map(this.register, this);
|
||||
|
||||
try {
|
||||
let engine = new engineObject();
|
||||
let name = engine.name;
|
||||
if (name in this._engines)
|
||||
this._log.error("Engine '" + name + "' is already registered!");
|
||||
else
|
||||
this._engines[name] = engine;
|
||||
}
|
||||
catch(ex) {
|
||||
let mesg = ex.message ? ex.message : ex;
|
||||
let name = engineObject || "";
|
||||
name = name.prototype || "";
|
||||
name = name.name || "";
|
||||
|
||||
let out = "Could not initialize engine '" + name + "': " + mesg;
|
||||
dump(out);
|
||||
this._log.error(out);
|
||||
|
||||
return engineObject;
|
||||
}
|
||||
},
|
||||
unregister: function EngMgr_unregister(val) {
|
||||
let name = val;
|
||||
if (val instanceof Engine)
|
||||
name = val.name;
|
||||
delete this._engines[name];
|
||||
}
|
||||
};
|
||||
|
||||
function Engine(name) {
|
||||
this.Name = name || "Unnamed";
|
||||
this.name = name.toLowerCase();
|
||||
|
||||
this._notify = Utils.notify("weave:engine:");
|
||||
this._log = Log4Moz.repository.getLogger("Engine." + this.Name);
|
||||
let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
|
||||
this._log.level = Log4Moz.Level[level];
|
||||
|
||||
this._tracker; // initialize tracker to load previously changed IDs
|
||||
this._log.debug("Engine initialized");
|
||||
}
|
||||
Engine.prototype = {
|
||||
// _storeObj, and _trackerObj should to be overridden in subclasses
|
||||
_storeObj: Store,
|
||||
_trackerObj: Tracker,
|
||||
|
||||
get prefName() this.name,
|
||||
get enabled() Svc.Prefs.get("engine." + this.prefName, false),
|
||||
set enabled(val) Svc.Prefs.set("engine." + this.prefName, !!val),
|
||||
|
||||
get score() this._tracker.score,
|
||||
|
||||
get _store() {
|
||||
let store = new this._storeObj(this.Name);
|
||||
this.__defineGetter__("_store", function() store);
|
||||
return store;
|
||||
},
|
||||
|
||||
get _tracker() {
|
||||
let tracker = new this._trackerObj(this.Name);
|
||||
this.__defineGetter__("_tracker", function() tracker);
|
||||
return tracker;
|
||||
},
|
||||
|
||||
sync: function Engine_sync() {
|
||||
if (!this.enabled)
|
||||
return;
|
||||
|
||||
if (!this._sync)
|
||||
throw "engine does not implement _sync method";
|
||||
|
||||
let times = {};
|
||||
let wrapped = {};
|
||||
// Find functions in any point of the prototype chain
|
||||
for (let _name in this) {
|
||||
let name = _name;
|
||||
|
||||
// Ignore certain constructors/functions
|
||||
if (name.search(/^_(.+Obj|notify)$/) == 0)
|
||||
continue;
|
||||
|
||||
// Only track functions but skip the constructors
|
||||
if (typeof this[name] == "function") {
|
||||
times[name] = [];
|
||||
wrapped[name] = this[name];
|
||||
|
||||
// Wrap the original function with a start/stop timer
|
||||
this[name] = function() {
|
||||
let start = Date.now();
|
||||
try {
|
||||
return wrapped[name].apply(this, arguments);
|
||||
}
|
||||
finally {
|
||||
times[name].push(Date.now() - start);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this._notify("sync", this.name, this._sync)();
|
||||
}
|
||||
finally {
|
||||
// Restore original unwrapped functionality
|
||||
for (let [name, func] in Iterator(wrapped))
|
||||
this[name] = func;
|
||||
|
||||
let stats = {};
|
||||
for (let [name, time] in Iterator(times)) {
|
||||
// Figure out stats on the times unless there's nothing
|
||||
let num = time.length;
|
||||
if (num == 0)
|
||||
continue;
|
||||
|
||||
// Track the min/max/sum of the values
|
||||
let stat = {
|
||||
num: num,
|
||||
sum: 0
|
||||
};
|
||||
time.forEach(function(val) {
|
||||
if (stat.min == null || val < stat.min)
|
||||
stat.min = val;
|
||||
if (stat.max == null || val > stat.max)
|
||||
stat.max = val;
|
||||
stat.sum += val;
|
||||
});
|
||||
|
||||
stat.avg = Number((stat.sum / num).toFixed(2));
|
||||
stats[name] = stat;
|
||||
}
|
||||
|
||||
stats.toString = function() {
|
||||
let sums = [];
|
||||
for (let [name, stat] in Iterator(this))
|
||||
if (stat.sum != null)
|
||||
sums.push(name.replace(/^_/, "") + " " + stat.sum);
|
||||
|
||||
// Order certain functions first before any other random ones
|
||||
let nameOrder = ["sync", "processIncoming", "uploadOutgoing",
|
||||
"syncStartup", "syncFinish"];
|
||||
let getPos = function(str) {
|
||||
let pos = nameOrder.indexOf(str.split(" ")[0]);
|
||||
return pos != -1 ? pos : Infinity;
|
||||
};
|
||||
let order = function(a, b) getPos(a) > getPos(b);
|
||||
|
||||
return "Total (ms): " + sums.sort(order).join(", ");
|
||||
};
|
||||
|
||||
this._log.debug(stats);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get rid of any local meta-data
|
||||
*/
|
||||
resetClient: function Engine_resetClient() {
|
||||
if (!this._resetClient)
|
||||
throw "engine does not implement _resetClient method";
|
||||
|
||||
this._notify("reset-client", this.name, this._resetClient)();
|
||||
},
|
||||
|
||||
_wipeClient: function Engine__wipeClient() {
|
||||
this.resetClient();
|
||||
this._log.debug("Deleting all local data");
|
||||
this._tracker.ignoreAll = true;
|
||||
this._store.wipe();
|
||||
this._tracker.ignoreAll = false;
|
||||
this._tracker.clearChangedIDs();
|
||||
},
|
||||
|
||||
wipeClient: function Engine_wipeClient() {
|
||||
this._notify("wipe-client", this.name, this._wipeClient)();
|
||||
}
|
||||
};
|
||||
|
||||
function SyncEngine(name) {
|
||||
Engine.call(this, name || "SyncEngine");
|
||||
this.loadToFetch();
|
||||
}
|
||||
SyncEngine.prototype = {
|
||||
__proto__: Engine.prototype,
|
||||
_recordObj: CryptoWrapper,
|
||||
version: 1,
|
||||
|
||||
get storageURL() Svc.Prefs.get("clusterURL") + Svc.Prefs.get("storageAPI") +
|
||||
"/" + ID.get("WeaveID").username + "/storage/",
|
||||
|
||||
get engineURL() this.storageURL + this.name,
|
||||
|
||||
get cryptoMetaURL() this.storageURL + "crypto/" + this.name,
|
||||
|
||||
get metaURL() this.storageURL + "meta/global",
|
||||
|
||||
get syncID() {
|
||||
// Generate a random syncID if we don't have one
|
||||
let syncID = Svc.Prefs.get(this.name + ".syncID", "");
|
||||
return syncID == "" ? this.syncID = Utils.makeGUID() : syncID;
|
||||
},
|
||||
set syncID(value) {
|
||||
Svc.Prefs.set(this.name + ".syncID", value);
|
||||
},
|
||||
|
||||
get lastSync() {
|
||||
return parseFloat(Svc.Prefs.get(this.name + ".lastSync", "0"));
|
||||
},
|
||||
set lastSync(value) {
|
||||
// Reset the pref in-case it's a number instead of a string
|
||||
Svc.Prefs.reset(this.name + ".lastSync");
|
||||
// Store the value as a string to keep floating point precision
|
||||
Svc.Prefs.set(this.name + ".lastSync", value.toString());
|
||||
},
|
||||
resetLastSync: function SyncEngine_resetLastSync() {
|
||||
this._log.debug("Resetting " + this.name + " last sync time");
|
||||
Svc.Prefs.reset(this.name + ".lastSync");
|
||||
Svc.Prefs.set(this.name + ".lastSync", "0");
|
||||
},
|
||||
|
||||
get toFetch() this._toFetch,
|
||||
set toFetch(val) {
|
||||
this._toFetch = val;
|
||||
Utils.jsonSave("toFetch/" + this.name, this, val);
|
||||
},
|
||||
|
||||
loadToFetch: function loadToFetch() {
|
||||
// Initialize to empty if there's no file
|
||||
this._toFetch = [];
|
||||
Utils.jsonLoad("toFetch/" + this.name, this, Utils.bind2(this, function(o)
|
||||
this._toFetch = o));
|
||||
},
|
||||
|
||||
// Create a new record using the store and add in crypto fields
|
||||
_createRecord: function SyncEngine__createRecord(id) {
|
||||
let record = this._store.createRecord(id);
|
||||
record.id = id;
|
||||
record.encryption = this.cryptoMetaURL;
|
||||
return record;
|
||||
},
|
||||
|
||||
// Any setup that needs to happen at the beginning of each sync.
|
||||
// Makes sure crypto records and keys are all set-up
|
||||
_syncStartup: function SyncEngine__syncStartup() {
|
||||
this._log.trace("Ensuring server crypto records are there");
|
||||
|
||||
// Try getting/unwrapping the crypto record
|
||||
let meta = CryptoMetas.get(this.cryptoMetaURL);
|
||||
if (meta) {
|
||||
try {
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
meta.getKey(privkey, ID.get("WeaveCryptoID"));
|
||||
}
|
||||
catch(ex) {
|
||||
// Indicate that we don't have a cryptometa to delete and reupload
|
||||
this._log.debug("Purging bad data after failed unwrap crypto: " + ex);
|
||||
meta = null;
|
||||
}
|
||||
}
|
||||
// Don't proceed if we failed to get the crypto meta for reasons not 404
|
||||
else if (CryptoMetas.response.status != 404) {
|
||||
let resp = CryptoMetas.response;
|
||||
resp.failureCode = ENGINE_METARECORD_DOWNLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
|
||||
// Determine if we need to wipe on outdated versions
|
||||
let metaGlobal = Records.get(this.metaURL);
|
||||
let engines = metaGlobal.payload.engines || {};
|
||||
let engineData = engines[this.name] || {};
|
||||
|
||||
// Assume missing versions are 0 and wipe the server
|
||||
if ((engineData.version || 0) < this.version) {
|
||||
this._log.debug("Old engine data: " + [engineData.version, this.version]);
|
||||
|
||||
// Prepare to clear the server and upload everything
|
||||
meta = null;
|
||||
this.syncID = "";
|
||||
|
||||
// Set the newer version and newly generated syncID
|
||||
engineData.version = this.version;
|
||||
engineData.syncID = this.syncID;
|
||||
|
||||
// Put the new data back into meta/global and mark for upload
|
||||
engines[this.name] = engineData;
|
||||
metaGlobal.payload.engines = engines;
|
||||
metaGlobal.changed = true;
|
||||
}
|
||||
// Don't sync this engine if the server has newer data
|
||||
else if (engineData.version > this.version) {
|
||||
let error = new String("New data: " + [engineData.version, this.version]);
|
||||
error.failureCode = VERSION_OUT_OF_DATE;
|
||||
throw error;
|
||||
}
|
||||
// Changes to syncID mean we'll need to upload everything
|
||||
else if (engineData.syncID != this.syncID) {
|
||||
this._log.debug("Engine syncIDs: " + [engineData.syncID, this.syncID]);
|
||||
this.syncID = engineData.syncID;
|
||||
this._resetClient();
|
||||
};
|
||||
|
||||
// Delete any existing data and reupload on bad version or missing meta
|
||||
if (meta == null) {
|
||||
new Resource(this.engineURL).delete();
|
||||
this._resetClient();
|
||||
|
||||
// Generate a new crypto record
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
let pubkey = PubKeys.getDefaultKey();
|
||||
meta = new CryptoMeta(this.cryptoMetaURL);
|
||||
meta.addUnwrappedKey(pubkey, symkey);
|
||||
let res = new Resource(meta.uri);
|
||||
let resp = res.put(meta);
|
||||
if (!resp.success) {
|
||||
this._log.debug("Metarecord upload fail:" + resp);
|
||||
resp.failureCode = ENGINE_METARECORD_UPLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
|
||||
// Cache the cryto meta that we just put on the server
|
||||
CryptoMetas.set(meta.uri, meta);
|
||||
}
|
||||
|
||||
// Mark all items to be uploaded, but treat them as changed from long ago
|
||||
if (!this.lastSync) {
|
||||
this._log.debug("First sync, uploading all items");
|
||||
for (let id in this._store.getAllIDs())
|
||||
this._tracker.addChangedID(id, 0);
|
||||
}
|
||||
|
||||
let outnum = [i for (i in this._tracker.changedIDs)].length;
|
||||
this._log.info(outnum + " outgoing items pre-reconciliation");
|
||||
|
||||
// Keep track of what to delete at the end of sync
|
||||
this._delete = {};
|
||||
},
|
||||
|
||||
// Generate outgoing records
|
||||
_processIncoming: function SyncEngine__processIncoming() {
|
||||
this._log.trace("Downloading & applying server changes");
|
||||
|
||||
// Figure out how many total items to fetch this sync; do less on mobile.
|
||||
// 50 is hardcoded here because of URL length restrictions.
|
||||
// (GUIDs can be up to 64 chars long)
|
||||
let fetchNum = Infinity;
|
||||
if (Svc.Prefs.get("client.type") == "mobile")
|
||||
fetchNum = 50;
|
||||
|
||||
let newitems = new Collection(this.engineURL, this._recordObj);
|
||||
newitems.newer = this.lastSync;
|
||||
newitems.full = true;
|
||||
newitems.sort = "index";
|
||||
newitems.limit = fetchNum;
|
||||
|
||||
let count = {applied: 0, reconciled: 0};
|
||||
let handled = [];
|
||||
newitems.recordHandler = Utils.bind2(this, function(item) {
|
||||
// Grab a later last modified if possible
|
||||
if (this.lastModified == null || item.modified > this.lastModified)
|
||||
this.lastModified = item.modified;
|
||||
|
||||
// Remember which records were processed
|
||||
handled.push(item.id);
|
||||
|
||||
try {
|
||||
item.decrypt(ID.get("WeaveCryptoID"));
|
||||
if (this._reconcile(item)) {
|
||||
count.applied++;
|
||||
this._tracker.ignoreAll = true;
|
||||
this._store.applyIncoming(item);
|
||||
} else {
|
||||
count.reconciled++;
|
||||
this._log.trace("Skipping reconciled incoming item " + item.id);
|
||||
}
|
||||
}
|
||||
catch(ex) {
|
||||
this._log.warn("Error processing record: " + Utils.exceptionStr(ex));
|
||||
|
||||
// Upload a new record to replace the bad one if we have it
|
||||
if (this._store.itemExists(item.id))
|
||||
this._tracker.addChangedID(item.id, 0);
|
||||
}
|
||||
this._tracker.ignoreAll = false;
|
||||
Sync.sleep(0);
|
||||
});
|
||||
|
||||
// Only bother getting data from the server if there's new things
|
||||
if (this.lastModified == null || this.lastModified > this.lastSync) {
|
||||
let resp = newitems.get();
|
||||
if (!resp.success) {
|
||||
resp.failureCode = ENGINE_DOWNLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
|
||||
// Subtract out the number of items we just got
|
||||
fetchNum -= handled.length;
|
||||
}
|
||||
|
||||
// Check if we got the maximum that we requested; get the rest if so
|
||||
if (handled.length == newitems.limit) {
|
||||
let guidColl = new Collection(this.engineURL);
|
||||
guidColl.newer = this.lastSync;
|
||||
guidColl.sort = "index";
|
||||
|
||||
let guids = guidColl.get();
|
||||
if (!guids.success)
|
||||
throw guids;
|
||||
|
||||
// Figure out which guids weren't just fetched then remove any guids that
|
||||
// were already waiting and prepend the new ones
|
||||
let extra = Utils.arraySub(guids.obj, handled);
|
||||
if (extra.length > 0)
|
||||
this.toFetch = extra.concat(Utils.arraySub(this.toFetch, extra));
|
||||
}
|
||||
|
||||
// Process any backlog of GUIDs if we haven't fetched too many this sync
|
||||
while (this.toFetch.length > 0 && fetchNum > 0) {
|
||||
// Reuse the original query, but get rid of the restricting params
|
||||
newitems.limit = 0;
|
||||
newitems.newer = 0;
|
||||
|
||||
// Get the first bunch of records and save the rest for later
|
||||
let minFetch = Math.min(150, this.toFetch.length, fetchNum);
|
||||
newitems.ids = this.toFetch.slice(0, minFetch);
|
||||
this.toFetch = this.toFetch.slice(minFetch);
|
||||
fetchNum -= minFetch;
|
||||
|
||||
// Reuse the existing record handler set earlier
|
||||
let resp = newitems.get();
|
||||
if (!resp.success) {
|
||||
resp.failureCode = ENGINE_DOWNLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.lastSync < this.lastModified)
|
||||
this.lastSync = this.lastModified;
|
||||
|
||||
this._log.info(["Records:", count.applied, "applied,", count.reconciled,
|
||||
"reconciled,", this.toFetch.length, "left to fetch"].join(" "));
|
||||
},
|
||||
|
||||
/**
|
||||
* Find a GUID of an item that is a duplicate of the incoming item but happens
|
||||
* to have a different GUID
|
||||
*
|
||||
* @return GUID of the similar item; falsy otherwise
|
||||
*/
|
||||
_findDupe: function _findDupe(item) {
|
||||
// By default, assume there's no dupe items for the engine
|
||||
},
|
||||
|
||||
_isEqual: function SyncEngine__isEqual(item) {
|
||||
let local = this._createRecord(item.id);
|
||||
if (this._log.level <= Log4Moz.Level.Trace)
|
||||
this._log.trace("Local record: " + local);
|
||||
if (Utils.deepEquals(item.cleartext, local.cleartext)) {
|
||||
this._log.trace("Local record is the same");
|
||||
return true;
|
||||
} else {
|
||||
this._log.trace("Local record is different");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
_deleteId: function _deleteId(id) {
|
||||
this._tracker.removeChangedID(id);
|
||||
|
||||
// Remember this id to delete at the end of sync
|
||||
if (this._delete.ids == null)
|
||||
this._delete.ids = [id];
|
||||
else
|
||||
this._delete.ids.push(id);
|
||||
},
|
||||
|
||||
_handleDupe: function _handleDupe(item, dupeId) {
|
||||
// Prefer shorter guids; for ties, just do an ASCII compare
|
||||
let preferLocal = dupeId.length < item.id.length ||
|
||||
(dupeId.length == item.id.length && dupeId < item.id);
|
||||
|
||||
if (preferLocal) {
|
||||
this._log.trace("Preferring local id: " + [dupeId, item.id]);
|
||||
this._deleteId(item.id);
|
||||
item.id = dupeId;
|
||||
this._tracker.addChangedID(dupeId, 0);
|
||||
}
|
||||
else {
|
||||
this._log.trace("Switching local id to incoming: " + [item.id, dupeId]);
|
||||
this._store.changeItemID(dupeId, item.id);
|
||||
this._deleteId(dupeId);
|
||||
}
|
||||
},
|
||||
|
||||
// Reconcile incoming and existing records. Return true if server
|
||||
// data should be applied.
|
||||
_reconcile: function SyncEngine__reconcile(item) {
|
||||
if (this._log.level <= Log4Moz.Level.Trace)
|
||||
this._log.trace("Incoming: " + item);
|
||||
|
||||
this._log.trace("Reconcile step 1: Check for conflicts");
|
||||
if (item.id in this._tracker.changedIDs) {
|
||||
// If the incoming and local changes are the same, skip
|
||||
if (this._isEqual(item)) {
|
||||
this._tracker.removeChangedID(item.id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Records differ so figure out which to take
|
||||
let recordAge = Resource.serverTime - item.modified;
|
||||
let localAge = Date.now() / 1000 - this._tracker.changedIDs[item.id];
|
||||
this._log.trace("Record age vs local age: " + [recordAge, localAge]);
|
||||
|
||||
// Apply the record if the record is newer (server wins)
|
||||
return recordAge < localAge;
|
||||
}
|
||||
|
||||
this._log.trace("Reconcile step 2: Check for updates");
|
||||
if (this._store.itemExists(item.id))
|
||||
return !this._isEqual(item);
|
||||
|
||||
this._log.trace("Reconcile step 2.5: Don't dupe deletes");
|
||||
if (item.deleted)
|
||||
return true;
|
||||
|
||||
this._log.trace("Reconcile step 3: Find dupes");
|
||||
let dupeId = this._findDupe(item);
|
||||
if (dupeId)
|
||||
this._handleDupe(item, dupeId);
|
||||
|
||||
// Apply the incoming item (now that the dupe is the right id)
|
||||
return true;
|
||||
},
|
||||
|
||||
// Upload outgoing records
|
||||
_uploadOutgoing: function SyncEngine__uploadOutgoing() {
|
||||
let outnum = [i for (i in this._tracker.changedIDs)].length;
|
||||
if (outnum) {
|
||||
this._log.trace("Preparing " + outnum + " outgoing records");
|
||||
|
||||
// collection we'll upload
|
||||
let up = new Collection(this.engineURL);
|
||||
let count = 0;
|
||||
|
||||
// Upload what we've got so far in the collection
|
||||
let doUpload = Utils.bind2(this, function(desc) {
|
||||
this._log.info("Uploading " + desc + " of " + outnum + " records");
|
||||
let resp = up.post();
|
||||
if (!resp.success) {
|
||||
this._log.debug("Uploading records failed: " + resp);
|
||||
resp.failureCode = ENGINE_UPLOAD_FAIL;
|
||||
throw resp;
|
||||
}
|
||||
|
||||
// Record the modified time of the upload
|
||||
let modified = resp.headers["x-weave-timestamp"];
|
||||
if (modified > this.lastSync)
|
||||
this.lastSync = modified;
|
||||
|
||||
up.clearRecords();
|
||||
});
|
||||
|
||||
for (let id in this._tracker.changedIDs) {
|
||||
try {
|
||||
let out = this._createRecord(id);
|
||||
if (this._log.level <= Log4Moz.Level.Trace)
|
||||
this._log.trace("Outgoing: " + out);
|
||||
|
||||
out.encrypt(ID.get("WeaveCryptoID"));
|
||||
up.pushData(out);
|
||||
}
|
||||
catch(ex) {
|
||||
this._log.warn("Error creating record: " + Utils.exceptionStr(ex));
|
||||
}
|
||||
|
||||
// Partial upload
|
||||
if ((++count % MAX_UPLOAD_RECORDS) == 0)
|
||||
doUpload((count - MAX_UPLOAD_RECORDS) + " - " + count + " out");
|
||||
|
||||
Sync.sleep(0);
|
||||
}
|
||||
|
||||
// Final upload
|
||||
if (count % MAX_UPLOAD_RECORDS > 0)
|
||||
doUpload(count >= MAX_UPLOAD_RECORDS ? "last batch" : "all");
|
||||
}
|
||||
this._tracker.clearChangedIDs();
|
||||
},
|
||||
|
||||
// Any cleanup necessary.
|
||||
// Save the current snapshot so as to calculate changes at next sync
|
||||
_syncFinish: function SyncEngine__syncFinish() {
|
||||
this._log.trace("Finishing up sync");
|
||||
this._tracker.resetScore();
|
||||
|
||||
let doDelete = Utils.bind2(this, function(key, val) {
|
||||
let coll = new Collection(this.engineURL, this._recordObj);
|
||||
coll[key] = val;
|
||||
coll.delete();
|
||||
});
|
||||
|
||||
for (let [key, val] in Iterator(this._delete)) {
|
||||
// Remove the key for future uses
|
||||
delete this._delete[key];
|
||||
|
||||
// Send a simple delete for the property
|
||||
if (key != "ids" || val.length <= 100)
|
||||
doDelete(key, val);
|
||||
else {
|
||||
// For many ids, split into chunks of at most 100
|
||||
while (val.length > 0) {
|
||||
doDelete(key, val.slice(0, 100));
|
||||
val = val.slice(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_sync: function SyncEngine__sync() {
|
||||
try {
|
||||
this._syncStartup();
|
||||
Observers.notify("weave:engine:sync:status", "process-incoming");
|
||||
this._processIncoming();
|
||||
Observers.notify("weave:engine:sync:status", "upload-outgoing");
|
||||
this._uploadOutgoing();
|
||||
this._syncFinish();
|
||||
}
|
||||
catch (e) {
|
||||
this._log.warn("Sync failed");
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
_testDecrypt: function _testDecrypt() {
|
||||
// Report failure even if there's nothing to decrypt
|
||||
let canDecrypt = false;
|
||||
|
||||
// Fetch the most recently uploaded record and try to decrypt it
|
||||
let test = new Collection(this.engineURL, this._recordObj);
|
||||
test.limit = 1;
|
||||
test.sort = "newest";
|
||||
test.full = true;
|
||||
test.recordHandler = function(record) {
|
||||
record.decrypt(ID.get("WeaveCryptoID"));
|
||||
canDecrypt = true;
|
||||
};
|
||||
|
||||
// Any failure fetching/decrypting will just result in false
|
||||
try {
|
||||
this._log.trace("Trying to decrypt a record from the server..");
|
||||
test.get();
|
||||
}
|
||||
catch(ex) {
|
||||
this._log.debug("Failed test decrypt: " + Utils.exceptionStr(ex));
|
||||
}
|
||||
|
||||
return canDecrypt;
|
||||
},
|
||||
|
||||
_resetClient: function SyncEngine__resetClient() {
|
||||
this.resetLastSync();
|
||||
this.toFetch = [];
|
||||
}
|
||||
};
|
1176
services/sync/modules/engines/bookmarks.js
Normal file
1176
services/sync/modules/engines/bookmarks.js
Normal file
File diff suppressed because it is too large
Load Diff
209
services/sync/modules/engines/clients.js
Normal file
209
services/sync/modules/engines/clients.js
Normal file
@ -0,0 +1,209 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["Clients"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/ext/StringBundle.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/type_records/clients.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
Utils.lazy(this, "Clients", ClientEngine);
|
||||
|
||||
function ClientEngine() {
|
||||
SyncEngine.call(this, "Clients");
|
||||
|
||||
// Reset the client on every startup so that we fetch recent clients
|
||||
this._resetClient();
|
||||
}
|
||||
ClientEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: ClientStore,
|
||||
_recordObj: ClientsRec,
|
||||
|
||||
// Always sync client data as it controls other sync behavior
|
||||
get enabled() true,
|
||||
|
||||
// Aggregate some stats on the composition of clients on this account
|
||||
get stats() {
|
||||
let stats = {
|
||||
hasMobile: this.localType == "mobile",
|
||||
names: [this.localName],
|
||||
numClients: 1,
|
||||
};
|
||||
|
||||
for each (let {name, type} in this._store._remoteClients) {
|
||||
stats.hasMobile = stats.hasMobile || type == "mobile";
|
||||
stats.names.push(name);
|
||||
stats.numClients++;
|
||||
}
|
||||
|
||||
return stats;
|
||||
},
|
||||
|
||||
// Remove any commands for the local client and mark it for upload
|
||||
clearCommands: function clearCommands() {
|
||||
delete this.localCommands;
|
||||
this._tracker.addChangedID(this.localID);
|
||||
},
|
||||
|
||||
// Send a command+args pair to each remote client
|
||||
sendCommand: function sendCommand(command, args) {
|
||||
// Helper to determine if the client already has this command
|
||||
let notDupe = function(other) other.command != command ||
|
||||
JSON.stringify(other.args) != JSON.stringify(args);
|
||||
|
||||
// Package the command/args pair into an object
|
||||
let action = {
|
||||
command: command,
|
||||
args: args,
|
||||
};
|
||||
|
||||
// Send the command to each remote client
|
||||
for (let [id, client] in Iterator(this._store._remoteClients)) {
|
||||
// Set the action to be a new commands array if none exists
|
||||
if (client.commands == null)
|
||||
client.commands = [action];
|
||||
// Add the new action if there are no duplicates
|
||||
else if (client.commands.every(notDupe))
|
||||
client.commands.push(action);
|
||||
// Must have been a dupe.. skip!
|
||||
else
|
||||
continue;
|
||||
|
||||
this._log.trace("Client " + id + " got a new action: " + [command, args]);
|
||||
this._tracker.addChangedID(id);
|
||||
}
|
||||
},
|
||||
|
||||
get localID() {
|
||||
// Generate a random GUID id we don't have one
|
||||
let localID = Svc.Prefs.get("client.GUID", "");
|
||||
return localID == "" ? this.localID = Utils.makeGUID() : localID;
|
||||
},
|
||||
set localID(value) Svc.Prefs.set("client.GUID", value),
|
||||
|
||||
get localName() {
|
||||
let localName = Svc.Prefs.get("client.name", "");
|
||||
if (localName != "")
|
||||
return localName;
|
||||
|
||||
// Generate a client name if we don't have a useful one yet
|
||||
let user = Svc.Env.get("USER") || Svc.Env.get("USERNAME") ||
|
||||
Svc.Prefs.get("username");
|
||||
let brand = new StringBundle("chrome://branding/locale/brand.properties");
|
||||
let app = brand.get("brandShortName");
|
||||
let os = Cc["@mozilla.org/network/protocol;1?name=http"].
|
||||
getService(Ci.nsIHttpProtocolHandler).oscpu;
|
||||
|
||||
return this.localName = Str.sync.get("client.name2", [user, app, os]);
|
||||
},
|
||||
set localName(value) Svc.Prefs.set("client.name", value),
|
||||
|
||||
get localType() Svc.Prefs.get("client.type", "desktop"),
|
||||
set localType(value) Svc.Prefs.set("client.type", value),
|
||||
|
||||
isMobile: function isMobile(id) {
|
||||
if (this._store._remoteClients[id])
|
||||
return this._store._remoteClients[id].type == "mobile";
|
||||
return false;
|
||||
},
|
||||
|
||||
// Always process incoming items because they might have commands
|
||||
_reconcile: function _reconcile() {
|
||||
return true;
|
||||
},
|
||||
|
||||
// Treat reset the same as wiping for locally cached clients
|
||||
_resetClient: function _resetClient() this._wipeClient(),
|
||||
|
||||
_wipeClient: function _wipeClient() {
|
||||
SyncEngine.prototype._resetClient.call(this);
|
||||
this._store.wipe();
|
||||
}
|
||||
};
|
||||
|
||||
function ClientStore(name) {
|
||||
Store.call(this, name);
|
||||
}
|
||||
ClientStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
create: function create(record) this.update(record),
|
||||
|
||||
update: function update(record) {
|
||||
// Only grab commands from the server; local name/type always wins
|
||||
if (record.id == Clients.localID)
|
||||
Clients.localCommands = record.commands;
|
||||
else
|
||||
this._remoteClients[record.id] = record.cleartext;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid) {
|
||||
let record = new ClientsRec();
|
||||
|
||||
// Package the individual components into a record for the local client
|
||||
if (guid == Clients.localID) {
|
||||
record.name = Clients.localName;
|
||||
record.type = Clients.localType;
|
||||
record.commands = Clients.localCommands;
|
||||
}
|
||||
else
|
||||
record.cleartext = this._remoteClients[guid];
|
||||
|
||||
return record;
|
||||
},
|
||||
|
||||
itemExists: function itemExists(id) id in this.getAllIDs(),
|
||||
|
||||
getAllIDs: function getAllIDs() {
|
||||
let ids = {};
|
||||
ids[Clients.localID] = true;
|
||||
for (let id in this._remoteClients)
|
||||
ids[id] = true;
|
||||
return ids;
|
||||
},
|
||||
|
||||
wipe: function wipe() {
|
||||
this._remoteClients = {};
|
||||
},
|
||||
};
|
311
services/sync/modules/engines/forms.js
Normal file
311
services/sync/modules/engines/forms.js
Normal file
@ -0,0 +1,311 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['FormEngine'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/type_records/forms.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
let FormWrapper = {
|
||||
getAllEntries: function getAllEntries() {
|
||||
// Sort by (lastUsed - minLast) / (maxLast - minLast) * timesUsed / maxTimes
|
||||
let query = this.createStatement(
|
||||
"SELECT fieldname name, value FROM moz_formhistory " +
|
||||
"ORDER BY 1.0 * (lastUsed - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) / " +
|
||||
"((SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed DESC LIMIT 1) - (SELECT lastUsed FROM moz_formhistory ORDER BY lastUsed ASC LIMIT 1)) * " +
|
||||
"timesUsed / (SELECT timesUsed FROM moz_formhistory ORDER BY timesUsed DESC LIMIT 1) DESC " +
|
||||
"LIMIT 500");
|
||||
return Utils.queryAsync(query, ["name", "value"]);
|
||||
},
|
||||
|
||||
getEntry: function getEntry(guid) {
|
||||
let query = this.createStatement(
|
||||
"SELECT fieldname name, value FROM moz_formhistory WHERE guid = :guid");
|
||||
query.params.guid = guid;
|
||||
return Utils.queryAsync(query, ["name", "value"])[0];
|
||||
},
|
||||
|
||||
getGUID: function getGUID(name, value) {
|
||||
// Query for the provided entry
|
||||
let getQuery = this.createStatement(
|
||||
"SELECT guid FROM moz_formhistory " +
|
||||
"WHERE fieldname = :name AND value = :value");
|
||||
getQuery.params.name = name;
|
||||
getQuery.params.value = value;
|
||||
|
||||
// Give the guid if we found one
|
||||
let item = Utils.queryAsync(getQuery, "guid")[0];
|
||||
if (item.guid != null)
|
||||
return item.guid;
|
||||
|
||||
// We need to create a guid for this entry
|
||||
let setQuery = this.createStatement(
|
||||
"UPDATE moz_formhistory SET guid = :guid " +
|
||||
"WHERE fieldname = :name AND value = :value");
|
||||
let guid = Utils.makeGUID();
|
||||
setQuery.params.guid = guid;
|
||||
setQuery.params.name = name;
|
||||
setQuery.params.value = value;
|
||||
Utils.queryAsync(setQuery);
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
hasGUID: function hasGUID(guid) {
|
||||
let query = this.createStatement(
|
||||
"SELECT 1 FROM moz_formhistory WHERE guid = :guid");
|
||||
query.params.guid = guid;
|
||||
return Utils.queryAsync(query).length == 1;
|
||||
},
|
||||
|
||||
replaceGUID: function replaceGUID(oldGUID, newGUID) {
|
||||
let query = this.createStatement(
|
||||
"UPDATE moz_formhistory SET guid = :newGUID WHERE guid = :oldGUID");
|
||||
query.params.oldGUID = oldGUID;
|
||||
query.params.newGUID = newGUID;
|
||||
Utils.queryAsync(query);
|
||||
},
|
||||
|
||||
createStatement: function createStatement(query) {
|
||||
try {
|
||||
// Just return the statement right away if it's okay
|
||||
return Svc.Form.DBConnection.createStatement(query);
|
||||
}
|
||||
catch(ex) {
|
||||
// Assume guid column must not exist yet, so add it with an index
|
||||
Svc.Form.DBConnection.executeSimpleSQL(
|
||||
"ALTER TABLE moz_formhistory ADD COLUMN guid TEXT");
|
||||
Svc.Form.DBConnection.executeSimpleSQL(
|
||||
"CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index " +
|
||||
"ON moz_formhistory (guid)");
|
||||
|
||||
// Try creating the query now that the column exists
|
||||
return Svc.Form.DBConnection.createStatement(query);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function FormEngine() {
|
||||
SyncEngine.call(this, "Forms");
|
||||
}
|
||||
FormEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: FormStore,
|
||||
_trackerObj: FormTracker,
|
||||
_recordObj: FormRec,
|
||||
get prefName() "history",
|
||||
|
||||
_findDupe: function _findDupe(item) {
|
||||
if (Svc.Form.entryExists(item.name, item.value))
|
||||
return FormWrapper.getGUID(item.name, item.value);
|
||||
}
|
||||
};
|
||||
|
||||
function FormStore(name) {
|
||||
Store.call(this, name);
|
||||
}
|
||||
FormStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
getAllIDs: function FormStore_getAllIDs() {
|
||||
let guids = {};
|
||||
for each (let {name, value} in FormWrapper.getAllEntries())
|
||||
guids[FormWrapper.getGUID(name, value)] = true;
|
||||
return guids;
|
||||
},
|
||||
|
||||
changeItemID: function FormStore_changeItemID(oldID, newID) {
|
||||
FormWrapper.replaceGUID(oldID, newID);
|
||||
},
|
||||
|
||||
itemExists: function FormStore_itemExists(id) {
|
||||
return FormWrapper.hasGUID(id);
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid) {
|
||||
let record = new FormRec();
|
||||
let entry = FormWrapper.getEntry(guid);
|
||||
if (entry != null) {
|
||||
record.name = entry.name;
|
||||
record.value = entry.value
|
||||
}
|
||||
else
|
||||
record.deleted = true;
|
||||
return record;
|
||||
},
|
||||
|
||||
create: function FormStore_create(record) {
|
||||
this._log.trace("Adding form record for " + record.name);
|
||||
Svc.Form.addEntry(record.name, record.value);
|
||||
},
|
||||
|
||||
remove: function FormStore_remove(record) {
|
||||
this._log.trace("Removing form record: " + record.id);
|
||||
|
||||
// Just skip remove requests for things already gone
|
||||
let entry = FormWrapper.getEntry(record.id);
|
||||
if (entry == null)
|
||||
return;
|
||||
|
||||
Svc.Form.removeEntry(entry.name, entry.value);
|
||||
},
|
||||
|
||||
update: function FormStore_update(record) {
|
||||
this._log.warn("Ignoring form record update request!");
|
||||
},
|
||||
|
||||
wipe: function FormStore_wipe() {
|
||||
Svc.Form.removeAllEntries();
|
||||
}
|
||||
};
|
||||
|
||||
function FormTracker(name) {
|
||||
Tracker.call(this, name);
|
||||
Svc.Obs.add("form-notifier", this);
|
||||
|
||||
// nsHTMLFormElement doesn't use the normal observer/observe pattern and looks
|
||||
// up nsIFormSubmitObservers to .notify() them so add manually to observers
|
||||
Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
|
||||
addObserver(this, "earlyformsubmit", false);
|
||||
}
|
||||
FormTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIFormSubmitObserver,
|
||||
Ci.nsIObserver]),
|
||||
|
||||
trackEntry: function trackEntry(name, value) {
|
||||
this.addChangedID(FormWrapper.getGUID(name, value));
|
||||
this.score += 10;
|
||||
},
|
||||
|
||||
observe: function observe(subject, topic, data) {
|
||||
let name, value;
|
||||
|
||||
// Figure out if it's a function that we care about tracking
|
||||
let formCall = JSON.parse(data);
|
||||
let func = formCall.func;
|
||||
if ((func == "addEntry" && formCall.type == "after") ||
|
||||
(func == "removeEntry" && formCall.type == "before"))
|
||||
[name, value] = formCall.args;
|
||||
|
||||
// Skip if there's nothing of interest
|
||||
if (name == null || value == null)
|
||||
return;
|
||||
|
||||
this._log.trace("Logging form action: " + [func, name, value]);
|
||||
this.trackEntry(name, value);
|
||||
},
|
||||
|
||||
notify: function FormTracker_notify(formElement, aWindow, actionURI) {
|
||||
if (this.ignoreAll)
|
||||
return;
|
||||
|
||||
this._log.trace("Form submission notification for " + actionURI.spec);
|
||||
|
||||
// XXX Bug 487541 Copy the logic from nsFormHistory::Notify to avoid
|
||||
// divergent logic, which can lead to security issues, until there's a
|
||||
// better way to get satchel's results like with a notification.
|
||||
|
||||
// Determine if a dom node has the autocomplete attribute set to "off"
|
||||
let completeOff = function(domNode) {
|
||||
let autocomplete = domNode.getAttribute("autocomplete");
|
||||
return autocomplete && autocomplete.search(/^off$/i) == 0;
|
||||
}
|
||||
|
||||
if (completeOff(formElement)) {
|
||||
this._log.trace("Form autocomplete set to off");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get number of elements in form, add points and changedIDs */
|
||||
let len = formElement.length;
|
||||
let elements = formElement.elements;
|
||||
for (let i = 0; i < len; i++) {
|
||||
let el = elements.item(i);
|
||||
|
||||
// Grab the name for debugging, but check if empty when satchel would
|
||||
let name = el.name;
|
||||
if (name === "")
|
||||
name = el.id;
|
||||
|
||||
if (!(el instanceof Ci.nsIDOMHTMLInputElement)) {
|
||||
this._log.trace(name + " is not a DOMHTMLInputElement: " + el);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el.type.search(/^text$/i) != 0) {
|
||||
this._log.trace(name + "'s type is not 'text': " + el.type);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (completeOff(el)) {
|
||||
this._log.trace(name + "'s autocomplete set to off");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el.value === "") {
|
||||
this._log.trace(name + "'s value is empty");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el.value == el.defaultValue) {
|
||||
this._log.trace(name + "'s value is the default");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name === "") {
|
||||
this._log.trace("Text input element has no name or id");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the GUID on a delay so that it can be added to the DB first...
|
||||
Utils.delay(function() {
|
||||
this._log.trace("Logging form element: " + [name, el.value]);
|
||||
this.trackEntry(name, el.value);
|
||||
}, 0, this);
|
||||
}
|
||||
}
|
||||
};
|
297
services/sync/modules/engines/history.js
Normal file
297
services/sync/modules/engines/history.js
Normal file
@ -0,0 +1,297 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['HistoryEngine'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const GUID_ANNO = "weave/guid";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/type_records/history.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// Create some helper functions to handle GUIDs
|
||||
function setGUID(uri, guid) {
|
||||
if (arguments.length == 1)
|
||||
guid = Utils.makeGUID();
|
||||
Utils.anno(uri, GUID_ANNO, guid, "WITH_HISTORY");
|
||||
return guid;
|
||||
}
|
||||
function GUIDForUri(uri, create) {
|
||||
try {
|
||||
// Use the existing GUID if it exists
|
||||
return Utils.anno(uri, GUID_ANNO);
|
||||
}
|
||||
catch (ex) {
|
||||
// Give the uri a GUID if it doesn't have one
|
||||
if (create)
|
||||
return setGUID(uri);
|
||||
}
|
||||
}
|
||||
|
||||
function HistoryEngine() {
|
||||
SyncEngine.call(this, "History");
|
||||
}
|
||||
HistoryEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_recordObj: HistoryRec,
|
||||
_storeObj: HistoryStore,
|
||||
_trackerObj: HistoryTracker,
|
||||
|
||||
_sync: Utils.batchSync("History", SyncEngine),
|
||||
|
||||
_findDupe: function _findDupe(item) {
|
||||
return GUIDForUri(item.histUri);
|
||||
}
|
||||
};
|
||||
|
||||
function HistoryStore(name) {
|
||||
Store.call(this, name);
|
||||
}
|
||||
HistoryStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
get _hsvc() {
|
||||
let hsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
|
||||
getService(Ci.nsINavHistoryService);
|
||||
hsvc.QueryInterface(Ci.nsIGlobalHistory2);
|
||||
hsvc.QueryInterface(Ci.nsIBrowserHistory);
|
||||
hsvc.QueryInterface(Ci.nsPIPlacesDatabase);
|
||||
this.__defineGetter__("_hsvc", function() hsvc);
|
||||
return hsvc;
|
||||
},
|
||||
|
||||
get _db() {
|
||||
return this._hsvc.DBConnection;
|
||||
},
|
||||
|
||||
get _visitStm() {
|
||||
this._log.trace("Creating SQL statement: _visitStm");
|
||||
let stm = this._db.createStatement(
|
||||
"SELECT visit_type type, visit_date date " +
|
||||
"FROM moz_historyvisits " +
|
||||
"WHERE place_id = (" +
|
||||
"SELECT id " +
|
||||
"FROM moz_places " +
|
||||
"WHERE url = :url) " +
|
||||
"ORDER BY date DESC LIMIT 10");
|
||||
this.__defineGetter__("_visitStm", function() stm);
|
||||
return stm;
|
||||
},
|
||||
|
||||
get _urlStm() {
|
||||
this._log.trace("Creating SQL statement: _urlStm");
|
||||
let stm = this._db.createStatement(
|
||||
"SELECT url, title, frecency " +
|
||||
"FROM moz_places " +
|
||||
"WHERE id = (" +
|
||||
"SELECT place_id " +
|
||||
"FROM moz_annos " +
|
||||
"WHERE content = :guid AND anno_attribute_id = (" +
|
||||
"SELECT id " +
|
||||
"FROM moz_anno_attributes " +
|
||||
"WHERE name = '" + GUID_ANNO + "'))");
|
||||
this.__defineGetter__("_urlStm", function() stm);
|
||||
return stm;
|
||||
},
|
||||
|
||||
get _allUrlStm() {
|
||||
this._log.trace("Creating SQL statement: _allUrlStm");
|
||||
let stm = this._db.createStatement(
|
||||
"SELECT url " +
|
||||
"FROM moz_places " +
|
||||
"WHERE last_visit_date > :cutoff_date " +
|
||||
"ORDER BY frecency DESC " +
|
||||
"LIMIT :max_results");
|
||||
this.__defineGetter__("_allUrlStm", function() stm);
|
||||
return stm;
|
||||
},
|
||||
|
||||
// See bug 320831 for why we use SQL here
|
||||
_getVisits: function HistStore__getVisits(uri) {
|
||||
this._visitStm.params.url = uri;
|
||||
return Utils.queryAsync(this._visitStm, ["date", "type"]);
|
||||
},
|
||||
|
||||
// See bug 468732 for why we use SQL here
|
||||
_findURLByGUID: function HistStore__findURLByGUID(guid) {
|
||||
this._urlStm.params.guid = guid;
|
||||
return Utils.queryAsync(this._urlStm, ["url", "title", "frecency"])[0];
|
||||
},
|
||||
|
||||
changeItemID: function HStore_changeItemID(oldID, newID) {
|
||||
setGUID(this._findURLByGUID(oldID).url, newID);
|
||||
},
|
||||
|
||||
|
||||
getAllIDs: function HistStore_getAllIDs() {
|
||||
// Only get places visited within the last 30 days (30*24*60*60*1000ms)
|
||||
this._allUrlStm.params.cutoff_date = (Date.now() - 2592000000) * 1000;
|
||||
this._allUrlStm.params.max_results = 5000;
|
||||
|
||||
let urls = Utils.queryAsync(this._allUrlStm, "url");
|
||||
return urls.reduce(function(ids, item) {
|
||||
ids[GUIDForUri(item.url, true)] = item.url;
|
||||
return ids;
|
||||
}, {});
|
||||
},
|
||||
|
||||
create: function HistStore_create(record) {
|
||||
// Add the url and set the GUID
|
||||
this.update(record);
|
||||
setGUID(record.histUri, record.id);
|
||||
},
|
||||
|
||||
remove: function HistStore_remove(record) {
|
||||
let page = this._findURLByGUID(record.id)
|
||||
if (page == null) {
|
||||
this._log.debug("Page already removed: " + record.id);
|
||||
return;
|
||||
}
|
||||
|
||||
let uri = Utils.makeURI(page.url);
|
||||
Svc.History.removePage(uri);
|
||||
this._log.trace("Removed page: " + [record.id, page.url, page.title]);
|
||||
},
|
||||
|
||||
update: function HistStore_update(record) {
|
||||
this._log.trace(" -> processing history entry: " + record.histUri);
|
||||
|
||||
let uri = Utils.makeURI(record.histUri);
|
||||
if (!uri) {
|
||||
this._log.warn("Attempted to process invalid URI, skipping");
|
||||
throw "invalid URI in record";
|
||||
}
|
||||
let curvisits = [];
|
||||
if (this.urlExists(uri))
|
||||
curvisits = this._getVisits(record.histUri);
|
||||
|
||||
// Add visits if there's no local visit with the same date
|
||||
for each (let {date, type} in record.visits)
|
||||
if (curvisits.every(function(cur) cur.date != date))
|
||||
Svc.History.addVisit(uri, date, null, type, type == 5 || type == 6, 0);
|
||||
|
||||
this._hsvc.setPageTitle(uri, record.title);
|
||||
},
|
||||
|
||||
itemExists: function HistStore_itemExists(id) {
|
||||
if (this._findURLByGUID(id))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
urlExists: function HistStore_urlExists(url) {
|
||||
if (typeof(url) == "string")
|
||||
url = Utils.makeURI(url);
|
||||
// Don't call isVisited on a null URL to work around crasher bug 492442.
|
||||
return url ? this._hsvc.isVisited(url) : false;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid) {
|
||||
let foo = this._findURLByGUID(guid);
|
||||
let record = new HistoryRec();
|
||||
if (foo) {
|
||||
record.histUri = foo.url;
|
||||
record.title = foo.title;
|
||||
record.sortindex = foo.frecency;
|
||||
record.visits = this._getVisits(record.histUri);
|
||||
}
|
||||
else
|
||||
record.deleted = true;
|
||||
|
||||
return record;
|
||||
},
|
||||
|
||||
wipe: function HistStore_wipe() {
|
||||
this._hsvc.removeAllPages();
|
||||
}
|
||||
};
|
||||
|
||||
function HistoryTracker(name) {
|
||||
Tracker.call(this, name);
|
||||
Svc.History.addObserver(this, false);
|
||||
}
|
||||
HistoryTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsINavHistoryObserver,
|
||||
Ci.nsINavHistoryObserver_MOZILLA_1_9_1_ADDITIONS
|
||||
]),
|
||||
|
||||
onBeginUpdateBatch: function HT_onBeginUpdateBatch() {},
|
||||
onEndUpdateBatch: function HT_onEndUpdateBatch() {},
|
||||
onPageChanged: function HT_onPageChanged() {},
|
||||
onTitleChanged: function HT_onTitleChanged() {},
|
||||
|
||||
/* Every add or remove is worth 1 point.
|
||||
* Clearing the whole history is worth 50 points (see below)
|
||||
*/
|
||||
_upScore: function BMT__upScore() {
|
||||
this.score += 1;
|
||||
},
|
||||
|
||||
onVisit: function HT_onVisit(uri, vid, time, session, referrer, trans) {
|
||||
if (this.ignoreAll)
|
||||
return;
|
||||
this._log.trace("onVisit: " + uri.spec);
|
||||
if (this.addChangedID(GUIDForUri(uri, true)))
|
||||
this._upScore();
|
||||
},
|
||||
onDeleteVisits: function onDeleteVisits() {
|
||||
},
|
||||
onPageExpired: function HT_onPageExpired(uri, time, entry) {
|
||||
},
|
||||
onBeforeDeleteURI: function onBeforeDeleteURI(uri) {
|
||||
if (this.ignoreAll)
|
||||
return;
|
||||
this._log.trace("onBeforeDeleteURI: " + uri.spec);
|
||||
if (this.addChangedID(GUIDForUri(uri, true)))
|
||||
this._upScore();
|
||||
},
|
||||
onDeleteURI: function HT_onDeleteURI(uri) {
|
||||
},
|
||||
onClearHistory: function HT_onClearHistory() {
|
||||
this._log.trace("onClearHistory");
|
||||
this.score += 500;
|
||||
}
|
||||
};
|
259
services/sync/modules/engines/passwords.js
Normal file
259
services/sync/modules/engines/passwords.js
Normal file
@ -0,0 +1,259 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Justin Dolske <dolske@mozilla.com>
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['PasswordEngine'];
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/collection.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/type_records/passwords.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PasswordEngine() {
|
||||
SyncEngine.call(this, "Passwords");
|
||||
}
|
||||
PasswordEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: PasswordStore,
|
||||
_trackerObj: PasswordTracker,
|
||||
_recordObj: LoginRec,
|
||||
|
||||
_syncFinish: function _syncFinish() {
|
||||
SyncEngine.prototype._syncFinish.call(this);
|
||||
|
||||
// Delete the weave credentials from the server once
|
||||
if (!Svc.Prefs.get("deletePwd", false)) {
|
||||
try {
|
||||
let ids = Svc.Login.findLogins({}, PWDMGR_HOST, "", "").map(function(info)
|
||||
info.QueryInterface(Components.interfaces.nsILoginMetaInfo).guid);
|
||||
let coll = new Collection(this.engineURL);
|
||||
coll.ids = ids;
|
||||
let ret = coll.delete();
|
||||
this._log.debug("Delete result: " + ret);
|
||||
|
||||
Svc.Prefs.set("deletePwd", true);
|
||||
}
|
||||
catch(ex) {
|
||||
this._log.debug("Password deletes failed: " + Utils.exceptionStr(ex));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_findDupe: function _findDupe(item) {
|
||||
let login = this._store._nsLoginInfoFromRecord(item);
|
||||
let logins = Svc.Login.findLogins({}, login.hostname, login.formSubmitURL,
|
||||
login.httpRealm);
|
||||
|
||||
// Look for existing logins that match the hostname but ignore the password
|
||||
for each (let local in logins)
|
||||
if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo)
|
||||
return local.guid;
|
||||
}
|
||||
};
|
||||
|
||||
function PasswordStore(name) {
|
||||
Store.call(this, name);
|
||||
this._nsLoginInfo = new Components.Constructor(
|
||||
"@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
|
||||
}
|
||||
PasswordStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
_nsLoginInfoFromRecord: function PasswordStore__nsLoginInfoRec(record) {
|
||||
let info = new this._nsLoginInfo(record.hostname,
|
||||
record.formSubmitURL,
|
||||
record.httpRealm,
|
||||
record.username,
|
||||
record.password,
|
||||
record.usernameField,
|
||||
record.passwordField);
|
||||
info.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
info.guid = record.id;
|
||||
return info;
|
||||
},
|
||||
|
||||
_getLoginFromGUID: function PasswordStore__getLoginFromGUID(id) {
|
||||
let prop = Cc["@mozilla.org/hash-property-bag;1"].
|
||||
createInstance(Ci.nsIWritablePropertyBag2);
|
||||
prop.setPropertyAsAUTF8String("guid", id);
|
||||
|
||||
let logins = Svc.Login.searchLogins({}, prop);
|
||||
if (logins.length > 0) {
|
||||
this._log.trace(logins.length + " items matching " + id + " found.");
|
||||
return logins[0];
|
||||
} else {
|
||||
this._log.trace("No items matching " + id + " found. Ignoring");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getAllIDs: function PasswordStore__getAllIDs() {
|
||||
let items = {};
|
||||
let logins = Svc.Login.getAllLogins({});
|
||||
|
||||
for (let i = 0; i < logins.length; i++) {
|
||||
// Skip over Weave password/passphrase entries
|
||||
let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
|
||||
if (metaInfo.hostname == PWDMGR_HOST)
|
||||
continue;
|
||||
|
||||
items[metaInfo.guid] = metaInfo;
|
||||
}
|
||||
|
||||
return items;
|
||||
},
|
||||
|
||||
changeItemID: function PasswordStore__changeItemID(oldID, newID) {
|
||||
this._log.trace("Changing item ID: " + oldID + " to " + newID);
|
||||
|
||||
let oldLogin = this._getLoginFromGUID(oldID);
|
||||
if (!oldLogin) {
|
||||
this._log.trace("Can't change item ID: item doesn't exist");
|
||||
return;
|
||||
}
|
||||
if (this._getLoginFromGUID(newID)) {
|
||||
this._log.trace("Can't change item ID: new ID already in use");
|
||||
return;
|
||||
}
|
||||
|
||||
let prop = Cc["@mozilla.org/hash-property-bag;1"].
|
||||
createInstance(Ci.nsIWritablePropertyBag2);
|
||||
prop.setPropertyAsAUTF8String("guid", newID);
|
||||
|
||||
Svc.Login.modifyLogin(oldLogin, prop);
|
||||
},
|
||||
|
||||
itemExists: function PasswordStore__itemExists(id) {
|
||||
if (this._getLoginFromGUID(id))
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid) {
|
||||
let record = new LoginRec();
|
||||
let login = this._getLoginFromGUID(guid);
|
||||
|
||||
if (login) {
|
||||
record.hostname = login.hostname;
|
||||
record.formSubmitURL = login.formSubmitURL;
|
||||
record.httpRealm = login.httpRealm;
|
||||
record.username = login.username;
|
||||
record.password = login.password;
|
||||
record.usernameField = login.usernameField;
|
||||
record.passwordField = login.passwordField;
|
||||
}
|
||||
else
|
||||
record.deleted = true;
|
||||
return record;
|
||||
},
|
||||
|
||||
create: function PasswordStore__create(record) {
|
||||
this._log.debug("Adding login for " + record.hostname);
|
||||
Svc.Login.addLogin(this._nsLoginInfoFromRecord(record));
|
||||
},
|
||||
|
||||
remove: function PasswordStore__remove(record) {
|
||||
this._log.trace("Removing login " + record.id);
|
||||
|
||||
let loginItem = this._getLoginFromGUID(record.id);
|
||||
if (!loginItem) {
|
||||
this._log.trace("Asked to remove record that doesn't exist, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
Svc.Login.removeLogin(loginItem);
|
||||
},
|
||||
|
||||
update: function PasswordStore__update(record) {
|
||||
let loginItem = this._getLoginFromGUID(record.id);
|
||||
if (!loginItem) {
|
||||
this._log.debug("Skipping update for unknown item: " + record.hostname);
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.debug("Updating " + record.hostname);
|
||||
let newinfo = this._nsLoginInfoFromRecord(record);
|
||||
Svc.Login.modifyLogin(loginItem, newinfo);
|
||||
},
|
||||
|
||||
wipe: function PasswordStore_wipe() {
|
||||
Svc.Login.removeAllLogins();
|
||||
}
|
||||
};
|
||||
|
||||
function PasswordTracker(name) {
|
||||
Tracker.call(this, name);
|
||||
Observers.add("passwordmgr-storage-changed", this);
|
||||
}
|
||||
PasswordTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
/* A single add, remove or change is 15 points, all items removed is 50 */
|
||||
observe: function PasswordTracker_observe(aSubject, aTopic, aData) {
|
||||
if (this.ignoreAll)
|
||||
return;
|
||||
|
||||
switch (aData) {
|
||||
case 'modifyLogin':
|
||||
aSubject = aSubject.QueryInterface(Ci.nsIArray).
|
||||
queryElementAt(1, Ci.nsILoginMetaInfo);
|
||||
case 'addLogin':
|
||||
case 'removeLogin':
|
||||
// Skip over Weave password/passphrase changes
|
||||
aSubject.QueryInterface(Ci.nsILoginMetaInfo).
|
||||
QueryInterface(Ci.nsILoginInfo);
|
||||
if (aSubject.hostname == PWDMGR_HOST)
|
||||
break;
|
||||
|
||||
this.score += 15;
|
||||
this._log.trace(aData + ": " + aSubject.guid);
|
||||
this.addChangedID(aSubject.guid);
|
||||
break;
|
||||
case 'removeAllLogins':
|
||||
this._log.trace(aData);
|
||||
this.score += 500;
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
262
services/sync/modules/engines/prefs.js
Normal file
262
services/sync/modules/engines/prefs.js
Normal file
@ -0,0 +1,262 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['PrefsEngine'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
const WEAVE_SYNC_PREFS = "services.sync.prefs.sync.";
|
||||
const WEAVE_PREFS_GUID = "preferences";
|
||||
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/type_records/prefs.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PrefsEngine() {
|
||||
SyncEngine.call(this, "Prefs");
|
||||
}
|
||||
PrefsEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: PrefStore,
|
||||
_trackerObj: PrefTracker,
|
||||
_recordObj: PrefRec,
|
||||
|
||||
_wipeClient: function _wipeClient() {
|
||||
SyncEngine.prototype._wipeClient.call(this);
|
||||
this.justWiped = true;
|
||||
},
|
||||
|
||||
_reconcile: function _reconcile(item) {
|
||||
// Apply the incoming item if we don't care about the local data
|
||||
if (this.justWiped) {
|
||||
this.justWiped = false;
|
||||
return true;
|
||||
}
|
||||
return SyncEngine.prototype._reconcile.call(this, item);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function PrefStore(name) {
|
||||
Store.call(this, name);
|
||||
}
|
||||
PrefStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
get _prefs() {
|
||||
let prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch);
|
||||
|
||||
this.__defineGetter__("_prefs", function() prefs);
|
||||
return prefs;
|
||||
},
|
||||
|
||||
get _syncPrefs() {
|
||||
let service = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefService);
|
||||
let syncPrefs = service.getBranch(WEAVE_SYNC_PREFS).getChildList("", {});
|
||||
|
||||
this.__defineGetter__("_syncPrefs", function() syncPrefs);
|
||||
return syncPrefs;
|
||||
},
|
||||
|
||||
_getAllPrefs: function PrefStore__getAllPrefs() {
|
||||
let values = [];
|
||||
let toSync = this._syncPrefs;
|
||||
|
||||
let pref;
|
||||
for (let i = 0; i < toSync.length; i++) {
|
||||
if (!this._prefs.getBoolPref(WEAVE_SYNC_PREFS + toSync[i]))
|
||||
continue;
|
||||
|
||||
pref = {};
|
||||
pref["name"] = toSync[i];
|
||||
|
||||
switch (this._prefs.getPrefType(toSync[i])) {
|
||||
case Ci.nsIPrefBranch.PREF_INT:
|
||||
pref["type"] = "int";
|
||||
pref["value"] = this._prefs.getIntPref(toSync[i]);
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_STRING:
|
||||
pref["type"] = "string";
|
||||
pref["value"] = this._prefs.getCharPref(toSync[i]);
|
||||
break;
|
||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
||||
pref["type"] = "boolean";
|
||||
pref["value"] = this._prefs.getBoolPref(toSync[i]);
|
||||
break;
|
||||
default:
|
||||
this._log.trace("Unsupported pref type for " + toSync[i]);
|
||||
}
|
||||
if ("value" in pref)
|
||||
values[values.length] = pref;
|
||||
}
|
||||
|
||||
return values;
|
||||
},
|
||||
|
||||
_setAllPrefs: function PrefStore__setAllPrefs(values) {
|
||||
// cache
|
||||
let ltmExists = true;
|
||||
let ltm = {};
|
||||
let enabledBefore = false;
|
||||
let prevTheme = "";
|
||||
try {
|
||||
Cu.import("resource://gre/modules/LightweightThemeManager.jsm", ltm);
|
||||
ltm = ltm.LightweightThemeManager;
|
||||
|
||||
let enabledPref = "lightweightThemes.isThemeSelected";
|
||||
if (this._prefs.getPrefType(enabledPref) == this._prefs.PREF_BOOL) {
|
||||
enabledBefore = this._prefs.getBoolPref(enabledPref);
|
||||
prevTheme = ltm.currentTheme;
|
||||
}
|
||||
} catch(ex) {
|
||||
ltmExists = false;
|
||||
} // LightweightThemeManager only exists in Firefox 3.6+
|
||||
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
switch (values[i]["type"]) {
|
||||
case "int":
|
||||
this._prefs.setIntPref(values[i]["name"], values[i]["value"]);
|
||||
break;
|
||||
case "string":
|
||||
this._prefs.setCharPref(values[i]["name"], values[i]["value"]);
|
||||
break;
|
||||
case "boolean":
|
||||
this._prefs.setBoolPref(values[i]["name"], values[i]["value"]);
|
||||
break;
|
||||
default:
|
||||
this._log.trace("Unexpected preference type: " + values[i]["type"]);
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the lightweight theme manager of all the new values
|
||||
if (ltmExists) {
|
||||
let enabledNow = this._prefs.getBoolPref("lightweightThemes.isThemeSelected");
|
||||
if (enabledBefore && !enabledNow)
|
||||
ltm.currentTheme = null;
|
||||
else if (enabledNow && ltm.usedThemes[0] != prevTheme) {
|
||||
ltm.currentTheme = null;
|
||||
ltm.currentTheme = ltm.usedThemes[0];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getAllIDs: function PrefStore_getAllIDs() {
|
||||
/* We store all prefs in just one WBO, with just one GUID */
|
||||
let allprefs = {};
|
||||
allprefs[WEAVE_PREFS_GUID] = this._getAllPrefs();
|
||||
return allprefs;
|
||||
},
|
||||
|
||||
changeItemID: function PrefStore_changeItemID(oldID, newID) {
|
||||
this._log.trace("PrefStore GUID is constant!");
|
||||
},
|
||||
|
||||
itemExists: function FormStore_itemExists(id) {
|
||||
return (id === WEAVE_PREFS_GUID);
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid) {
|
||||
let record = new PrefRec();
|
||||
|
||||
if (guid == WEAVE_PREFS_GUID) {
|
||||
record.value = this._getAllPrefs();
|
||||
} else {
|
||||
record.deleted = true;
|
||||
}
|
||||
|
||||
return record;
|
||||
},
|
||||
|
||||
create: function PrefStore_create(record) {
|
||||
this._log.trace("Ignoring create request");
|
||||
},
|
||||
|
||||
remove: function PrefStore_remove(record) {
|
||||
this._log.trace("Ignoring remove request")
|
||||
},
|
||||
|
||||
update: function PrefStore_update(record) {
|
||||
this._log.trace("Received pref updates, applying...");
|
||||
this._setAllPrefs(record.value);
|
||||
},
|
||||
|
||||
wipe: function PrefStore_wipe() {
|
||||
this._log.trace("Ignoring wipe request");
|
||||
}
|
||||
};
|
||||
|
||||
function PrefTracker(name) {
|
||||
Tracker.call(this, name);
|
||||
this._prefs.addObserver("", this, false);
|
||||
}
|
||||
PrefTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
get _prefs() {
|
||||
let prefs = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefBranch2);
|
||||
|
||||
this.__defineGetter__("_prefs", function() prefs);
|
||||
return prefs;
|
||||
},
|
||||
|
||||
get _syncPrefs() {
|
||||
let service = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefService);
|
||||
let syncPrefs = service.getBranch(WEAVE_SYNC_PREFS).getChildList("", {});
|
||||
|
||||
this.__defineGetter__("_syncPrefs", function() syncPrefs);
|
||||
return syncPrefs;
|
||||
},
|
||||
|
||||
/* 25 points per pref change */
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
if (aTopic != "nsPref:changed")
|
||||
return;
|
||||
|
||||
if (this._syncPrefs.indexOf(aData) != -1) {
|
||||
this.score += 1;
|
||||
this.addChangedID(WEAVE_PREFS_GUID);
|
||||
this._log.trace("Preference " + aData + " changed");
|
||||
}
|
||||
}
|
||||
};
|
270
services/sync/modules/engines/tabs.js
Normal file
270
services/sync/modules/engines/tabs.js
Normal file
@ -0,0 +1,270 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
* Jono DiCarlo <jdicarlo@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['TabEngine'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/engines/clients.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/type_records/tabs.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function TabEngine() {
|
||||
SyncEngine.call(this, "Tabs");
|
||||
|
||||
// Reset the client on every startup so that we fetch recent tabs
|
||||
this._resetClient();
|
||||
}
|
||||
TabEngine.prototype = {
|
||||
__proto__: SyncEngine.prototype,
|
||||
_storeObj: TabStore,
|
||||
_trackerObj: TabTracker,
|
||||
_recordObj: TabSetRecord,
|
||||
|
||||
// API for use by Weave UI code to give user choices of tabs to open:
|
||||
getAllClients: function TabEngine_getAllClients() {
|
||||
return this._store._remoteClients;
|
||||
},
|
||||
|
||||
getClientById: function TabEngine_getClientById(id) {
|
||||
return this._store._remoteClients[id];
|
||||
},
|
||||
|
||||
_resetClient: function TabEngine__resetClient() {
|
||||
SyncEngine.prototype._resetClient.call(this);
|
||||
this._store.wipe();
|
||||
},
|
||||
|
||||
/* The intent is not to show tabs in the menu if they're already
|
||||
* open locally. There are a couple ways to interpret this: for
|
||||
* instance, we could do it by removing a tab from the list when
|
||||
* you open it -- but then if you close it, you can't get back to
|
||||
* it. So the way I'm doing it here is to not show a tab in the menu
|
||||
* if you have a tab open to the same URL, even though this means
|
||||
* that as soon as you navigate anywhere, the original tab will
|
||||
* reappear in the menu.
|
||||
*/
|
||||
locallyOpenTabMatchesURL: function TabEngine_localTabMatches(url) {
|
||||
return this._store.getAllTabs().some(function(tab) {
|
||||
return tab.urlHistory[0] == url;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function TabStore(name) {
|
||||
Store.call(this, name);
|
||||
}
|
||||
TabStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
itemExists: function TabStore_itemExists(id) {
|
||||
return id == Clients.localID;
|
||||
},
|
||||
|
||||
getAllTabs: function getAllTabs(filter) {
|
||||
let filteredUrls = new RegExp(Svc.Prefs.get("engine.tabs.filteredUrls"), "i");
|
||||
|
||||
let allTabs = [];
|
||||
|
||||
let currentState = JSON.parse(Svc.Session.getBrowserState());
|
||||
currentState.windows.forEach(function(window) {
|
||||
window.tabs.forEach(function(tab) {
|
||||
// Make sure there are history entries to look at.
|
||||
if (!tab.entries.length)
|
||||
return;
|
||||
// Until we store full or partial history, just grab the current entry.
|
||||
// index is 1 based, so make sure we adjust.
|
||||
let entry = tab.entries[tab.index - 1];
|
||||
|
||||
// Filter out some urls if necessary. SessionStore can return empty
|
||||
// tabs in some cases - easiest thing is to just ignore them for now.
|
||||
if (!entry.url || filter && filteredUrls.test(entry.url))
|
||||
return;
|
||||
|
||||
// weaveLastUsed will only be set if the tab was ever selected (or
|
||||
// opened after Weave was running). So it might not ever be set.
|
||||
// I think it's also possible that attributes[.image] might not be set
|
||||
// so handle that as well.
|
||||
allTabs.push({
|
||||
title: entry.title || "",
|
||||
urlHistory: [entry.url],
|
||||
icon: tab.attributes && tab.attributes.image || "",
|
||||
lastUsed: tab.extData && tab.extData.weaveLastUsed || 0
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return allTabs;
|
||||
},
|
||||
|
||||
createRecord: function createRecord(guid) {
|
||||
let record = new TabSetRecord();
|
||||
record.clientName = Clients.localName;
|
||||
|
||||
// Sort tabs in descending-used order to grab the most recently used
|
||||
let tabs = this.getAllTabs(true).sort(function(a, b) {
|
||||
return b.lastUsed - a.lastUsed;
|
||||
});
|
||||
|
||||
// Figure out how many tabs we can pack into a payload. Starting with a 28KB
|
||||
// payload, we can estimate various overheads from encryption/JSON/WBO.
|
||||
let size = JSON.stringify(tabs).length;
|
||||
let origLength = tabs.length;
|
||||
const MAX_TAB_SIZE = 20000;
|
||||
if (size > MAX_TAB_SIZE) {
|
||||
// Estimate a little more than the direct fraction to maximize packing
|
||||
let cutoff = Math.ceil(tabs.length * MAX_TAB_SIZE / size);
|
||||
tabs = tabs.slice(0, cutoff + 1);
|
||||
|
||||
// Keep dropping off the last entry until the data fits
|
||||
while (JSON.stringify(tabs).length > MAX_TAB_SIZE)
|
||||
tabs.pop();
|
||||
}
|
||||
|
||||
this._log.trace("Created tabs " + tabs.length + " of " + origLength);
|
||||
tabs.forEach(function(tab) {
|
||||
this._log.trace("Wrapping tab: " + JSON.stringify(tab));
|
||||
}, this);
|
||||
|
||||
record.tabs = tabs;
|
||||
return record;
|
||||
},
|
||||
|
||||
getAllIDs: function TabStore_getAllIds() {
|
||||
let ids = {};
|
||||
ids[Clients.localID] = true;
|
||||
return ids;
|
||||
},
|
||||
|
||||
wipe: function TabStore_wipe() {
|
||||
this._remoteClients = {};
|
||||
},
|
||||
|
||||
create: function TabStore_create(record) {
|
||||
this._log.debug("Adding remote tabs from " + record.clientName);
|
||||
this._remoteClients[record.id] = record.cleartext;
|
||||
|
||||
// Lose some precision, but that's good enough (seconds)
|
||||
let roundModify = Math.floor(record.modified / 1000);
|
||||
let notifyState = Svc.Prefs.get("notifyTabState");
|
||||
// If there's no existing pref, save this first modified time
|
||||
if (notifyState == null)
|
||||
Svc.Prefs.set("notifyTabState", roundModify);
|
||||
// Don't change notifyState if it's already 0 (don't notify)
|
||||
else if (notifyState == 0)
|
||||
return;
|
||||
// We must have gotten a new tab that isn't the same as last time
|
||||
else if (notifyState != roundModify)
|
||||
Svc.Prefs.set("notifyTabState", 0);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function TabTracker(name) {
|
||||
Tracker.call(this, name);
|
||||
|
||||
// Make sure "this" pointer is always set correctly for event listeners
|
||||
this.onTab = Utils.bind2(this, this.onTab);
|
||||
|
||||
// Register as an observer so we can catch windows opening and closing:
|
||||
Svc.WinWatcher.registerNotification(this);
|
||||
|
||||
// Also register listeners on already open windows
|
||||
let wins = Svc.WinMediator.getEnumerator("navigator:browser");
|
||||
while (wins.hasMoreElements())
|
||||
this._registerListenersForWindow(wins.getNext());
|
||||
}
|
||||
TabTracker.prototype = {
|
||||
__proto__: Tracker.prototype,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
_registerListenersForWindow: function TabTracker__registerListen(window) {
|
||||
this._log.trace("Registering tab listeners in new window");
|
||||
|
||||
// For each topic, add or remove onTab as the listener
|
||||
let topics = ["pageshow", "TabOpen", "TabClose", "TabSelect"];
|
||||
let onTab = this.onTab;
|
||||
let addRem = function(add) topics.forEach(function(topic) {
|
||||
window[(add ? "add" : "remove") + "EventListener"](topic, onTab, false);
|
||||
});
|
||||
|
||||
// Add the listeners now and remove them on unload
|
||||
addRem(true);
|
||||
window.addEventListener("unload", function() addRem(false), false);
|
||||
},
|
||||
|
||||
observe: function TabTracker_observe(aSubject, aTopic, aData) {
|
||||
// Add tab listeners now that a window has opened
|
||||
if (aTopic == "domwindowopened") {
|
||||
let self = this;
|
||||
aSubject.addEventListener("load", function onLoad(event) {
|
||||
aSubject.removeEventListener("load", onLoad, false);
|
||||
// Only register after the window is done loading to avoid unloads
|
||||
self._registerListenersForWindow(aSubject);
|
||||
}, false);
|
||||
}
|
||||
},
|
||||
|
||||
onTab: function onTab(event) {
|
||||
this._log.trace("onTab event: " + event.type);
|
||||
this.addChangedID(Clients.localID);
|
||||
|
||||
// For pageshow events, only give a partial score bump (~.1)
|
||||
let chance = .1;
|
||||
|
||||
// For regular Tab events, do a full score bump and remember when it changed
|
||||
if (event.type != "pageshow") {
|
||||
chance = 1;
|
||||
|
||||
// Store a timestamp in the tab to track when it was last used
|
||||
Svc.Session.setTabValue(event.originalTarget, "weaveLastUsed",
|
||||
Math.floor(Date.now() / 1000));
|
||||
}
|
||||
|
||||
// Only increase the score by whole numbers, so use random for partial score
|
||||
if (Math.random() < chance)
|
||||
this.score++;
|
||||
},
|
||||
}
|
183
services/sync/modules/ext/Observers.js
Normal file
183
services/sync/modules/ext/Observers.js
Normal file
@ -0,0 +1,183 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Observers.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Daniel Aquino.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Daniel Aquino <mr.danielaquino@gmail.com>
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
let EXPORTED_SYMBOLS = ["Observers"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
/**
|
||||
* A service for adding, removing and notifying observers of notifications.
|
||||
* Wraps the nsIObserverService interface.
|
||||
*
|
||||
* @version 0.2
|
||||
*/
|
||||
let Observers = {
|
||||
/**
|
||||
* Register the given callback as an observer of the given topic.
|
||||
*
|
||||
* @param topic {String}
|
||||
* the topic to observe
|
||||
*
|
||||
* @param callback {Object}
|
||||
* the callback; an Object that implements nsIObserver or a Function
|
||||
* that gets called when the notification occurs
|
||||
*
|
||||
* @param thisObject {Object} [optional]
|
||||
* the object to use as |this| when calling a Function callback
|
||||
*
|
||||
* @returns the observer
|
||||
*/
|
||||
add: function(topic, callback, thisObject) {
|
||||
let observer = new Observer(topic, callback, thisObject);
|
||||
this._cache.push(observer);
|
||||
this._service.addObserver(observer, topic, true);
|
||||
|
||||
return observer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Unregister the given callback as an observer of the given topic.
|
||||
*
|
||||
* @param topic {String}
|
||||
* the topic being observed
|
||||
*
|
||||
* @param callback {Object}
|
||||
* the callback doing the observing
|
||||
*
|
||||
* @param thisObject {Object} [optional]
|
||||
* the object being used as |this| when calling a Function callback
|
||||
*/
|
||||
remove: function(topic, callback, thisObject) {
|
||||
// This seems fairly inefficient, but I'm not sure how much better
|
||||
// we can make it. We could index by topic, but we can't index by callback
|
||||
// or thisObject, as far as I know, since the keys to JavaScript hashes
|
||||
// (a.k.a. objects) can apparently only be primitive values.
|
||||
let [observer] = this._cache.filter(function(v) v.topic == topic &&
|
||||
v.callback == callback &&
|
||||
v.thisObject == thisObject);
|
||||
if (observer) {
|
||||
this._service.removeObserver(observer, topic);
|
||||
this._cache.splice(this._cache.indexOf(observer), 1);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Notify observers about something.
|
||||
*
|
||||
* @param topic {String}
|
||||
* the topic to notify observers about
|
||||
*
|
||||
* @param subject {Object} [optional]
|
||||
* some information about the topic; can be any JS object or primitive
|
||||
*
|
||||
* @param data {String} [optional] [deprecated]
|
||||
* some more information about the topic; deprecated as the subject
|
||||
* is sufficient to pass all needed information to the JS observers
|
||||
* that this module targets; if you have multiple values to pass to
|
||||
* the observer, wrap them in an object and pass them via the subject
|
||||
* parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
|
||||
*/
|
||||
notify: function(topic, subject, data) {
|
||||
subject = (typeof subject == "undefined") ? null : new Subject(subject);
|
||||
data = (typeof data == "undefined") ? null : data;
|
||||
this._service.notifyObservers(subject, topic, data);
|
||||
},
|
||||
|
||||
_service: Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService),
|
||||
|
||||
/**
|
||||
* A cache of observers that have been added.
|
||||
*
|
||||
* We use this to remove observers when a caller calls |remove|.
|
||||
*
|
||||
* XXX This might result in reference cycles, causing memory leaks,
|
||||
* if we hold a reference to an observer that holds a reference to us.
|
||||
* Could we fix that by making this an independent top-level object
|
||||
* rather than a property of this object?
|
||||
*/
|
||||
_cache: []
|
||||
};
|
||||
|
||||
|
||||
function Observer(topic, callback, thisObject) {
|
||||
this.topic = topic;
|
||||
this.callback = callback;
|
||||
this.thisObject = thisObject;
|
||||
}
|
||||
|
||||
Observer.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
|
||||
observe: function(subject, topic, data) {
|
||||
// Extract the wrapped object for subjects that are one of our wrappers
|
||||
// around a JS object. This way we support both wrapped subjects created
|
||||
// using this module and those that are real XPCOM components.
|
||||
if (subject && typeof subject == "object" &&
|
||||
("wrappedJSObject" in subject) &&
|
||||
("observersModuleSubjectWrapper" in subject.wrappedJSObject))
|
||||
subject = subject.wrappedJSObject.object;
|
||||
|
||||
if (typeof this.callback == "function") {
|
||||
if (this.thisObject)
|
||||
this.callback.call(this.thisObject, subject, data);
|
||||
else
|
||||
this.callback(subject, data);
|
||||
}
|
||||
else // typeof this.callback == "object" (nsIObserver)
|
||||
this.callback.observe(subject, topic, data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Subject(object) {
|
||||
// Double-wrap the object and set a property identifying the wrappedJSObject
|
||||
// as one of our wrappers to distinguish between subjects that are one of our
|
||||
// wrappers (which we should unwrap when notifying our observers) and those
|
||||
// that are real JS XPCOM components (which we should pass through unaltered).
|
||||
this.wrappedJSObject = { observersModuleSubjectWrapper: true, object: object };
|
||||
}
|
||||
|
||||
Subject.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([]),
|
||||
getHelperForLanguage: function() {},
|
||||
getInterfaces: function() {}
|
||||
};
|
532
services/sync/modules/ext/Preferences.js
Normal file
532
services/sync/modules/ext/Preferences.js
Normal file
@ -0,0 +1,532 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Preferences.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
* Daniel Aquino <mr.danielaquino@gmail.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
let EXPORTED_SYMBOLS = ["Preferences"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
// The minimum and maximum integers that can be set as preferences.
|
||||
// The range of valid values is narrower than the range of valid JS values
|
||||
// because the native preferences code treats integers as NSPR PRInt32s,
|
||||
// which are 32-bit signed integers on all platforms.
|
||||
const MAX_INT = Math.pow(2, 31) - 1;
|
||||
const MIN_INT = -MAX_INT;
|
||||
|
||||
function Preferences(args) {
|
||||
if (isObject(args)) {
|
||||
if (args.branch)
|
||||
this._prefBranch = args.branch;
|
||||
if (args.defaultBranch)
|
||||
this._defaultBranch = args.defaultBranch;
|
||||
if (args.site)
|
||||
this._site = args.site;
|
||||
}
|
||||
else if (args)
|
||||
this._prefBranch = args;
|
||||
}
|
||||
|
||||
Preferences.prototype = {
|
||||
/**
|
||||
* Get the value of a pref, if any; otherwise return the default value.
|
||||
*
|
||||
* @param prefName {String|Array}
|
||||
* the pref to get, or an array of prefs to get
|
||||
*
|
||||
* @param defaultValue
|
||||
* the default value, if any, for prefs that don't have one
|
||||
*
|
||||
* @returns the value of the pref, if any; otherwise the default value
|
||||
*/
|
||||
get: function(prefName, defaultValue) {
|
||||
if (isArray(prefName))
|
||||
return prefName.map(function(v) this.get(v, defaultValue), this);
|
||||
|
||||
if (this._site)
|
||||
return this._siteGet(prefName, defaultValue);
|
||||
else
|
||||
return this._get(prefName, defaultValue);
|
||||
},
|
||||
|
||||
_get: function(prefName, defaultValue) {
|
||||
switch (this._prefSvc.getPrefType(prefName)) {
|
||||
case Ci.nsIPrefBranch.PREF_STRING:
|
||||
return this._prefSvc.getComplexValue(prefName, Ci.nsISupportsString).data;
|
||||
|
||||
case Ci.nsIPrefBranch.PREF_INT:
|
||||
return this._prefSvc.getIntPref(prefName);
|
||||
|
||||
case Ci.nsIPrefBranch.PREF_BOOL:
|
||||
return this._prefSvc.getBoolPref(prefName);
|
||||
|
||||
case Ci.nsIPrefBranch.PREF_INVALID:
|
||||
return defaultValue;
|
||||
|
||||
default:
|
||||
// This should never happen.
|
||||
throw "Error getting pref " + prefName + "; its value's type is " +
|
||||
this._prefSvc.getPrefType(prefName) + ", which I don't know " +
|
||||
"how to handle.";
|
||||
}
|
||||
},
|
||||
|
||||
_siteGet: function(prefName, defaultValue) {
|
||||
let value = this._contentPrefSvc.getPref(this._site, this._prefBranch + prefName);
|
||||
return typeof value != "undefined" ? value : defaultValue;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a preference to a value.
|
||||
*
|
||||
* You can set multiple prefs by passing an object as the only parameter.
|
||||
* In that case, this method will treat the properties of the object
|
||||
* as preferences to set, where each property name is the name of a pref
|
||||
* and its corresponding property value is the value of the pref.
|
||||
*
|
||||
* @param prefName {String|Object}
|
||||
* the name of the pref to set; or an object containing a set
|
||||
* of prefs to set
|
||||
*
|
||||
* @param prefValue {String|Number|Boolean}
|
||||
* the value to which to set the pref
|
||||
*
|
||||
* Note: Preferences cannot store non-integer numbers or numbers outside
|
||||
* the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
|
||||
* store it as a string by calling toString() on the number before passing
|
||||
* it to this method, i.e.:
|
||||
* Preferences.set("pi", 3.14159.toString())
|
||||
* Preferences.set("big", Math.pow(2, 31).toString()).
|
||||
*/
|
||||
set: function(prefName, prefValue) {
|
||||
if (isObject(prefName)) {
|
||||
for (let [name, value] in Iterator(prefName))
|
||||
this.set(name, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._site)
|
||||
this._siteSet(prefName, prefValue);
|
||||
else
|
||||
this._set(prefName, prefValue);
|
||||
},
|
||||
|
||||
_set: function(prefName, prefValue) {
|
||||
let prefType;
|
||||
if (typeof prefValue != "undefined" && prefValue != null)
|
||||
prefType = prefValue.constructor.name;
|
||||
|
||||
switch (prefType) {
|
||||
case "String":
|
||||
{
|
||||
let string = Cc["@mozilla.org/supports-string;1"].
|
||||
createInstance(Ci.nsISupportsString);
|
||||
string.data = prefValue;
|
||||
this._prefSvc.setComplexValue(prefName, Ci.nsISupportsString, string);
|
||||
}
|
||||
break;
|
||||
|
||||
case "Number":
|
||||
// We throw if the number is outside the range, since the result
|
||||
// will never be what the consumer wanted to store, but we only warn
|
||||
// if the number is non-integer, since the consumer might not mind
|
||||
// the loss of precision.
|
||||
if (prefValue > MAX_INT || prefValue < MIN_INT)
|
||||
throw("you cannot set the " + prefName + " pref to the number " +
|
||||
prefValue + ", as number pref values must be in the signed " +
|
||||
"32-bit integer range -(2^31-1) to 2^31-1. To store numbers " +
|
||||
"outside that range, store them as strings.");
|
||||
this._prefSvc.setIntPref(prefName, prefValue);
|
||||
if (prefValue % 1 != 0)
|
||||
Cu.reportError("Warning: setting the " + prefName + " pref to the " +
|
||||
"non-integer number " + prefValue + " converted it " +
|
||||
"to the integer number " + this.get(prefName) +
|
||||
"; to retain fractional precision, store non-integer " +
|
||||
"numbers as strings.");
|
||||
break;
|
||||
|
||||
case "Boolean":
|
||||
this._prefSvc.setBoolPref(prefName, prefValue);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "can't set pref " + prefName + " to value '" + prefValue +
|
||||
"'; it isn't a String, Number, or Boolean";
|
||||
}
|
||||
},
|
||||
|
||||
_siteSet: function(prefName, prefValue) {
|
||||
this._contentPrefSvc.setPref(this._site, this._prefBranch + prefName, prefValue);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not the given pref has a value. This is different from isSet
|
||||
* because it returns true whether the value of the pref is a default value
|
||||
* or a user-set value, while isSet only returns true if the value
|
||||
* is a user-set value.
|
||||
*
|
||||
* @param prefName {String|Array}
|
||||
* the pref to check, or an array of prefs to check
|
||||
*
|
||||
* @returns {Boolean|Array}
|
||||
* whether or not the pref has a value; or, if the caller provided
|
||||
* an array of pref names, an array of booleans indicating whether
|
||||
* or not the prefs have values
|
||||
*/
|
||||
has: function(prefName) {
|
||||
if (isArray(prefName))
|
||||
return prefName.map(this.has, this);
|
||||
|
||||
if (this._site)
|
||||
return this._siteHas(prefName);
|
||||
else
|
||||
return this._has(prefName);
|
||||
},
|
||||
|
||||
_has: function(prefName) {
|
||||
return (this._prefSvc.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID);
|
||||
},
|
||||
|
||||
_siteHas: function(prefName) {
|
||||
return this._contentPrefSvc.hasPref(this._site, this._prefBranch + prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not the given pref has a user-set value. This is different
|
||||
* from |has| because it returns true only if the value of the pref is a user-
|
||||
* set value, while |has| returns true if the value of the pref is a default
|
||||
* value or a user-set value.
|
||||
*
|
||||
* @param prefName {String|Array}
|
||||
* the pref to check, or an array of prefs to check
|
||||
*
|
||||
* @returns {Boolean|Array}
|
||||
* whether or not the pref has a user-set value; or, if the caller
|
||||
* provided an array of pref names, an array of booleans indicating
|
||||
* whether or not the prefs have user-set values
|
||||
*/
|
||||
isSet: function(prefName) {
|
||||
if (isArray(prefName))
|
||||
return prefName.map(this.isSet, this);
|
||||
|
||||
return (this.has(prefName) && this._prefSvc.prefHasUserValue(prefName));
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not the given pref has a user-set value. Use isSet instead,
|
||||
* which is equivalent.
|
||||
* @deprecated
|
||||
*/
|
||||
modified: function(prefName) { return this.isSet(prefName) },
|
||||
|
||||
reset: function(prefName) {
|
||||
if (isArray(prefName)) {
|
||||
prefName.map(function(v) this.reset(v), this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._site)
|
||||
this._siteReset(prefName);
|
||||
else
|
||||
this._reset(prefName);
|
||||
},
|
||||
|
||||
_reset: function(prefName) {
|
||||
try {
|
||||
this._prefSvc.clearUserPref(prefName);
|
||||
}
|
||||
catch(ex) {
|
||||
// The pref service throws NS_ERROR_UNEXPECTED when the caller tries
|
||||
// to reset a pref that doesn't exist or is already set to its default
|
||||
// value. This interface fails silently in those cases, so callers
|
||||
// can unconditionally reset a pref without having to check if it needs
|
||||
// resetting first or trap exceptions after the fact. It passes through
|
||||
// other exceptions, however, so callers know about them, since we don't
|
||||
// know what other exceptions might be thrown and what they might mean.
|
||||
if (ex.result != Cr.NS_ERROR_UNEXPECTED)
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
|
||||
_siteReset: function(prefName) {
|
||||
return this._contentPrefSvc.removePref(this._site, this._prefBranch + prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Lock a pref so it can't be changed.
|
||||
*
|
||||
* @param prefName {String|Array}
|
||||
* the pref to lock, or an array of prefs to lock
|
||||
*/
|
||||
lock: function(prefName) {
|
||||
if (isArray(prefName))
|
||||
prefName.map(this.lock, this);
|
||||
|
||||
this._prefSvc.lockPref(prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Unlock a pref so it can be changed.
|
||||
*
|
||||
* @param prefName {String|Array}
|
||||
* the pref to lock, or an array of prefs to lock
|
||||
*/
|
||||
unlock: function(prefName) {
|
||||
if (isArray(prefName))
|
||||
prefName.map(this.unlock, this);
|
||||
|
||||
this._prefSvc.unlockPref(prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether or not the given pref is locked against changes.
|
||||
*
|
||||
* @param prefName {String|Array}
|
||||
* the pref to check, or an array of prefs to check
|
||||
*
|
||||
* @returns {Boolean|Array}
|
||||
* whether or not the pref has a user-set value; or, if the caller
|
||||
* provided an array of pref names, an array of booleans indicating
|
||||
* whether or not the prefs have user-set values
|
||||
*/
|
||||
locked: function(prefName) {
|
||||
if (isArray(prefName))
|
||||
return prefName.map(this.locked, this);
|
||||
|
||||
return this._prefSvc.prefIsLocked(prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start observing a pref.
|
||||
*
|
||||
* The callback can be a function or any object that implements nsIObserver.
|
||||
* When the callback is a function and thisObject is provided, it gets called
|
||||
* as a method of thisObject.
|
||||
*
|
||||
* @param prefName {String}
|
||||
* the name of the pref to observe
|
||||
*
|
||||
* @param callback {Function|Object}
|
||||
* the code to notify when the pref changes;
|
||||
*
|
||||
* @param thisObject {Object} [optional]
|
||||
* the object to use as |this| when calling a Function callback;
|
||||
*
|
||||
* @returns the wrapped observer
|
||||
*/
|
||||
observe: function(prefName, callback, thisObject) {
|
||||
let fullPrefName = this._prefBranch + (prefName || "");
|
||||
|
||||
let observer = new PrefObserver(fullPrefName, callback, thisObject);
|
||||
Preferences._prefSvc.addObserver(fullPrefName, observer, true);
|
||||
observers.push(observer);
|
||||
|
||||
return observer;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop observing a pref.
|
||||
*
|
||||
* You must call this method with the same prefName, callback, and thisObject
|
||||
* with which you originally registered the observer. However, you don't have
|
||||
* to call this method on the same exact instance of Preferences; you can call
|
||||
* it on any instance. For example, the following code first starts and then
|
||||
* stops observing the "foo.bar.baz" preference:
|
||||
*
|
||||
* let observer = function() {...};
|
||||
* Preferences.observe("foo.bar.baz", observer);
|
||||
* new Preferences("foo.bar.").ignore("baz", observer);
|
||||
*
|
||||
* @param prefName {String}
|
||||
* the name of the pref being observed
|
||||
*
|
||||
* @param callback {Function|Object}
|
||||
* the code being notified when the pref changes
|
||||
*
|
||||
* @param thisObject {Object} [optional]
|
||||
* the object being used as |this| when calling a Function callback
|
||||
*/
|
||||
ignore: function(prefName, callback, thisObject) {
|
||||
let fullPrefName = this._prefBranch + (prefName || "");
|
||||
|
||||
// This seems fairly inefficient, but I'm not sure how much better we can
|
||||
// make it. We could index by fullBranch, but we can't index by callback
|
||||
// or thisObject, as far as I know, since the keys to JavaScript hashes
|
||||
// (a.k.a. objects) can apparently only be primitive values.
|
||||
let [observer] = observers.filter(function(v) v.prefName == fullPrefName &&
|
||||
v.callback == callback &&
|
||||
v.thisObject == thisObject);
|
||||
|
||||
if (observer) {
|
||||
Preferences._prefSvc.removeObserver(fullPrefName, observer);
|
||||
observers.splice(observers.indexOf(observer), 1);
|
||||
}
|
||||
},
|
||||
|
||||
resetBranch: function(prefBranch) {
|
||||
try {
|
||||
this._prefSvc.resetBranch(prefBranch);
|
||||
}
|
||||
catch(ex) {
|
||||
// The current implementation of nsIPrefBranch in Mozilla
|
||||
// doesn't implement resetBranch, so we do it ourselves.
|
||||
if (ex.result == Cr.NS_ERROR_NOT_IMPLEMENTED)
|
||||
this.reset(this._prefSvc.getChildList(prefBranch, []));
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The branch of the preferences tree to which this instance provides access.
|
||||
* @private
|
||||
*/
|
||||
_prefBranch: "",
|
||||
|
||||
site: function(site) {
|
||||
if (!(site instanceof Ci.nsIURI))
|
||||
site = this._ioSvc.newURI("http://" + site, null, null);
|
||||
return new Preferences({ branch: this._prefBranch, site: site });
|
||||
},
|
||||
|
||||
/**
|
||||
* Preferences Service
|
||||
* @private
|
||||
*/
|
||||
get _prefSvc() {
|
||||
let prefSvc = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService);
|
||||
if (this._defaultBranch) {
|
||||
prefSvc = prefSvc.getDefaultBranch(this._prefBranch);
|
||||
} else {
|
||||
prefSvc = prefSvc.getBranch(this._prefBranch)
|
||||
.QueryInterface(Ci.nsIPrefBranch2);
|
||||
}
|
||||
|
||||
this.__defineGetter__("_prefSvc", function() prefSvc);
|
||||
return this._prefSvc;
|
||||
},
|
||||
|
||||
/**
|
||||
* IO Service
|
||||
* @private
|
||||
*/
|
||||
get _ioSvc() {
|
||||
let ioSvc = Cc["@mozilla.org/network/io-service;1"].
|
||||
getService(Ci.nsIIOService);
|
||||
this.__defineGetter__("_ioSvc", function() ioSvc);
|
||||
return this._ioSvc;
|
||||
},
|
||||
|
||||
/**
|
||||
* Site Preferences Service
|
||||
* @private
|
||||
*/
|
||||
get _contentPrefSvc() {
|
||||
let contentPrefSvc = Cc["@mozilla.org/content-pref/service;1"].
|
||||
getService(Ci.nsIContentPrefService);
|
||||
this.__defineGetter__("_contentPrefSvc", function() contentPrefSvc);
|
||||
return this._contentPrefSvc;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Give the constructor the same prototype as its instances, so users can access
|
||||
// preferences directly via the constructor without having to create an instance
|
||||
// first.
|
||||
Preferences.__proto__ = Preferences.prototype;
|
||||
|
||||
/**
|
||||
* A cache of pref observers.
|
||||
*
|
||||
* We use this to remove observers when a caller calls Preferences::ignore.
|
||||
*
|
||||
* All Preferences instances share this object, because we want callers to be
|
||||
* able to remove an observer using a different Preferences object than the one
|
||||
* with which they added it. That means we have to identify the observers
|
||||
* in this object by their complete pref name, not just their name relative to
|
||||
* the root branch of the Preferences object with which they were created.
|
||||
*/
|
||||
let observers = [];
|
||||
|
||||
function PrefObserver(prefName, callback, thisObject) {
|
||||
this.prefName = prefName;
|
||||
this.callback = callback;
|
||||
this.thisObject = thisObject;
|
||||
}
|
||||
|
||||
PrefObserver.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
// The pref service only observes whole branches, but we only observe
|
||||
// individual preferences, so we check here that the pref that changed
|
||||
// is the exact one we're observing (and not some sub-pref on the branch).
|
||||
if (data != this.prefName)
|
||||
return;
|
||||
|
||||
if (typeof this.callback == "function") {
|
||||
let prefValue = Preferences.get(this.prefName);
|
||||
|
||||
if (this.thisObject)
|
||||
this.callback.call(this.thisObject, prefValue);
|
||||
else
|
||||
this.callback(prefValue);
|
||||
}
|
||||
else // typeof this.callback == "object" (nsIObserver)
|
||||
this.callback.observe(subject, topic, data);
|
||||
}
|
||||
};
|
||||
|
||||
function isArray(val) {
|
||||
// We can't check for |val.constructor == Array| here, since the value
|
||||
// might be from a different context whose Array constructor is not the same
|
||||
// as ours, so instead we match based on the name of the constructor.
|
||||
return (typeof val != "undefined" && val != null && typeof val == "object" &&
|
||||
val.constructor.name == "Array");
|
||||
}
|
||||
|
||||
function isObject(val) {
|
||||
// We can't check for |val.constructor == Object| here, since the value
|
||||
// might be from a different context whose Object constructor is not the same
|
||||
// as ours, so instead we match based on the name of the constructor.
|
||||
return (typeof val != "undefined" && val != null && typeof val == "object" &&
|
||||
val.constructor.name == "Object");
|
||||
}
|
238
services/sync/modules/ext/StringBundle.js
Normal file
238
services/sync/modules/ext/StringBundle.js
Normal file
@ -0,0 +1,238 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is String Bundle.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
let EXPORTED_SYMBOLS = ["StringBundle"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
/**
|
||||
* A string bundle.
|
||||
*
|
||||
* This object presents two APIs: a deprecated one that is equivalent to the API
|
||||
* for the stringbundle XBL binding, to make it easy to switch from that binding
|
||||
* to this module, and a new one that is simpler and easier to use.
|
||||
*
|
||||
* The benefit of this module over the XBL binding is that it can also be used
|
||||
* in JavaScript modules and components, not only in chrome JS.
|
||||
*
|
||||
* To use this module, import it, create a new instance of StringBundle,
|
||||
* and then use the instance's |get| and |getAll| methods to retrieve strings
|
||||
* (you can get both plain and formatted strings with |get|):
|
||||
*
|
||||
* let strings =
|
||||
* new StringBundle("chrome://example/locale/strings.properties");
|
||||
* let foo = strings.get("foo");
|
||||
* let barFormatted = strings.get("bar", [arg1, arg2]);
|
||||
* for each (let string in strings.getAll())
|
||||
* dump (string.key + " = " + string.value + "\n");
|
||||
*
|
||||
* @param url {String}
|
||||
* the URL of the string bundle
|
||||
*/
|
||||
function StringBundle(url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
StringBundle.prototype = {
|
||||
/**
|
||||
* the locale associated with the application
|
||||
* @type nsILocale
|
||||
* @private
|
||||
*/
|
||||
get _appLocale() {
|
||||
try {
|
||||
return Cc["@mozilla.org/intl/nslocaleservice;1"].
|
||||
getService(Ci.nsILocaleService).
|
||||
getApplicationLocale();
|
||||
}
|
||||
catch(ex) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* the wrapped nsIStringBundle
|
||||
* @type nsIStringBundle
|
||||
* @private
|
||||
*/
|
||||
get _stringBundle() {
|
||||
let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
|
||||
getService(Ci.nsIStringBundleService).
|
||||
createBundle(this.url, this._appLocale);
|
||||
this.__defineGetter__("_stringBundle", function() stringBundle);
|
||||
return this._stringBundle;
|
||||
},
|
||||
|
||||
|
||||
// the new API
|
||||
|
||||
/**
|
||||
* the URL of the string bundle
|
||||
* @type String
|
||||
*/
|
||||
_url: null,
|
||||
get url() {
|
||||
return this._url;
|
||||
},
|
||||
set url(newVal) {
|
||||
this._url = newVal;
|
||||
delete this._stringBundle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a string from the bundle.
|
||||
*
|
||||
* @param key {String}
|
||||
* the identifier of the string to get
|
||||
* @param args {array} [optional]
|
||||
* an array of arguments that replace occurrences of %S in the string
|
||||
*
|
||||
* @returns {String} the value of the string
|
||||
*/
|
||||
get: function(key, args) {
|
||||
if (args)
|
||||
return this.stringBundle.formatStringFromName(key, args, args.length);
|
||||
else
|
||||
return this.stringBundle.GetStringFromName(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all the strings in the bundle.
|
||||
*
|
||||
* @returns {Array}
|
||||
* an array of objects with key and value properties
|
||||
*/
|
||||
getAll: function() {
|
||||
let strings = [];
|
||||
|
||||
// FIXME: for performance, return an enumerable array that wraps the string
|
||||
// bundle's nsISimpleEnumerator (does JavaScript already support this?).
|
||||
|
||||
let enumerator = this.stringBundle.getSimpleEnumeration();
|
||||
|
||||
while (enumerator.hasMoreElements()) {
|
||||
// We could simply return the nsIPropertyElement objects, but I think
|
||||
// it's better to return standard JS objects that behave as consumers
|
||||
// expect JS objects to behave (f.e. you can modify them dynamically).
|
||||
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
|
||||
strings.push({ key: string.key, value: string.value });
|
||||
}
|
||||
|
||||
return strings;
|
||||
},
|
||||
|
||||
|
||||
// the deprecated XBL binding-compatible API
|
||||
|
||||
/**
|
||||
* the URL of the string bundle
|
||||
* @deprecated because its name doesn't make sense outside of an XBL binding
|
||||
* @type String
|
||||
*/
|
||||
get src() {
|
||||
return this.url;
|
||||
},
|
||||
set src(newVal) {
|
||||
this.url = newVal;
|
||||
},
|
||||
|
||||
/**
|
||||
* the locale associated with the application
|
||||
* @deprecated because it has never been used outside the XBL binding itself,
|
||||
* and consumers should obtain it directly from the locale service anyway.
|
||||
* @type nsILocale
|
||||
*/
|
||||
get appLocale() {
|
||||
return this._appLocale;
|
||||
},
|
||||
|
||||
/**
|
||||
* the wrapped nsIStringBundle
|
||||
* @deprecated because this module should provide all necessary functionality
|
||||
* @type nsIStringBundle
|
||||
*
|
||||
* If you do ever need to use this, let the authors of this module know why
|
||||
* so they can surface functionality for your use case in the module itself
|
||||
* and you don't have to access this underlying XPCOM component.
|
||||
*/
|
||||
get stringBundle() {
|
||||
return this._stringBundle;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a string from the bundle.
|
||||
* @deprecated use |get| instead
|
||||
*
|
||||
* @param key {String}
|
||||
* the identifier of the string to get
|
||||
*
|
||||
* @returns {String}
|
||||
* the value of the string
|
||||
*/
|
||||
getString: function(key) {
|
||||
return this.get(key);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a formatted string from the bundle.
|
||||
* @deprecated use |get| instead
|
||||
*
|
||||
* @param key {string}
|
||||
* the identifier of the string to get
|
||||
* @param args {array}
|
||||
* an array of arguments that replace occurrences of %S in the string
|
||||
*
|
||||
* @returns {String}
|
||||
* the formatted value of the string
|
||||
*/
|
||||
getFormattedString: function(key, args) {
|
||||
return this.get(key, args);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an enumeration of the strings in the bundle.
|
||||
* @deprecated use |getAll| instead
|
||||
*
|
||||
* @returns {nsISimpleEnumerator}
|
||||
* a enumeration of the strings in the bundle
|
||||
*/
|
||||
get strings() {
|
||||
return this.stringBundle.getSimpleEnumeration();
|
||||
}
|
||||
}
|
215
services/sync/modules/ext/Sync.js
Normal file
215
services/sync/modules/ext/Sync.js
Normal file
@ -0,0 +1,215 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Edward Lee <edilee@mozilla.com>
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
let EXPORTED_SYMBOLS = ["Sync"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
// Define some constants to specify various sync. callback states
|
||||
const CB_READY = {};
|
||||
const CB_COMPLETE = {};
|
||||
const CB_FAIL = {};
|
||||
|
||||
// Share a secret only for functions in this file to prevent outside access
|
||||
const SECRET = {};
|
||||
|
||||
/**
|
||||
* Check if the app is ready (not quitting)
|
||||
*/
|
||||
function checkAppReady() {
|
||||
// Watch for app-quit notification to stop any sync. calls
|
||||
let os = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
os.addObserver({
|
||||
observe: function observe() {
|
||||
// Now that the app is quitting, make checkAppReady throw
|
||||
checkAppReady = function() {
|
||||
throw Components.Exception("App. Quitting", Cr.NS_ERROR_ABORT);
|
||||
};
|
||||
os.removeObserver(this, "quit-application");
|
||||
}
|
||||
}, "quit-application", false);
|
||||
|
||||
// In the common case, checkAppReady just returns true
|
||||
return (checkAppReady = function() true)();
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a callback that remembers state like whether it's been called
|
||||
*/
|
||||
function makeCallback() {
|
||||
// Initialize private callback data to prepare to be called
|
||||
let _ = {
|
||||
state: CB_READY,
|
||||
value: null
|
||||
};
|
||||
|
||||
// The main callback remembers the value it's passed and that it got data
|
||||
let onComplete = function makeCallback_onComplete(data) {
|
||||
_.state = CB_COMPLETE;
|
||||
_.value = data;
|
||||
};
|
||||
|
||||
// Only allow access to the private data if the secret matches
|
||||
onComplete._ = function onComplete__(secret) secret == SECRET ? _ : {};
|
||||
|
||||
// Allow an alternate callback to trigger an exception to be thrown
|
||||
onComplete.throw = function onComplete_throw(data) {
|
||||
_.state = CB_FAIL;
|
||||
_.value = data;
|
||||
|
||||
// Cause the caller to get an exception and stop execution
|
||||
throw data;
|
||||
};
|
||||
|
||||
return onComplete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a synchronous version of the function object that will be called with
|
||||
* the provided thisArg.
|
||||
*
|
||||
* @param func {Function}
|
||||
* The asynchronous function to make a synchronous function
|
||||
* @param thisArg {Object} [optional]
|
||||
* The object that the function accesses with "this"
|
||||
* @param callback {Function} [optional] [internal]
|
||||
* The callback that will trigger the end of the async. call
|
||||
* @usage let ret = Sync(asyncFunc, obj)(arg1, arg2);
|
||||
* @usage let ret = Sync(ignoreThisFunc)(arg1, arg2);
|
||||
* @usage let sync = Sync(async); let ret = sync(arg1, arg2);
|
||||
*/
|
||||
function Sync(func, thisArg, callback) {
|
||||
return function syncFunc(/* arg1, arg2, ... */) {
|
||||
// Grab the current thread so we can make it give up priority
|
||||
let thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
|
||||
|
||||
// Save the original arguments into an array
|
||||
let args = Array.slice(arguments);
|
||||
|
||||
let instanceCallback = callback;
|
||||
// We need to create a callback and insert it if we weren't given one
|
||||
if (instanceCallback == null) {
|
||||
// Create a new callback for this invocation instance and pass it in
|
||||
instanceCallback = makeCallback();
|
||||
args.unshift(instanceCallback);
|
||||
}
|
||||
|
||||
// Call the async function bound to thisArg with the passed args
|
||||
func.apply(thisArg, args);
|
||||
|
||||
// Keep waiting until our callback is triggered unless the app is quitting
|
||||
let callbackData = instanceCallback._(SECRET);
|
||||
while (checkAppReady() && callbackData.state == CB_READY)
|
||||
thread.processNextEvent(true);
|
||||
|
||||
// Reset the state of the callback to prepare for another call
|
||||
let state = callbackData.state;
|
||||
callbackData.state = CB_READY;
|
||||
|
||||
// Throw the value the callback decided to fail with
|
||||
if (state == CB_FAIL)
|
||||
throw callbackData.value;
|
||||
|
||||
// Return the value passed to the callback
|
||||
return callbackData.value;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a synchronous version of an async. function and the callback to trigger
|
||||
* the end of the async. call.
|
||||
*
|
||||
* @param func {Function}
|
||||
* The asynchronous function to make a synchronous function
|
||||
* @param thisArg {Object} [optional]
|
||||
* The object that the function accesses with "this"
|
||||
* @usage let [sync, cb] = Sync.withCb(async); let ret = sync(arg1, arg2, cb);
|
||||
*/
|
||||
Sync.withCb = function Sync_withCb(func, thisArg) {
|
||||
let cb = makeCallback();
|
||||
return [Sync(func, thisArg, cb), cb];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a timer, simulating the API for the window.setTimeout call.
|
||||
* This only simulates the API for the version of the call that accepts
|
||||
* a function as its first argument and no additional parameters,
|
||||
* and it doesn't return the timeout ID.
|
||||
*
|
||||
* @param func {Function}
|
||||
* the function to call after the delay
|
||||
* @param delay {Number}
|
||||
* the number of milliseconds to wait
|
||||
*/
|
||||
function setTimeout(func, delay) {
|
||||
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
let callback = {
|
||||
notify: function notify() {
|
||||
// This line actually just keeps a reference to timer (prevent GC)
|
||||
timer = null;
|
||||
|
||||
// Call the function so that "this" is global
|
||||
func();
|
||||
}
|
||||
}
|
||||
timer.initWithCallback(callback, delay, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
function sleep(callback, milliseconds) {
|
||||
setTimeout(callback, milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep the specified number of milliseconds, pausing execution of the caller
|
||||
* without halting the current thread.
|
||||
* For example, the following code pauses 1000ms between dumps:
|
||||
*
|
||||
* dump("Wait for it...\n");
|
||||
* Sync.sleep(1000);
|
||||
* dump("Wait for it...\n");
|
||||
* Sync.sleep(1000);
|
||||
* dump("What are you waiting for?!\n");
|
||||
*
|
||||
* @param milliseconds {Number}
|
||||
* The number of milliseconds to sleep
|
||||
*/
|
||||
Sync.sleep = Sync(sleep);
|
122
services/sync/modules/identity.js
Normal file
122
services/sync/modules/identity.js
Normal file
@ -0,0 +1,122 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Identity', 'ID'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/ext/Sync.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
Utils.lazy(this, 'ID', IDManager);
|
||||
|
||||
// For storing identities we'll use throughout Weave
|
||||
function IDManager() {
|
||||
this._ids = {};
|
||||
}
|
||||
IDManager.prototype = {
|
||||
get: function IDMgr_get(name) {
|
||||
if (name in this._ids)
|
||||
return this._ids[name];
|
||||
return null;
|
||||
},
|
||||
set: function IDMgr_set(name, id) {
|
||||
this._ids[name] = id;
|
||||
return id;
|
||||
},
|
||||
del: function IDMgr_del(name) {
|
||||
delete this._ids[name];
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Identity
|
||||
* These objects hold a realm, username, and password
|
||||
* They can hold a password in memory, but will try to fetch it from
|
||||
* the password manager if it's not set.
|
||||
* FIXME: need to rethink this stuff as part of a bigger identity mgmt framework
|
||||
*/
|
||||
|
||||
function Identity(realm, username, password) {
|
||||
this.realm = realm;
|
||||
this.username = username;
|
||||
this._password = password;
|
||||
}
|
||||
Identity.prototype = {
|
||||
get password() {
|
||||
// Look up the password then cache it
|
||||
if (this._password == null)
|
||||
for each (let login in this._logins)
|
||||
if (login.username.toLowerCase() == this.username)
|
||||
this._password = login.password;
|
||||
return this._password;
|
||||
},
|
||||
|
||||
set password(value) this._password = value,
|
||||
|
||||
persist: function persist() {
|
||||
// Clean up any existing passwords unless it's what we're persisting
|
||||
let exists = false;
|
||||
for each (let login in this._logins) {
|
||||
if (login.username == this.username && login.password == this._password)
|
||||
exists = true;
|
||||
else
|
||||
Svc.Login.removeLogin(login);
|
||||
}
|
||||
|
||||
// No need to create the login after clearing out the other ones
|
||||
let log = Log4Moz.repository.getLogger("Identity");
|
||||
if (exists) {
|
||||
log.trace("Skipping persist: " + this.realm + " for " + this.username);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the new username/password
|
||||
log.trace("Persisting " + this.realm + " for " + this.username);
|
||||
let nsLoginInfo = new Components.Constructor(
|
||||
"@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
|
||||
let newLogin = new nsLoginInfo(PWDMGR_HOST, null, this.realm,
|
||||
this.username, this.password, "", "");
|
||||
Svc.Login.addLogin(newLogin);
|
||||
},
|
||||
|
||||
get _logins() Svc.Login.findLogins({}, PWDMGR_HOST, null, this.realm)
|
||||
};
|
561
services/sync/modules/log4moz.js
Normal file
561
services/sync/modules/log4moz.js
Normal file
@ -0,0 +1,561 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is log4moz
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* Michael Johnston
|
||||
* Portions created by the Initial Developer are Copyright (C) 2006
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Michael Johnston <special.michael@gmail.com>
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Log4Moz'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
const MODE_RDONLY = 0x01;
|
||||
const MODE_WRONLY = 0x02;
|
||||
const MODE_CREATE = 0x08;
|
||||
const MODE_APPEND = 0x10;
|
||||
const MODE_TRUNCATE = 0x20;
|
||||
|
||||
const PERMS_FILE = 0644;
|
||||
const PERMS_DIRECTORY = 0755;
|
||||
|
||||
const ONE_BYTE = 1;
|
||||
const ONE_KILOBYTE = 1024 * ONE_BYTE;
|
||||
const ONE_MEGABYTE = 1024 * ONE_KILOBYTE;
|
||||
|
||||
let Log4Moz = {
|
||||
Level: {
|
||||
Fatal: 70,
|
||||
Error: 60,
|
||||
Warn: 50,
|
||||
Info: 40,
|
||||
Config: 30,
|
||||
Debug: 20,
|
||||
Trace: 10,
|
||||
All: 0,
|
||||
Desc: {
|
||||
70: "FATAL",
|
||||
60: "ERROR",
|
||||
50: "WARN",
|
||||
40: "INFO",
|
||||
30: "CONFIG",
|
||||
20: "DEBUG",
|
||||
10: "TRACE",
|
||||
0: "ALL"
|
||||
}
|
||||
},
|
||||
|
||||
get repository() {
|
||||
delete Log4Moz.repository;
|
||||
Log4Moz.repository = new LoggerRepository();
|
||||
return Log4Moz.repository;
|
||||
},
|
||||
set repository(value) {
|
||||
delete Log4Moz.repository;
|
||||
Log4Moz.repository = value;
|
||||
},
|
||||
|
||||
get LogMessage() { return LogMessage; },
|
||||
get Logger() { return Logger; },
|
||||
get LoggerRepository() { return LoggerRepository; },
|
||||
|
||||
get Formatter() { return Formatter; },
|
||||
get BasicFormatter() { return BasicFormatter; },
|
||||
|
||||
get Appender() { return Appender; },
|
||||
get DumpAppender() { return DumpAppender; },
|
||||
get ConsoleAppender() { return ConsoleAppender; },
|
||||
get FileAppender() { return FileAppender; },
|
||||
get RotatingFileAppender() { return RotatingFileAppender; },
|
||||
|
||||
// Logging helper:
|
||||
// let logger = Log4Moz.repository.getLogger("foo");
|
||||
// logger.info(Log4Moz.enumerateInterfaces(someObject).join(","));
|
||||
enumerateInterfaces: function Log4Moz_enumerateInterfaces(aObject) {
|
||||
let interfaces = [];
|
||||
|
||||
for (i in Ci) {
|
||||
try {
|
||||
aObject.QueryInterface(Ci[i]);
|
||||
interfaces.push(i);
|
||||
}
|
||||
catch(ex) {}
|
||||
}
|
||||
|
||||
return interfaces;
|
||||
},
|
||||
|
||||
// Logging helper:
|
||||
// let logger = Log4Moz.repository.getLogger("foo");
|
||||
// logger.info(Log4Moz.enumerateProperties(someObject).join(","));
|
||||
enumerateProperties: function Log4Moz_enumerateProps(aObject,
|
||||
aExcludeComplexTypes) {
|
||||
let properties = [];
|
||||
|
||||
for (p in aObject) {
|
||||
try {
|
||||
if (aExcludeComplexTypes &&
|
||||
(typeof aObject[p] == "object" || typeof aObject[p] == "function"))
|
||||
continue;
|
||||
properties.push(p + " = " + aObject[p]);
|
||||
}
|
||||
catch(ex) {
|
||||
properties.push(p + " = " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* LogMessage
|
||||
* Encapsulates a single log event's data
|
||||
*/
|
||||
function LogMessage(loggerName, level, message){
|
||||
this.loggerName = loggerName;
|
||||
this.message = message;
|
||||
this.level = level;
|
||||
this.time = Date.now();
|
||||
}
|
||||
LogMessage.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
|
||||
|
||||
get levelDesc() {
|
||||
if (this.level in Log4Moz.Level.Desc)
|
||||
return Log4Moz.Level.Desc[this.level];
|
||||
return "UNKNOWN";
|
||||
},
|
||||
|
||||
toString: function LogMsg_toString(){
|
||||
return "LogMessage [" + this.time + " " + this.level + " " +
|
||||
this.message + "]";
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Logger
|
||||
* Hierarchical version. Logs to all appenders, assigned or inherited
|
||||
*/
|
||||
|
||||
function Logger(name, repository) {
|
||||
if (!repository)
|
||||
repository = Log4Moz.repository;
|
||||
this._name = name;
|
||||
this._appenders = [];
|
||||
this._repository = repository;
|
||||
}
|
||||
Logger.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
|
||||
|
||||
parent: null,
|
||||
|
||||
get name() {
|
||||
return this._name;
|
||||
},
|
||||
|
||||
_level: null,
|
||||
get level() {
|
||||
if (this._level != null)
|
||||
return this._level;
|
||||
if (this.parent)
|
||||
return this.parent.level;
|
||||
dump("log4moz warning: root logger configuration error: no level defined\n");
|
||||
return Log4Moz.Level.All;
|
||||
},
|
||||
set level(level) {
|
||||
this._level = level;
|
||||
},
|
||||
|
||||
_appenders: null,
|
||||
get appenders() {
|
||||
if (!this.parent)
|
||||
return this._appenders;
|
||||
return this._appenders.concat(this.parent.appenders);
|
||||
},
|
||||
|
||||
addAppender: function Logger_addAppender(appender) {
|
||||
for (let i = 0; i < this._appenders.length; i++) {
|
||||
if (this._appenders[i] == appender)
|
||||
return;
|
||||
}
|
||||
this._appenders.push(appender);
|
||||
},
|
||||
|
||||
removeAppender: function Logger_removeAppender(appender) {
|
||||
let newAppenders = [];
|
||||
for (let i = 0; i < this._appenders.length; i++) {
|
||||
if (this._appenders[i] != appender)
|
||||
newAppenders.push(this._appenders[i]);
|
||||
}
|
||||
this._appenders = newAppenders;
|
||||
},
|
||||
|
||||
log: function Logger_log(message) {
|
||||
if (this.level > message.level)
|
||||
return;
|
||||
let appenders = this.appenders;
|
||||
for (let i = 0; i < appenders.length; i++){
|
||||
appenders[i].append(message);
|
||||
}
|
||||
},
|
||||
|
||||
fatal: function Logger_fatal(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Fatal, string));
|
||||
},
|
||||
error: function Logger_error(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Error, string));
|
||||
},
|
||||
warn: function Logger_warn(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Warn, string));
|
||||
},
|
||||
info: function Logger_info(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Info, string));
|
||||
},
|
||||
config: function Logger_config(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Config, string));
|
||||
},
|
||||
debug: function Logger_debug(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Debug, string));
|
||||
},
|
||||
trace: function Logger_trace(string) {
|
||||
this.log(new LogMessage(this._name, Log4Moz.Level.Trace, string));
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* LoggerRepository
|
||||
* Implements a hierarchy of Loggers
|
||||
*/
|
||||
|
||||
function LoggerRepository() {}
|
||||
LoggerRepository.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
|
||||
|
||||
_loggers: {},
|
||||
|
||||
_rootLogger: null,
|
||||
get rootLogger() {
|
||||
if (!this._rootLogger) {
|
||||
this._rootLogger = new Logger("root", this);
|
||||
this._rootLogger.level = Log4Moz.Level.All;
|
||||
}
|
||||
return this._rootLogger;
|
||||
},
|
||||
// FIXME: need to update all parent values if we do this
|
||||
//set rootLogger(logger) {
|
||||
// this._rootLogger = logger;
|
||||
//},
|
||||
|
||||
_updateParents: function LogRep__updateParents(name) {
|
||||
let pieces = name.split('.');
|
||||
let cur, parent;
|
||||
|
||||
// find the closest parent
|
||||
// don't test for the logger name itself, as there's a chance it's already
|
||||
// there in this._loggers
|
||||
for (let i = 0; i < pieces.length - 1; i++) {
|
||||
if (cur)
|
||||
cur += '.' + pieces[i];
|
||||
else
|
||||
cur = pieces[i];
|
||||
if (cur in this._loggers)
|
||||
parent = cur;
|
||||
}
|
||||
|
||||
// if we didn't assign a parent above, there is no parent
|
||||
if (!parent)
|
||||
this._loggers[name].parent = this.rootLogger;
|
||||
else
|
||||
this._loggers[name].parent = this._loggers[parent];
|
||||
|
||||
// trigger updates for any possible descendants of this logger
|
||||
for (let logger in this._loggers) {
|
||||
if (logger != name && logger.indexOf(name) == 0)
|
||||
this._updateParents(logger);
|
||||
}
|
||||
},
|
||||
|
||||
getLogger: function LogRep_getLogger(name) {
|
||||
if (!name)
|
||||
name = this.getLogger.caller.name;
|
||||
if (name in this._loggers)
|
||||
return this._loggers[name];
|
||||
this._loggers[name] = new Logger(name, this);
|
||||
this._updateParents(name);
|
||||
return this._loggers[name];
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Formatters
|
||||
* These massage a LogMessage into whatever output is desired
|
||||
* Only the BasicFormatter is currently implemented
|
||||
*/
|
||||
|
||||
// Abstract formatter
|
||||
function Formatter() {}
|
||||
Formatter.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
|
||||
format: function Formatter_format(message) {}
|
||||
};
|
||||
|
||||
// FIXME: should allow for formatting the whole string, not just the date
|
||||
function BasicFormatter(dateFormat) {
|
||||
if (dateFormat)
|
||||
this.dateFormat = dateFormat;
|
||||
}
|
||||
BasicFormatter.prototype = {
|
||||
__proto__: Formatter.prototype,
|
||||
|
||||
_dateFormat: null,
|
||||
|
||||
get dateFormat() {
|
||||
if (!this._dateFormat)
|
||||
this._dateFormat = "%Y-%m-%d %H:%M:%S";
|
||||
return this._dateFormat;
|
||||
},
|
||||
|
||||
set dateFormat(format) {
|
||||
this._dateFormat = format;
|
||||
},
|
||||
|
||||
format: function BF_format(message) {
|
||||
// Pad a string to a certain length (20) with a character (space)
|
||||
let pad = function BF__pad(str, len, chr) str +
|
||||
new Array(Math.max((len || 20) - str.length + 1, 0)).join(chr || " ");
|
||||
|
||||
// Generate a date string because toLocaleString doesn't work XXX 514803
|
||||
let z = function(n) n < 10 ? "0" + n : n;
|
||||
let d = new Date(message.time);
|
||||
let dateStr = [d.getFullYear(), "-", z(d.getMonth() + 1), "-",
|
||||
z(d.getDate()), " ", z(d.getHours()), ":", z(d.getMinutes()), ":",
|
||||
z(d.getSeconds())].join("");
|
||||
|
||||
return dateStr + "\t" + pad(message.loggerName) + " " + message.levelDesc +
|
||||
"\t" + message.message + "\n";
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Appenders
|
||||
* These can be attached to Loggers to log to different places
|
||||
* Simply subclass and override doAppend to implement a new one
|
||||
*/
|
||||
|
||||
function Appender(formatter) {
|
||||
this._name = "Appender";
|
||||
this._formatter = formatter? formatter : new BasicFormatter();
|
||||
}
|
||||
Appender.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports]),
|
||||
|
||||
_level: Log4Moz.Level.All,
|
||||
get level() { return this._level; },
|
||||
set level(level) { this._level = level; },
|
||||
|
||||
append: function App_append(message) {
|
||||
if(this._level <= message.level)
|
||||
this.doAppend(this._formatter.format(message));
|
||||
},
|
||||
toString: function App_toString() {
|
||||
return this._name + " [level=" + this._level +
|
||||
", formatter=" + this._formatter + "]";
|
||||
},
|
||||
doAppend: function App_doAppend(message) {}
|
||||
};
|
||||
|
||||
/*
|
||||
* DumpAppender
|
||||
* Logs to standard out
|
||||
*/
|
||||
|
||||
function DumpAppender(formatter) {
|
||||
this._name = "DumpAppender";
|
||||
this._formatter = formatter? formatter : new BasicFormatter();
|
||||
}
|
||||
DumpAppender.prototype = {
|
||||
__proto__: Appender.prototype,
|
||||
|
||||
doAppend: function DApp_doAppend(message) {
|
||||
dump(message);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* ConsoleAppender
|
||||
* Logs to the javascript console
|
||||
*/
|
||||
|
||||
function ConsoleAppender(formatter) {
|
||||
this._name = "ConsoleAppender";
|
||||
this._formatter = formatter;
|
||||
}
|
||||
ConsoleAppender.prototype = {
|
||||
__proto__: Appender.prototype,
|
||||
|
||||
doAppend: function CApp_doAppend(message) {
|
||||
if (message.level > Log4Moz.Level.Warn) {
|
||||
Cu.reportError(message);
|
||||
return;
|
||||
}
|
||||
Cc["@mozilla.org/consoleservice;1"].
|
||||
getService(Ci.nsIConsoleService).logStringMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* FileAppender
|
||||
* Logs to a file
|
||||
*/
|
||||
|
||||
function FileAppender(file, formatter) {
|
||||
this._name = "FileAppender";
|
||||
this._file = file; // nsIFile
|
||||
this._formatter = formatter? formatter : new BasicFormatter();
|
||||
}
|
||||
FileAppender.prototype = {
|
||||
__proto__: Appender.prototype,
|
||||
__fos: null,
|
||||
get _fos() {
|
||||
if (!this.__fos)
|
||||
this.openStream();
|
||||
return this.__fos;
|
||||
},
|
||||
|
||||
openStream: function FApp_openStream() {
|
||||
try {
|
||||
let __fos = Cc["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Ci.nsIFileOutputStream);
|
||||
let flags = MODE_WRONLY | MODE_CREATE | MODE_APPEND;
|
||||
__fos.init(this._file, flags, PERMS_FILE, 0);
|
||||
|
||||
this.__fos = Cc["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Ci.nsIConverterOutputStream);
|
||||
this.__fos.init(__fos, "UTF-8", 4096,
|
||||
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
} catch(e) {
|
||||
dump("Error opening stream:\n" + e);
|
||||
}
|
||||
},
|
||||
|
||||
closeStream: function FApp_closeStream() {
|
||||
if (!this.__fos)
|
||||
return;
|
||||
try {
|
||||
this.__fos.close();
|
||||
this.__fos = null;
|
||||
} catch(e) {
|
||||
dump("Failed to close file output stream\n" + e);
|
||||
}
|
||||
},
|
||||
|
||||
doAppend: function FApp_doAppend(message) {
|
||||
if (message === null || message.length <= 0)
|
||||
return;
|
||||
try {
|
||||
this._fos.writeString(message);
|
||||
} catch(e) {
|
||||
dump("Error writing file:\n" + e);
|
||||
}
|
||||
},
|
||||
|
||||
clear: function FApp_clear() {
|
||||
this.closeStream();
|
||||
try {
|
||||
this._file.remove(false);
|
||||
} catch (e) {
|
||||
// XXX do something?
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* RotatingFileAppender
|
||||
* Similar to FileAppender, but rotates logs when they become too large
|
||||
*/
|
||||
|
||||
function RotatingFileAppender(file, formatter, maxSize, maxBackups) {
|
||||
if (maxSize === undefined)
|
||||
maxSize = ONE_MEGABYTE * 2;
|
||||
|
||||
if (maxBackups === undefined)
|
||||
maxBackups = 0;
|
||||
|
||||
this._name = "RotatingFileAppender";
|
||||
this._file = file; // nsIFile
|
||||
this._formatter = formatter? formatter : new BasicFormatter();
|
||||
this._maxSize = maxSize;
|
||||
this._maxBackups = maxBackups;
|
||||
}
|
||||
RotatingFileAppender.prototype = {
|
||||
__proto__: FileAppender.prototype,
|
||||
|
||||
doAppend: function RFApp_doAppend(message) {
|
||||
if (message === null || message.length <= 0)
|
||||
return;
|
||||
try {
|
||||
this.rotateLogs();
|
||||
FileAppender.prototype.doAppend.call(this, message);
|
||||
} catch(e) {
|
||||
dump("Error writing file:" + e + "\n");
|
||||
}
|
||||
},
|
||||
|
||||
rotateLogs: function RFApp_rotateLogs() {
|
||||
if(this._file.exists() &&
|
||||
this._file.fileSize < this._maxSize)
|
||||
return;
|
||||
|
||||
this.closeStream();
|
||||
|
||||
for (let i = this.maxBackups - 1; i > 0; i--){
|
||||
let backup = this._file.parent.clone();
|
||||
backup.append(this._file.leafName + "." + i);
|
||||
if (backup.exists())
|
||||
backup.moveTo(this._file.parent, this._file.leafName + "." + (i + 1));
|
||||
}
|
||||
|
||||
let cur = this._file.clone();
|
||||
if (cur.exists())
|
||||
cur.moveTo(cur.parent, cur.leafName + ".1");
|
||||
|
||||
// Note: this._file still points to the same file
|
||||
}
|
||||
};
|
158
services/sync/modules/notifications.js
Normal file
158
services/sync/modules/notifications.js
Normal file
@ -0,0 +1,158 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Myk Melez <myk@mozilla.org>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["Notifications", "Notification", "NotificationButton"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
let Notifications = {
|
||||
// Match the referenced values in toolkit/content/widgets/notification.xml.
|
||||
get PRIORITY_INFO() 1, // PRIORITY_INFO_LOW
|
||||
get PRIORITY_WARNING() 4, // PRIORITY_WARNING_LOW
|
||||
get PRIORITY_ERROR() 7, // PRIORITY_CRITICAL_LOW
|
||||
|
||||
// FIXME: instead of making this public, dress the Notifications object
|
||||
// to behave like an iterator (using generators?) and have callers access
|
||||
// this array through the Notifications object.
|
||||
notifications: [],
|
||||
|
||||
_observers: [],
|
||||
|
||||
// XXX Should we have a helper method for adding a simple notification?
|
||||
// I.e. something like |function notify(title, description, priority)|.
|
||||
|
||||
add: function Notifications_add(notification) {
|
||||
this.notifications.push(notification);
|
||||
Observers.notify("weave:notification:added", notification, null);
|
||||
},
|
||||
|
||||
remove: function Notifications_remove(notification) {
|
||||
let index = this.notifications.indexOf(notification);
|
||||
|
||||
if (index != -1) {
|
||||
this.notifications.splice(index, 1);
|
||||
Observers.notify("weave:notification:removed", notification, null);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Replace an existing notification.
|
||||
*/
|
||||
replace: function Notifications_replace(oldNotification, newNotification) {
|
||||
let index = this.notifications.indexOf(oldNotification);
|
||||
|
||||
if (index != -1)
|
||||
this.notifications.splice(index, 1, newNotification);
|
||||
else {
|
||||
this.notifications.push(newNotification);
|
||||
// XXX Should we throw because we didn't find the existing notification?
|
||||
// XXX Should we notify observers about weave:notification:added?
|
||||
}
|
||||
|
||||
// XXX Should we notify observers about weave:notification:replaced?
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove all notifications that match a title. If no title is provided, all
|
||||
* notifications are removed.
|
||||
*
|
||||
* @param title [optional]
|
||||
* Title of notifications to remove; falsy value means remove all
|
||||
*/
|
||||
removeAll: function Notifications_removeAll(title) {
|
||||
this.notifications.filter(function(old) old.title == title || !title).
|
||||
forEach(function(old) this.remove(old), this);
|
||||
},
|
||||
|
||||
// replaces all existing notifications with the same title as the new one
|
||||
replaceTitle: function Notifications_replaceTitle(notification) {
|
||||
this.removeAll(notification.title);
|
||||
this.add(notification);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A basic notification. Subclass this to create more complex notifications.
|
||||
*/
|
||||
function Notification(title, description, iconURL, priority, buttons) {
|
||||
this.title = title;
|
||||
this.description = description;
|
||||
|
||||
if (iconURL)
|
||||
this.iconURL = iconURL;
|
||||
|
||||
if (priority)
|
||||
this.priority = priority;
|
||||
|
||||
if (buttons)
|
||||
this.buttons = buttons;
|
||||
}
|
||||
|
||||
// We set each prototype property individually instead of redefining
|
||||
// the entire prototype to avoid blowing away existing properties
|
||||
// of the prototype like the the "constructor" property, which we use
|
||||
// to bind notification objects to their XBL representations.
|
||||
Notification.prototype.priority = Notifications.PRIORITY_INFO;
|
||||
Notification.prototype.iconURL = null;
|
||||
Notification.prototype.buttons = [];
|
||||
|
||||
/**
|
||||
* A button to display in a notification.
|
||||
*/
|
||||
function NotificationButton(label, accessKey, callback) {
|
||||
function callbackWrapper() {
|
||||
try {
|
||||
callback.apply(this, arguments);
|
||||
} catch (e) {
|
||||
let logger = Log4Moz.repository.getLogger("Notifications");
|
||||
logger.error("An exception occurred: " + Utils.exceptionStr(e));
|
||||
logger.info(Utils.stackTrace(e));
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
this.label = label;
|
||||
this.accessKey = accessKey;
|
||||
this.callback = callbackWrapper;
|
||||
}
|
417
services/sync/modules/resource.js
Normal file
417
services/sync/modules/resource.js
Normal file
@ -0,0 +1,417 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["Resource"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/ext/Preferences.js");
|
||||
Cu.import("resource://services-sync/ext/Sync.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// = Resource =
|
||||
//
|
||||
// Represents a remote network resource, identified by a URI.
|
||||
function Resource(uri) {
|
||||
this._log = Log4Moz.repository.getLogger(this._logName);
|
||||
this._log.level =
|
||||
Log4Moz.Level[Utils.prefs.getCharPref("log.logger.network.resources")];
|
||||
this.uri = uri;
|
||||
this._headers = {};
|
||||
}
|
||||
Resource.prototype = {
|
||||
_logName: "Net.Resource",
|
||||
|
||||
// ** {{{ Resource.serverTime }}} **
|
||||
//
|
||||
// Caches the latest server timestamp (X-Weave-Timestamp header).
|
||||
serverTime: null,
|
||||
|
||||
// ** {{{ Resource.authenticator }}} **
|
||||
//
|
||||
// Getter and setter for the authenticator module
|
||||
// responsible for this particular resource. The authenticator
|
||||
// module may modify the headers to perform authentication
|
||||
// while performing a request for the resource, for example.
|
||||
get authenticator() {
|
||||
if (this._authenticator)
|
||||
return this._authenticator;
|
||||
else
|
||||
return Auth.lookupAuthenticator(this.spec);
|
||||
},
|
||||
set authenticator(value) {
|
||||
this._authenticator = value;
|
||||
},
|
||||
|
||||
// ** {{{ Resource.headers }}} **
|
||||
//
|
||||
// Headers to be included when making a request for the resource.
|
||||
// Note: Header names should be all lower case, there's no explicit
|
||||
// check for duplicates due to case!
|
||||
get headers() {
|
||||
return this.authenticator.onRequest(this._headers);
|
||||
},
|
||||
set headers(value) {
|
||||
this._headers = value;
|
||||
},
|
||||
setHeader: function Res_setHeader() {
|
||||
if (arguments.length % 2)
|
||||
throw "setHeader only accepts arguments in multiples of 2";
|
||||
for (let i = 0; i < arguments.length; i += 2) {
|
||||
this._headers[arguments[i].toLowerCase()] = arguments[i + 1];
|
||||
}
|
||||
},
|
||||
|
||||
// ** {{{ Resource.uri }}} **
|
||||
//
|
||||
// URI representing this resource.
|
||||
get uri() {
|
||||
return this._uri;
|
||||
},
|
||||
set uri(value) {
|
||||
if (typeof value == 'string')
|
||||
this._uri = Utils.makeURI(value);
|
||||
else
|
||||
this._uri = value;
|
||||
},
|
||||
|
||||
// ** {{{ Resource.spec }}} **
|
||||
//
|
||||
// Get the string representation of the URI.
|
||||
get spec() {
|
||||
if (this._uri)
|
||||
return this._uri.spec;
|
||||
return null;
|
||||
},
|
||||
|
||||
// ** {{{ Resource.data }}} **
|
||||
//
|
||||
// Get and set the data encapulated in the resource.
|
||||
_data: null,
|
||||
get data() this._data,
|
||||
set data(value) {
|
||||
this._data = value;
|
||||
},
|
||||
|
||||
// ** {{{ Resource._createRequest }}} **
|
||||
//
|
||||
// This method returns a new IO Channel for requests to be made
|
||||
// through. It is never called directly, only {{{_request}}} uses it
|
||||
// to obtain a request channel.
|
||||
//
|
||||
_createRequest: function Res__createRequest() {
|
||||
let channel = Svc.IO.newChannel(this.spec, null, null).
|
||||
QueryInterface(Ci.nsIRequest).QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
// Always validate the cache:
|
||||
channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
|
||||
channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
||||
|
||||
// Setup a callback to handle bad HTTPS certificates.
|
||||
channel.notificationCallbacks = new BadCertListener();
|
||||
|
||||
// Avoid calling the authorizer more than once
|
||||
let headers = this.headers;
|
||||
for (let key in headers) {
|
||||
if (key == 'Authorization')
|
||||
this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
|
||||
else
|
||||
this._log.trace("HTTP Header " + key + ": " + headers[key]);
|
||||
channel.setRequestHeader(key, headers[key], false);
|
||||
}
|
||||
return channel;
|
||||
},
|
||||
|
||||
_onProgress: function Res__onProgress(channel) {},
|
||||
|
||||
// ** {{{ Resource._request }}} **
|
||||
//
|
||||
// Perform a particular HTTP request on the resource. This method
|
||||
// is never called directly, but is used by the high-level
|
||||
// {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods.
|
||||
_request: function Res__request(action, data) {
|
||||
let iter = 0;
|
||||
let channel = this._createRequest();
|
||||
|
||||
if ("undefined" != typeof(data))
|
||||
this._data = data;
|
||||
|
||||
// PUT and POST are trreated differently because
|
||||
// they have payload data.
|
||||
if ("PUT" == action || "POST" == action) {
|
||||
// Convert non-string bodies into JSON
|
||||
if (this._data.constructor.toString() != String)
|
||||
this._data = JSON.stringify(this._data);
|
||||
|
||||
this._log.debug(action + " Length: " + this._data.length);
|
||||
this._log.trace(action + " Body: " + this._data);
|
||||
|
||||
let type = ('content-type' in this._headers) ?
|
||||
this._headers['content-type'] : 'text/plain';
|
||||
|
||||
let stream = Cc["@mozilla.org/io/string-input-stream;1"].
|
||||
createInstance(Ci.nsIStringInputStream);
|
||||
stream.setData(this._data, this._data.length);
|
||||
|
||||
channel.QueryInterface(Ci.nsIUploadChannel);
|
||||
channel.setUploadStream(stream, type, this._data.length);
|
||||
}
|
||||
|
||||
// Setup a channel listener so that the actual network operation
|
||||
// is performed asynchronously.
|
||||
let [chanOpen, chanCb] = Sync.withCb(channel.asyncOpen, channel);
|
||||
let listener = new ChannelListener(chanCb, this._onProgress, this._log);
|
||||
channel.requestMethod = action;
|
||||
|
||||
// The channel listener might get a failure code
|
||||
try {
|
||||
this._data = chanOpen(listener, null);
|
||||
}
|
||||
catch(ex) {
|
||||
// Combine the channel stack with this request stack
|
||||
let error = Error(ex.message);
|
||||
let chanStack = [];
|
||||
if (ex.stack)
|
||||
chanStack = ex.stack.trim().split(/\n/).slice(1);
|
||||
let requestStack = error.stack.split(/\n/).slice(1);
|
||||
|
||||
// Strip out the args for the last 2 frames because they're usually HUGE!
|
||||
for (let i = 0; i <= 1; i++)
|
||||
requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@");
|
||||
|
||||
error.stack = chanStack.concat(requestStack).join("\n");
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Set some default values in-case there's no response header
|
||||
let headers = {};
|
||||
let status = 0;
|
||||
let success = true;
|
||||
try {
|
||||
// Read out the response headers if available
|
||||
channel.visitResponseHeaders({
|
||||
visitHeader: function visitHeader(header, value) {
|
||||
headers[header.toLowerCase()] = value;
|
||||
}
|
||||
});
|
||||
status = channel.responseStatus;
|
||||
success = channel.requestSucceeded;
|
||||
|
||||
// Log the status of the request
|
||||
let mesg = [action, success ? "success" : "fail", status,
|
||||
channel.URI.spec].join(" ");
|
||||
if (mesg.length > 200)
|
||||
mesg = mesg.substr(0, 200) + "…";
|
||||
this._log.debug(mesg);
|
||||
// Additionally give the full response body when Trace logging
|
||||
if (this._log.level <= Log4Moz.Level.Trace)
|
||||
this._log.trace(action + " body: " + this._data);
|
||||
|
||||
// this is a server-side safety valve to allow slowing down clients without hurting performance
|
||||
if (headers["x-weave-backoff"])
|
||||
Observers.notify("weave:service:backoff:interval", parseInt(headers["x-weave-backoff"], 10))
|
||||
}
|
||||
// Got a response but no header; must be cached (use default values)
|
||||
catch(ex) {
|
||||
this._log.debug(action + " cached: " + status);
|
||||
}
|
||||
|
||||
let ret = new String(this._data);
|
||||
ret.headers = headers;
|
||||
ret.status = status;
|
||||
ret.success = success;
|
||||
|
||||
// Make a lazy getter to convert the json response into an object
|
||||
Utils.lazy2(ret, "obj", function() JSON.parse(ret));
|
||||
|
||||
// Notify if we get a 401 to maybe try again with a new uri
|
||||
if (status == 401) {
|
||||
// Create an object to allow observers to decide if we should try again
|
||||
let subject = {
|
||||
newUri: "",
|
||||
resource: this,
|
||||
response: ret
|
||||
}
|
||||
Observers.notify("weave:resource:status:401", subject);
|
||||
|
||||
// Do the same type of request but with the new uri
|
||||
if (subject.newUri != "") {
|
||||
this.uri = subject.newUri;
|
||||
return this._request.apply(this, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
// ** {{{ Resource.get }}} **
|
||||
//
|
||||
// Perform an asynchronous HTTP GET for this resource.
|
||||
// onComplete will be called on completion of the request.
|
||||
get: function Res_get() {
|
||||
return this._request("GET");
|
||||
},
|
||||
|
||||
// ** {{{ Resource.get }}} **
|
||||
//
|
||||
// Perform a HTTP PUT for this resource.
|
||||
put: function Res_put(data) {
|
||||
return this._request("PUT", data);
|
||||
},
|
||||
|
||||
// ** {{{ Resource.post }}} **
|
||||
//
|
||||
// Perform a HTTP POST for this resource.
|
||||
post: function Res_post(data) {
|
||||
return this._request("POST", data);
|
||||
},
|
||||
|
||||
// ** {{{ Resource.delete }}} **
|
||||
//
|
||||
// Perform a HTTP DELETE for this resource.
|
||||
delete: function Res_delete() {
|
||||
return this._request("DELETE");
|
||||
}
|
||||
};
|
||||
|
||||
// = ChannelListener =
|
||||
//
|
||||
// This object implements the {{{nsIStreamListener}}} interface
|
||||
// and is called as the network operation proceeds.
|
||||
function ChannelListener(onComplete, onProgress, logger) {
|
||||
this._onComplete = onComplete;
|
||||
this._onProgress = onProgress;
|
||||
this._log = logger;
|
||||
this.delayAbort();
|
||||
}
|
||||
ChannelListener.prototype = {
|
||||
// Wait 5 minutes before killing a request
|
||||
ABORT_TIMEOUT: 300000,
|
||||
|
||||
onStartRequest: function Channel_onStartRequest(channel) {
|
||||
channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
|
||||
// Save the latest server timestamp when possible
|
||||
try {
|
||||
Resource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0;
|
||||
}
|
||||
catch(ex) {}
|
||||
|
||||
this._log.trace(channel.requestMethod + " " + channel.URI.spec);
|
||||
this._data = '';
|
||||
this.delayAbort();
|
||||
},
|
||||
|
||||
onStopRequest: function Channel_onStopRequest(channel, context, status) {
|
||||
// Clear the abort timer now that the channel is done
|
||||
this.abortTimer.clear();
|
||||
|
||||
if (this._data == '')
|
||||
this._data = null;
|
||||
|
||||
// Throw the failure code name (and stop execution)
|
||||
if (!Components.isSuccessCode(status))
|
||||
this._onComplete.throw(Error(Components.Exception("", status).name));
|
||||
|
||||
this._onComplete(this._data);
|
||||
},
|
||||
|
||||
onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {
|
||||
let siStream = Cc["@mozilla.org/scriptableinputstream;1"].
|
||||
createInstance(Ci.nsIScriptableInputStream);
|
||||
siStream.init(stream);
|
||||
|
||||
this._data += siStream.read(count);
|
||||
this._onProgress();
|
||||
this.delayAbort();
|
||||
},
|
||||
|
||||
/**
|
||||
* Create or push back the abort timer that kills this request
|
||||
*/
|
||||
delayAbort: function delayAbort() {
|
||||
Utils.delay(this.abortRequest, this.ABORT_TIMEOUT, this, "abortTimer");
|
||||
},
|
||||
|
||||
abortRequest: function abortRequest() {
|
||||
// Ignore any callbacks if we happen to get any now
|
||||
this.onStopRequest = function() {};
|
||||
this.onDataAvailable = function() {};
|
||||
this._onComplete.throw(Error("Aborting due to channel inactivity."));
|
||||
}
|
||||
};
|
||||
|
||||
// = BadCertListener =
|
||||
//
|
||||
// We use this listener to ignore bad HTTPS
|
||||
// certificates and continue a request on a network
|
||||
// channel. Probably not a very smart thing to do,
|
||||
// but greatly simplifies debugging and is just very
|
||||
// convenient.
|
||||
function BadCertListener() {
|
||||
}
|
||||
BadCertListener.prototype = {
|
||||
getInterface: function(aIID) {
|
||||
return this.QueryInterface(aIID);
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Components.interfaces.nsIBadCertListener2) ||
|
||||
aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
|
||||
aIID.equals(Components.interfaces.nsISupports))
|
||||
return this;
|
||||
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
},
|
||||
|
||||
notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) {
|
||||
// Silently ignore?
|
||||
let log = Log4Moz.repository.getLogger("Service.CertListener");
|
||||
log.level =
|
||||
Log4Moz.Level[Utils.prefs.getCharPref("log.logger.network.resources")];
|
||||
log.debug("Invalid HTTPS certificate encountered, ignoring!");
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
1646
services/sync/modules/service.js
Normal file
1646
services/sync/modules/service.js
Normal file
File diff suppressed because it is too large
Load Diff
89
services/sync/modules/status.js
Normal file
89
services/sync/modules/status.js
Normal file
@ -0,0 +1,89 @@
|
||||
/***************************** BEGIN LICENSE BLOCK *****************************
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with the
|
||||
* License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
||||
* the specific language governing rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is Weave Status.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla Corporation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009 the Initial
|
||||
* Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Edward Lee <edilee@mozilla.com> (original author)
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of either
|
||||
* the GNU General Public License Version 2 or later (the "GPL"), or the GNU
|
||||
* Lesser General Public License Version 2.1 or later (the "LGPL"), in which case
|
||||
* the provisions of the GPL or the LGPL are applicable instead of those above.
|
||||
* If you wish to allow use of your version of this file only under the terms of
|
||||
* either the GPL or the LGPL, and not to allow others to use your version of
|
||||
* this file under the terms of the MPL, indicate your decision by deleting the
|
||||
* provisions above and replace them with the notice and other provisions
|
||||
* required by the GPL or the LGPL. If you do not delete the provisions above, a
|
||||
* recipient may use your version of this file under the terms of any one of the
|
||||
* MPL, the GPL or the LGPL.
|
||||
*
|
||||
****************************** END LICENSE BLOCK ******************************/
|
||||
|
||||
const EXPORTED_SYMBOLS = ["Status"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
|
||||
let Status = {
|
||||
get login() this._login,
|
||||
set login(code) {
|
||||
this._login = code;
|
||||
|
||||
if (code == LOGIN_FAILED_NO_USERNAME ||
|
||||
code == LOGIN_FAILED_NO_PASSWORD ||
|
||||
code == LOGIN_FAILED_NO_PASSPHRASE)
|
||||
this.service = CLIENT_NOT_CONFIGURED;
|
||||
else if (code != LOGIN_SUCCEEDED)
|
||||
this.service = LOGIN_FAILED;
|
||||
else
|
||||
this.service = STATUS_OK;
|
||||
},
|
||||
|
||||
get sync() this._sync,
|
||||
set sync(code) {
|
||||
this._sync = code;
|
||||
this.service = code == SYNC_SUCCEEDED ? STATUS_OK : SYNC_FAILED;
|
||||
},
|
||||
|
||||
get engines() this._engines,
|
||||
set engines([name, code]) {
|
||||
this._engines[name] = code;
|
||||
|
||||
if (code != ENGINE_SUCCEEDED)
|
||||
this.service = SYNC_FAILED_PARTIAL;
|
||||
},
|
||||
|
||||
resetBackoff: function resetBackoff() {
|
||||
this.enforceBackoff = false;
|
||||
this.backoffInterval = 0;
|
||||
this.minimumNextSync = 0;
|
||||
},
|
||||
resetSync: function resetSync() {
|
||||
this.service = STATUS_OK;
|
||||
this._login = LOGIN_SUCCEEDED;
|
||||
this._sync = SYNC_SUCCEEDED;
|
||||
this._engines = {};
|
||||
this.partial = false;
|
||||
},
|
||||
};
|
||||
|
||||
// Initialize various status values
|
||||
Status.resetBackoff();
|
||||
Status.resetSync();
|
104
services/sync/modules/stores.js
Normal file
104
services/sync/modules/stores.js
Normal file
@ -0,0 +1,104 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["Store"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
/*
|
||||
* Data Stores
|
||||
* These can wrap, serialize items and apply commands
|
||||
*/
|
||||
|
||||
function Store(name) {
|
||||
name = name || "Unnamed";
|
||||
this.name = name.toLowerCase();
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Store." + name);
|
||||
let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
|
||||
this._log.level = Log4Moz.Level[level];
|
||||
}
|
||||
Store.prototype = {
|
||||
applyIncoming: function Store_applyIncoming(record) {
|
||||
if (record.deleted)
|
||||
this.remove(record);
|
||||
else if (!this.itemExists(record.id))
|
||||
this.create(record);
|
||||
else
|
||||
this.update(record);
|
||||
},
|
||||
|
||||
// override these in derived objects
|
||||
|
||||
create: function Store_create(record) {
|
||||
throw "override create in a subclass";
|
||||
},
|
||||
|
||||
remove: function Store_remove(record) {
|
||||
throw "override remove in a subclass";
|
||||
},
|
||||
|
||||
update: function Store_update(record) {
|
||||
throw "override update in a subclass";
|
||||
},
|
||||
|
||||
itemExists: function Store_itemExists(id) {
|
||||
throw "override itemExists in a subclass";
|
||||
},
|
||||
|
||||
createRecord: function Store_createRecord(id) {
|
||||
throw "override createRecord in a subclass";
|
||||
},
|
||||
|
||||
changeItemID: function Store_changeItemID(oldID, newID) {
|
||||
throw "override changeItemID in a subclass";
|
||||
},
|
||||
|
||||
getAllIDs: function Store_getAllIDs() {
|
||||
throw "override getAllIDs in a subclass";
|
||||
},
|
||||
|
||||
wipe: function Store_wipe() {
|
||||
throw "override wipe in a subclass";
|
||||
}
|
||||
};
|
167
services/sync/modules/trackers.js
Normal file
167
services/sync/modules/trackers.js
Normal file
@ -0,0 +1,167 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Tracker'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
/*
|
||||
* Trackers are associated with a single engine and deal with
|
||||
* listening for changes to their particular data type.
|
||||
*
|
||||
* There are two things they keep track of:
|
||||
* 1) A score, indicating how urgently the engine wants to sync
|
||||
* 2) A list of IDs for all the changed items that need to be synced
|
||||
* and updating their 'score', indicating how urgently they
|
||||
* want to sync.
|
||||
*
|
||||
*/
|
||||
function Tracker(name) {
|
||||
name = name || "Unnamed";
|
||||
this.name = this.file = name.toLowerCase();
|
||||
|
||||
this._log = Log4Moz.repository.getLogger("Tracker." + name);
|
||||
let level = Svc.Prefs.get("log.logger.engine." + this.name, "Debug");
|
||||
this._log.level = Log4Moz.Level[level];
|
||||
|
||||
this._score = 0;
|
||||
this._ignored = [];
|
||||
this.ignoreAll = false;
|
||||
this.changedIDs = {};
|
||||
this.loadChangedIDs();
|
||||
}
|
||||
Tracker.prototype = {
|
||||
/*
|
||||
* Score can be called as often as desired to decide which engines to sync
|
||||
*
|
||||
* Valid values for score:
|
||||
* -1: Do not sync unless the user specifically requests it (almost disabled)
|
||||
* 0: Nothing has changed
|
||||
* 100: Please sync me ASAP!
|
||||
*
|
||||
* Setting it to other values should (but doesn't currently) throw an exception
|
||||
*/
|
||||
get score() {
|
||||
return this._score;
|
||||
},
|
||||
|
||||
set score(value) {
|
||||
this._score = value;
|
||||
Observers.notify("weave:engine:score:updated", this.name);
|
||||
},
|
||||
|
||||
// Should be called by service everytime a sync has been done for an engine
|
||||
resetScore: function T_resetScore() {
|
||||
this._score = 0;
|
||||
},
|
||||
|
||||
saveChangedIDs: function T_saveChangedIDs() {
|
||||
Utils.delay(function() {
|
||||
Utils.jsonSave("changes/" + this.file, this, this.changedIDs);
|
||||
}, 1000, this, "_lazySave");
|
||||
},
|
||||
|
||||
loadChangedIDs: function T_loadChangedIDs() {
|
||||
Utils.jsonLoad("changes/" + this.file, this, function(json) {
|
||||
this.changedIDs = json;
|
||||
});
|
||||
},
|
||||
|
||||
// ignore/unignore specific IDs. Useful for ignoring items that are
|
||||
// being processed, or that shouldn't be synced.
|
||||
// But note: not persisted to disk
|
||||
|
||||
ignoreID: function T_ignoreID(id) {
|
||||
this.unignoreID(id);
|
||||
this._ignored.push(id);
|
||||
},
|
||||
|
||||
unignoreID: function T_unignoreID(id) {
|
||||
let index = this._ignored.indexOf(id);
|
||||
if (index != -1)
|
||||
this._ignored.splice(index, 1);
|
||||
},
|
||||
|
||||
addChangedID: function addChangedID(id, when) {
|
||||
if (!id) {
|
||||
this._log.warn("Attempted to add undefined ID to tracker");
|
||||
return false;
|
||||
}
|
||||
if (this.ignoreAll || (id in this._ignored))
|
||||
return false;
|
||||
|
||||
// Default to the current time in seconds if no time is provided
|
||||
if (when == null)
|
||||
when = Math.floor(Date.now() / 1000);
|
||||
|
||||
// Add/update the entry if we have a newer time
|
||||
if ((this.changedIDs[id] || -Infinity) < when) {
|
||||
this._log.trace("Adding changed ID: " + [id, when]);
|
||||
this.changedIDs[id] = when;
|
||||
this.saveChangedIDs();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
removeChangedID: function T_removeChangedID(id) {
|
||||
if (!id) {
|
||||
this._log.warn("Attempted to remove undefined ID to tracker");
|
||||
return false;
|
||||
}
|
||||
if (this.ignoreAll || (id in this._ignored))
|
||||
return false;
|
||||
if (this.changedIDs[id] != null) {
|
||||
this._log.trace("Removing changed ID " + id);
|
||||
delete this.changedIDs[id];
|
||||
this.saveChangedIDs();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
clearChangedIDs: function T_clearChangedIDs() {
|
||||
this._log.trace("Clearing changed ID list");
|
||||
this.changedIDs = {};
|
||||
this.saveChangedIDs();
|
||||
}
|
||||
};
|
150
services/sync/modules/type_records/bookmark.js
Normal file
150
services/sync/modules/type_records/bookmark.js
Normal file
@ -0,0 +1,150 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2008
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["PlacesItem", "Bookmark", "BookmarkFolder",
|
||||
"BookmarkMicsum", "BookmarkQuery", "Livemark", "BookmarkSeparator"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PlacesItem(uri, type) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
this.type = type || "item";
|
||||
}
|
||||
PlacesItem.prototype = {
|
||||
decrypt: function PlacesItem_decrypt(passphrase) {
|
||||
// Do the normal CryptoWrapper decrypt, but change types before returning
|
||||
let clear = CryptoWrapper.prototype.decrypt.apply(this, arguments);
|
||||
|
||||
// Convert the abstract places item to the actual object type
|
||||
if (!this.deleted)
|
||||
this.__proto__ = this.getTypeObject(this.type).prototype;
|
||||
|
||||
return clear;
|
||||
},
|
||||
|
||||
getTypeObject: function PlacesItem_getTypeObject(type) {
|
||||
switch (type) {
|
||||
case "bookmark":
|
||||
return Bookmark;
|
||||
case "microsummary":
|
||||
return BookmarkMicsum;
|
||||
case "query":
|
||||
return BookmarkQuery;
|
||||
case "folder":
|
||||
return BookmarkFolder;
|
||||
case "livemark":
|
||||
return Livemark;
|
||||
case "separator":
|
||||
return BookmarkSeparator;
|
||||
case "item":
|
||||
return PlacesItem;
|
||||
}
|
||||
throw "Unknown places item object type: " + type;
|
||||
},
|
||||
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.PlacesItem",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PlacesItem, "cleartext", ["hasDupe", "parentid", "parentName",
|
||||
"predecessorid", "type"]);
|
||||
|
||||
function Bookmark(uri, type) {
|
||||
PlacesItem.call(this, uri, type || "bookmark");
|
||||
}
|
||||
Bookmark.prototype = {
|
||||
__proto__: PlacesItem.prototype,
|
||||
_logName: "Record.Bookmark",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(Bookmark, "cleartext", ["title", "bmkUri", "description",
|
||||
"loadInSidebar", "tags", "keyword"]);
|
||||
|
||||
function BookmarkMicsum(uri) {
|
||||
Bookmark.call(this, uri, "microsummary");
|
||||
}
|
||||
BookmarkMicsum.prototype = {
|
||||
__proto__: Bookmark.prototype,
|
||||
_logName: "Record.BookmarkMicsum",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(BookmarkMicsum, "cleartext", ["generatorUri", "staticTitle"]);
|
||||
|
||||
function BookmarkQuery(uri) {
|
||||
Bookmark.call(this, uri, "query");
|
||||
}
|
||||
BookmarkQuery.prototype = {
|
||||
__proto__: Bookmark.prototype,
|
||||
_logName: "Record.BookmarkQuery",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName"]);
|
||||
|
||||
function BookmarkFolder(uri, type) {
|
||||
PlacesItem.call(this, uri, type || "folder");
|
||||
}
|
||||
BookmarkFolder.prototype = {
|
||||
__proto__: PlacesItem.prototype,
|
||||
_logName: "Record.Folder",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(BookmarkFolder, "cleartext", ["description", "title"]);
|
||||
|
||||
function Livemark(uri) {
|
||||
BookmarkFolder.call(this, uri, "livemark");
|
||||
}
|
||||
Livemark.prototype = {
|
||||
__proto__: BookmarkFolder.prototype,
|
||||
_logName: "Record.Livemark",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);
|
||||
|
||||
function BookmarkSeparator(uri) {
|
||||
PlacesItem.call(this, uri, "separator");
|
||||
}
|
||||
BookmarkSeparator.prototype = {
|
||||
__proto__: PlacesItem.prototype,
|
||||
_logName: "Record.Separator",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos");
|
55
services/sync/modules/type_records/clients.js
Normal file
55
services/sync/modules/type_records/clients.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["ClientsRec"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function ClientsRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
}
|
||||
ClientsRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Clients",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(ClientsRec, "cleartext", ["name", "type", "commands"]);
|
55
services/sync/modules/type_records/forms.js
Normal file
55
services/sync/modules/type_records/forms.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['FormRec'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function FormRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
}
|
||||
FormRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Form",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);
|
55
services/sync/modules/type_records/history.js
Normal file
55
services/sync/modules/type_records/history.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['HistoryRec'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function HistoryRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
}
|
||||
HistoryRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.History",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(HistoryRec, "cleartext", ["histUri", "title", "visits"]);
|
56
services/sync/modules/type_records/passwords.js
Normal file
56
services/sync/modules/type_records/passwords.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['LoginRec'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function LoginRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
}
|
||||
LoginRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Login",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(LoginRec, "cleartext", ["hostname", "formSubmitURL",
|
||||
"httpRealm", "username", "password", "usernameField", "passwordField"]);
|
55
services/sync/modules/type_records/prefs.js
Normal file
55
services/sync/modules/type_records/prefs.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['PrefRec'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function PrefRec(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
}
|
||||
PrefRec.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Pref",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(PrefRec, "cleartext", ["type", "value"]);
|
55
services/sync/modules/type_records/tabs.js
Normal file
55
services/sync/modules/type_records/tabs.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Weave.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2009
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Jono DiCarlo <jdicarlo@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['TabSetRecord'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function TabSetRecord(uri) {
|
||||
CryptoWrapper.call(this, uri);
|
||||
}
|
||||
TabSetRecord.prototype = {
|
||||
__proto__: CryptoWrapper.prototype,
|
||||
_logName: "Record.Tabs",
|
||||
};
|
||||
|
||||
Utils.deferGetSet(TabSetRecord, "cleartext", ["clientName", "tabs"]);
|
887
services/sync/modules/util.js
Normal file
887
services/sync/modules/util.js
Normal file
@ -0,0 +1,887 @@
|
||||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Bookmarks Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Mozilla.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2007
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ['Utils', 'Svc', 'Str'];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/ext/Preferences.js");
|
||||
Cu.import("resource://services-sync/ext/StringBundle.js");
|
||||
Cu.import("resource://services-sync/ext/Sync.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
|
||||
/*
|
||||
* Utility functions
|
||||
*/
|
||||
|
||||
let Utils = {
|
||||
/**
|
||||
* Wrap a function to catch all exceptions and log them
|
||||
*
|
||||
* @usage MyObj._catch = Utils.catch;
|
||||
* MyObj.foo = function() { this._catch(func)(); }
|
||||
*/
|
||||
catch: function Utils_catch(func) {
|
||||
let thisArg = this;
|
||||
return function WrappedCatch() {
|
||||
try {
|
||||
return func.call(thisArg);
|
||||
}
|
||||
catch(ex) {
|
||||
thisArg._log.debug("Exception: " + Utils.exceptionStr(ex));
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrap a function to call lock before calling the function then unlock.
|
||||
*
|
||||
* @usage MyObj._lock = Utils.lock;
|
||||
* MyObj.foo = function() { this._lock(func)(); }
|
||||
*/
|
||||
lock: function Utils_lock(func) {
|
||||
let thisArg = this;
|
||||
return function WrappedLock() {
|
||||
if (!thisArg.lock())
|
||||
throw "Could not acquire lock";
|
||||
|
||||
try {
|
||||
return func.call(thisArg);
|
||||
}
|
||||
finally {
|
||||
thisArg.unlock();
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrap functions to notify when it starts and finishes executing or if it got
|
||||
* an error. The message is a combination of a provided prefix and local name
|
||||
* with the current state and the subject is the provided subject.
|
||||
*
|
||||
* @usage function MyObj() { this._notify = Utils.notify("prefix:"); }
|
||||
* MyObj.foo = function() { this._notify(name, subject, func)(); }
|
||||
*/
|
||||
notify: function Utils_notify(prefix) {
|
||||
return function NotifyMaker(name, subject, func) {
|
||||
let thisArg = this;
|
||||
let notify = function(state) {
|
||||
let mesg = prefix + name + ":" + state;
|
||||
thisArg._log.trace("Event: " + mesg);
|
||||
Observers.notify(mesg, subject);
|
||||
};
|
||||
|
||||
return function WrappedNotify() {
|
||||
try {
|
||||
notify("start");
|
||||
let ret = func.call(thisArg);
|
||||
notify("finish");
|
||||
return ret;
|
||||
}
|
||||
catch(ex) {
|
||||
notify("error");
|
||||
throw ex;
|
||||
}
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
batchSync: function batchSync(service, engineType) {
|
||||
return function batchedSync() {
|
||||
let engine = this;
|
||||
let batchEx = null;
|
||||
|
||||
// Try running sync in batch mode
|
||||
Svc[service].runInBatchMode({
|
||||
runBatched: function wrappedSync() {
|
||||
try {
|
||||
engineType.prototype._sync.call(engine);
|
||||
}
|
||||
catch(ex) {
|
||||
batchEx = ex;
|
||||
}
|
||||
}
|
||||
}, null);
|
||||
|
||||
// Expose the exception if something inside the batch failed
|
||||
if (batchEx!= null)
|
||||
throw batchEx;
|
||||
};
|
||||
},
|
||||
|
||||
queryAsync: function(query, names) {
|
||||
// Allow array of names, single name, and no name
|
||||
if (!Utils.isArray(names))
|
||||
names = names == null ? [] : [names];
|
||||
|
||||
// Synchronously asyncExecute fetching all results by name
|
||||
let [exec, execCb] = Sync.withCb(query.executeAsync, query);
|
||||
return exec({
|
||||
items: [],
|
||||
handleResult: function handleResult(results) {
|
||||
let row;
|
||||
while ((row = results.getNextRow()) != null) {
|
||||
this.items.push(names.reduce(function(item, name) {
|
||||
item[name] = row.getResultByName(name);
|
||||
return item;
|
||||
}, {}));
|
||||
}
|
||||
},
|
||||
handleError: function handleError(error) {
|
||||
execCb.throw(error);
|
||||
},
|
||||
handleCompletion: function handleCompletion(reason) {
|
||||
execCb(this.items);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Generates a brand-new globally unique identifier (GUID).
|
||||
makeGUID: function makeGUID() {
|
||||
// 70 characters that are not-escaped URL-friendly
|
||||
const code =
|
||||
"!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
|
||||
|
||||
let guid = "";
|
||||
let num = 0;
|
||||
let val;
|
||||
|
||||
// Generate ten 70-value characters for a 70^10 (~61.29-bit) GUID
|
||||
for (let i = 0; i < 10; i++) {
|
||||
// Refresh the number source after using it a few times
|
||||
if (i == 0 || i == 5)
|
||||
num = Math.random();
|
||||
|
||||
// Figure out which code to use for the next GUID character
|
||||
num *= 70;
|
||||
val = Math.floor(num);
|
||||
guid += code[val];
|
||||
num -= val;
|
||||
}
|
||||
|
||||
return guid;
|
||||
},
|
||||
|
||||
anno: function anno(id, anno, val, expire) {
|
||||
// Figure out if we have a bookmark or page
|
||||
let annoFunc = (typeof id == "number" ? "Item" : "Page") + "Annotation";
|
||||
|
||||
// Convert to a nsIURI if necessary
|
||||
if (typeof id == "string")
|
||||
id = Utils.makeURI(id);
|
||||
|
||||
if (id == null)
|
||||
throw "Null id for anno! (invalid uri)";
|
||||
|
||||
switch (arguments.length) {
|
||||
case 2:
|
||||
// Get the annotation with 2 args
|
||||
return Svc.Annos["get" + annoFunc](id, anno);
|
||||
case 3:
|
||||
expire = "NEVER";
|
||||
// Fallthrough!
|
||||
case 4:
|
||||
// Convert to actual EXPIRE value
|
||||
expire = Svc.Annos["EXPIRE_" + expire];
|
||||
|
||||
// Set the annotation with 3 or 4 args
|
||||
return Svc.Annos["set" + annoFunc](id, anno, val, 0, expire);
|
||||
}
|
||||
},
|
||||
|
||||
ensureOneOpen: let (windows = {}) function ensureOneOpen(window) {
|
||||
// Close the other window if it exists
|
||||
let url = window.location.href;
|
||||
let other = windows[url];
|
||||
if (other != null)
|
||||
other.close();
|
||||
|
||||
// Save the new window for future closure
|
||||
windows[url] = window;
|
||||
|
||||
// Actively clean up when the window is closed
|
||||
window.addEventListener("unload", function() windows[url] = null, false);
|
||||
},
|
||||
|
||||
// Returns a nsILocalFile representing a file relative to the
|
||||
// current user's profile directory. If the argument is a string,
|
||||
// it should be a string with unix-style slashes for directory names
|
||||
// (these slashes are automatically converted to platform-specific
|
||||
// path separators).
|
||||
//
|
||||
// Alternatively, if the argument is an object, it should contain
|
||||
// the following attributes:
|
||||
//
|
||||
// path: the path to the file, relative to the current user's
|
||||
// profile dir.
|
||||
//
|
||||
// autoCreate: whether or not the file should be created if it
|
||||
// doesn't already exist.
|
||||
getProfileFile: function getProfileFile(arg) {
|
||||
if (typeof arg == "string")
|
||||
arg = {path: arg};
|
||||
|
||||
let pathParts = arg.path.split("/");
|
||||
let file = Svc.Directory.get("ProfD", Ci.nsIFile);
|
||||
file.QueryInterface(Ci.nsILocalFile);
|
||||
for (let i = 0; i < pathParts.length; i++)
|
||||
file.append(pathParts[i]);
|
||||
if (arg.autoCreate && !file.exists())
|
||||
file.create(file.NORMAL_FILE_TYPE, PERMS_FILE);
|
||||
return file;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a simple getter/setter to an object that defers access of a property
|
||||
* to an inner property.
|
||||
*
|
||||
* @param obj
|
||||
* Object to add properties to defer in its prototype
|
||||
* @param defer
|
||||
* Hash property of obj to defer to (dot split each level)
|
||||
* @param prop
|
||||
* Property name to defer (or an array of property names)
|
||||
*/
|
||||
deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
|
||||
if (Utils.isArray(prop))
|
||||
return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop));
|
||||
|
||||
// Split the defer into each dot part for each level to dereference
|
||||
let parts = defer.split(".");
|
||||
let deref = function(base) Utils.deref(base, parts);
|
||||
|
||||
let prot = obj.prototype;
|
||||
|
||||
// Create a getter if it doesn't exist yet
|
||||
if (!prot.__lookupGetter__(prop))
|
||||
prot.__defineGetter__(prop, function() deref(this)[prop]);
|
||||
|
||||
// Create a setter if it doesn't exist yet
|
||||
if (!prot.__lookupSetter__(prop))
|
||||
prot.__defineSetter__(prop, function(val) deref(this)[prop] = val);
|
||||
},
|
||||
|
||||
/**
|
||||
* Dereference an array of properties starting from a base object
|
||||
*
|
||||
* @param base
|
||||
* Base object to start dereferencing
|
||||
* @param props
|
||||
* Array of properties to dereference (one for each level)
|
||||
*/
|
||||
deref: function Utils_deref(base, props) props.reduce(function(curr, prop)
|
||||
curr[prop], base),
|
||||
|
||||
/**
|
||||
* Determine if some value is an array
|
||||
*
|
||||
* @param val
|
||||
* Value to check (can be null, undefined, etc.)
|
||||
* @return True if it's an array; false otherwise
|
||||
*/
|
||||
isArray: function Utils_isArray(val) val != null && typeof val == "object" &&
|
||||
val.constructor.name == "Array",
|
||||
|
||||
// lazy load objects from a constructor on first access. It will
|
||||
// work with the global object ('this' in the global context).
|
||||
lazy: function Weave_lazy(dest, prop, ctr) {
|
||||
delete dest[prop];
|
||||
dest.__defineGetter__(prop, Utils.lazyCb(dest, prop, ctr));
|
||||
},
|
||||
lazyCb: function Weave_lazyCb(dest, prop, ctr) {
|
||||
return function() {
|
||||
delete dest[prop];
|
||||
dest[prop] = new ctr();
|
||||
return dest[prop];
|
||||
};
|
||||
},
|
||||
|
||||
// like lazy, but rather than new'ing the 3rd arg we use its return value
|
||||
lazy2: function Weave_lazy2(dest, prop, fn) {
|
||||
delete dest[prop];
|
||||
dest.__defineGetter__(prop, Utils.lazyCb2(dest, prop, fn));
|
||||
},
|
||||
lazyCb2: function Weave_lazyCb2(dest, prop, fn) {
|
||||
return function() {
|
||||
delete dest[prop];
|
||||
return dest[prop] = fn();
|
||||
};
|
||||
},
|
||||
|
||||
lazySvc: function Weave_lazySvc(dest, prop, cid, iface) {
|
||||
let getter = function() {
|
||||
delete dest[prop];
|
||||
let svc = null;
|
||||
|
||||
// Use the platform's service if it exists
|
||||
if (cid in Cc && iface in Ci)
|
||||
svc = Cc[cid].getService(Ci[iface]);
|
||||
else {
|
||||
svc = FakeSvc[cid];
|
||||
|
||||
let log = Log4Moz.repository.getLogger("Service.Util");
|
||||
if (svc == null)
|
||||
log.warn("Component " + cid + " doesn't exist on this platform.");
|
||||
else
|
||||
log.debug("Using a fake svc object for " + cid);
|
||||
}
|
||||
|
||||
return dest[prop] = svc;
|
||||
};
|
||||
dest.__defineGetter__(prop, getter);
|
||||
},
|
||||
|
||||
lazyStrings: function Weave_lazyStrings(name) {
|
||||
let bundle = "chrome://weave/locale/services/" + name + ".properties";
|
||||
return function() new StringBundle(bundle);
|
||||
},
|
||||
|
||||
deepEquals: function eq(a, b) {
|
||||
// If they're triple equals, then it must be equals!
|
||||
if (a === b)
|
||||
return true;
|
||||
|
||||
// If they weren't equal, they must be objects to be different
|
||||
if (typeof a != "object" || typeof b != "object")
|
||||
return false;
|
||||
|
||||
// But null objects won't have properties to compare
|
||||
if (a === null || b === null)
|
||||
return false;
|
||||
|
||||
// Make sure all of a's keys have a matching value in b
|
||||
for (let k in a)
|
||||
if (!eq(a[k], b[k]))
|
||||
return false;
|
||||
|
||||
// Do the same for b's keys but skip those that we already checked
|
||||
for (let k in b)
|
||||
if (!(k in a) && !eq(a[k], b[k]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
deepCopy: function Weave_deepCopy(thing, noSort) {
|
||||
if (typeof(thing) != "object" || thing == null)
|
||||
return thing;
|
||||
let ret;
|
||||
|
||||
if (Utils.isArray(thing)) {
|
||||
ret = [];
|
||||
for (let i = 0; i < thing.length; i++)
|
||||
ret.push(Utils.deepCopy(thing[i], noSort));
|
||||
|
||||
} else {
|
||||
ret = {};
|
||||
let props = [p for (p in thing)];
|
||||
if (!noSort)
|
||||
props = props.sort();
|
||||
props.forEach(function(k) ret[k] = Utils.deepCopy(thing[k], noSort));
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
// Works on frames or exceptions, munges file:// URIs to shorten the paths
|
||||
// FIXME: filename munging is sort of hackish, might be confusing if
|
||||
// there are multiple extensions with similar filenames
|
||||
formatFrame: function Utils_formatFrame(frame) {
|
||||
let tmp = "<file:unknown>";
|
||||
|
||||
let file = frame.filename || frame.fileName;
|
||||
if (file)
|
||||
tmp = file.replace(/^(?:chrome|file):.*?([^\/\.]+\.\w+)$/, "$1");
|
||||
|
||||
if (frame.lineNumber)
|
||||
tmp += ":" + frame.lineNumber;
|
||||
if (frame.name)
|
||||
tmp = frame.name + "()@" + tmp;
|
||||
|
||||
return tmp;
|
||||
},
|
||||
|
||||
exceptionStr: function Weave_exceptionStr(e) {
|
||||
let message = e.message ? e.message : e;
|
||||
return message + " " + Utils.stackTrace(e);
|
||||
},
|
||||
|
||||
stackTraceFromFrame: function Weave_stackTraceFromFrame(frame) {
|
||||
let output = [];
|
||||
while (frame) {
|
||||
let str = Utils.formatFrame(frame);
|
||||
if (str)
|
||||
output.push(str);
|
||||
frame = frame.caller;
|
||||
}
|
||||
return output.join(" < ");
|
||||
},
|
||||
|
||||
stackTrace: function Weave_stackTrace(e) {
|
||||
// Wrapped nsIException
|
||||
if (e.location)
|
||||
return "Stack trace: " + Utils.stackTraceFromFrame(e.location);
|
||||
|
||||
// Standard JS exception
|
||||
if (e.stack)
|
||||
return "JS Stack trace: " + e.stack.trim().replace(/\n/g, " < ").
|
||||
replace(/@[^@]*?([^\/\.]+\.\w+:)/g, "@$1");
|
||||
|
||||
return "No traceback available";
|
||||
},
|
||||
|
||||
checkStatus: function Weave_checkStatus(code, msg, ranges) {
|
||||
if (!ranges)
|
||||
ranges = [[200,300]];
|
||||
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
var rng = ranges[i];
|
||||
if (typeof(rng) == "object" && code >= rng[0] && code < rng[1])
|
||||
return true;
|
||||
else if (typeof(rng) == "number" && code == rng) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (msg) {
|
||||
let log = Log4Moz.repository.getLogger("Service.Util");
|
||||
log.error(msg + " Error code: " + code);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
ensureStatus: function Weave_ensureStatus(args) {
|
||||
if (!Utils.checkStatus.apply(Utils, arguments))
|
||||
throw 'checkStatus failed';
|
||||
},
|
||||
|
||||
digest: function digest(message, hasher) {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
let data = converter.convertToByteArray(message, {});
|
||||
hasher.update(data, data.length);
|
||||
|
||||
// Convert each hashed byte into 2-hex strings then combine them
|
||||
return [("0" + byte.charCodeAt().toString(16)).slice(-2) for each (byte in
|
||||
hasher.finish(false))].join("");
|
||||
},
|
||||
|
||||
sha1: function sha1(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
return Utils.digest(message, hasher);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a sha256 HMAC for a string message and a given nsIKeyObject
|
||||
*/
|
||||
sha256HMAC: function sha256HMAC(message, key) {
|
||||
let hasher = Cc["@mozilla.org/security/hmac;1"].
|
||||
createInstance(Ci.nsICryptoHMAC);
|
||||
hasher.init(hasher.SHA256, key);
|
||||
return Utils.digest(message, hasher);
|
||||
},
|
||||
|
||||
makeURI: function Weave_makeURI(URIString) {
|
||||
if (!URIString)
|
||||
return null;
|
||||
try {
|
||||
return Svc.IO.newURI(URIString, null, null);
|
||||
} catch (e) {
|
||||
let log = Log4Moz.repository.getLogger("Service.Util");
|
||||
log.debug("Could not create URI: " + Utils.exceptionStr(e));
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
makeURL: function Weave_makeURL(URIString) {
|
||||
let url = Utils.makeURI(URIString);
|
||||
url.QueryInterface(Ci.nsIURL);
|
||||
return url;
|
||||
},
|
||||
|
||||
xpath: function Weave_xpath(xmlDoc, xpathString) {
|
||||
let root = xmlDoc.ownerDocument == null ?
|
||||
xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement;
|
||||
let nsResolver = xmlDoc.createNSResolver(root);
|
||||
|
||||
return xmlDoc.evaluate(xpathString, xmlDoc, nsResolver,
|
||||
Ci.nsIDOMXPathResult.ANY_TYPE, null);
|
||||
},
|
||||
|
||||
getTmp: function Weave_getTmp(name) {
|
||||
let tmp = Svc.Directory.get("ProfD", Ci.nsIFile);
|
||||
tmp.QueryInterface(Ci.nsILocalFile);
|
||||
|
||||
tmp.append("weave");
|
||||
tmp.append("tmp");
|
||||
if (!tmp.exists())
|
||||
tmp.create(tmp.DIRECTORY_TYPE, PERMS_DIRECTORY);
|
||||
|
||||
if (name)
|
||||
tmp.append(name);
|
||||
|
||||
return tmp;
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a json object from disk
|
||||
*
|
||||
* @param filePath
|
||||
* Json file path load from weave/[filePath].json
|
||||
* @param that
|
||||
* Object to use for logging and "this" for callback
|
||||
* @param callback
|
||||
* Function to process json object as its first parameter
|
||||
*/
|
||||
jsonLoad: function Utils_jsonLoad(filePath, that, callback) {
|
||||
filePath = "weave/" + filePath + ".json";
|
||||
if (that._log)
|
||||
that._log.trace("Loading json from disk: " + filePath);
|
||||
|
||||
let file = Utils.getProfileFile(filePath);
|
||||
if (!file.exists())
|
||||
return;
|
||||
|
||||
try {
|
||||
let [is] = Utils.open(file, "<");
|
||||
let json = Utils.readStream(is);
|
||||
is.close();
|
||||
callback.call(that, JSON.parse(json));
|
||||
}
|
||||
catch (ex) {
|
||||
if (that._log)
|
||||
that._log.debug("Failed to load json: " + Utils.exceptionStr(ex));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save a json-able object to disk
|
||||
*
|
||||
* @param filePath
|
||||
* Json file path save to weave/[filePath].json
|
||||
* @param that
|
||||
* Object to use for logging and "this" for callback
|
||||
* @param callback
|
||||
* Function to provide json-able object to save. If this isn't a
|
||||
* function, it'll be used as the object to make a json string.
|
||||
*/
|
||||
jsonSave: function Utils_jsonSave(filePath, that, callback) {
|
||||
filePath = "weave/" + filePath + ".json";
|
||||
if (that._log)
|
||||
that._log.trace("Saving json to disk: " + filePath);
|
||||
|
||||
let file = Utils.getProfileFile({ autoCreate: true, path: filePath });
|
||||
let json = typeof callback == "function" ? callback.call(that) : callback;
|
||||
let out = JSON.stringify(json);
|
||||
let [fos] = Utils.open(file, ">");
|
||||
fos.writeString(out);
|
||||
fos.close();
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a timer that is scheduled to call the callback after waiting the
|
||||
* provided time or as soon as possible. The timer will be set as a property
|
||||
* of the provided object with the given timer name.
|
||||
*/
|
||||
delay: function delay(callback, wait, thisObj, name) {
|
||||
// Default to running right away
|
||||
wait = wait || 0;
|
||||
|
||||
// Use a dummy object if one wasn't provided
|
||||
thisObj = thisObj || {};
|
||||
|
||||
// Delay an existing timer if it exists
|
||||
if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) {
|
||||
thisObj[name].delay = wait;
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a special timer that we can add extra properties
|
||||
let timer = {};
|
||||
timer.__proto__ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
|
||||
// Provide an easy way to clear out the timer
|
||||
timer.clear = function() {
|
||||
thisObj[name] = null;
|
||||
timer.cancel();
|
||||
};
|
||||
|
||||
// Initialize the timer with a smart callback
|
||||
timer.initWithCallback({
|
||||
notify: function notify() {
|
||||
// Clear out the timer once it's been triggered
|
||||
timer.clear();
|
||||
callback.call(thisObj, timer);
|
||||
}
|
||||
}, wait, timer.TYPE_ONE_SHOT);
|
||||
|
||||
return thisObj[name] = timer;
|
||||
},
|
||||
|
||||
open: function open(pathOrFile, mode, perms) {
|
||||
let stream, file;
|
||||
|
||||
if (pathOrFile instanceof Ci.nsIFile) {
|
||||
file = pathOrFile;
|
||||
} else {
|
||||
file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
|
||||
dump("PATH IS" + pathOrFile + "\n");
|
||||
file.initWithPath(pathOrFile);
|
||||
}
|
||||
|
||||
if (!perms)
|
||||
perms = PERMS_FILE;
|
||||
|
||||
switch(mode) {
|
||||
case "<": {
|
||||
if (!file.exists())
|
||||
throw "Cannot open file for reading, file does not exist";
|
||||
let fis = Cc["@mozilla.org/network/file-input-stream;1"].
|
||||
createInstance(Ci.nsIFileInputStream);
|
||||
fis.init(file, MODE_RDONLY, perms, 0);
|
||||
stream = Cc["@mozilla.org/intl/converter-input-stream;1"].
|
||||
createInstance(Ci.nsIConverterInputStream);
|
||||
stream.init(fis, "UTF-8", 4096,
|
||||
Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
|
||||
} break;
|
||||
|
||||
case ">": {
|
||||
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Ci.nsIFileOutputStream);
|
||||
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, perms, 0);
|
||||
stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Ci.nsIConverterOutputStream);
|
||||
stream.init(fos, "UTF-8", 4096, 0x0000);
|
||||
} break;
|
||||
|
||||
case ">>": {
|
||||
let fos = Cc["@mozilla.org/network/file-output-stream;1"].
|
||||
createInstance(Ci.nsIFileOutputStream);
|
||||
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_APPEND, perms, 0);
|
||||
stream = Cc["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Ci.nsIConverterOutputStream);
|
||||
stream.init(fos, "UTF-8", 4096, 0x0000);
|
||||
} break;
|
||||
|
||||
default:
|
||||
throw "Illegal mode to open(): " + mode;
|
||||
}
|
||||
|
||||
return [stream, file];
|
||||
},
|
||||
|
||||
getIcon: function(iconUri, defaultIcon) {
|
||||
try {
|
||||
let iconURI = Utils.makeURI(iconUri);
|
||||
return Svc.Favicon.getFaviconLinkForIcon(iconURI).spec;
|
||||
}
|
||||
catch(ex) {}
|
||||
|
||||
// Just give the provided default icon or the system's default
|
||||
return defaultIcon || Svc.Favicon.defaultFavicon.spec;
|
||||
},
|
||||
|
||||
getErrorString: function Utils_getErrorString(error, args) {
|
||||
try {
|
||||
return Str.errors.get(error, args || null);
|
||||
} catch (e) {}
|
||||
|
||||
// basically returns "Unknown Error"
|
||||
return Str.errors.get("error.reason.unknown");
|
||||
},
|
||||
|
||||
// assumes an nsIConverterInputStream
|
||||
readStream: function Weave_readStream(is) {
|
||||
let ret = "", str = {};
|
||||
while (is.readString(4096, str) != 0) {
|
||||
ret += str.value;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an array like the first but without elements of the second
|
||||
*/
|
||||
arraySub: function arraySub(minuend, subtrahend) {
|
||||
return minuend.filter(function(i) subtrahend.indexOf(i) == -1);
|
||||
},
|
||||
|
||||
bind2: function Async_bind2(object, method) {
|
||||
return function innerBind() { return method.apply(object, arguments); };
|
||||
},
|
||||
|
||||
mpLocked: function mpLocked() {
|
||||
let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"].
|
||||
getService(Ci.nsIPKCS11ModuleDB);
|
||||
let sdrSlot = modules.findSlotByName("");
|
||||
let status = sdrSlot.status;
|
||||
let slots = Ci.nsIPKCS11Slot;
|
||||
|
||||
if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN)
|
||||
return false;
|
||||
|
||||
if (status == slots.SLOT_NOT_LOGGED_IN)
|
||||
return true;
|
||||
|
||||
// something wacky happened, pretend MP is locked
|
||||
return true;
|
||||
},
|
||||
|
||||
__prefs: null,
|
||||
get prefs() {
|
||||
if (!this.__prefs) {
|
||||
this.__prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefService);
|
||||
this.__prefs = this.__prefs.getBranch(PREFS_BRANCH);
|
||||
this.__prefs.QueryInterface(Ci.nsIPrefBranch2);
|
||||
}
|
||||
return this.__prefs;
|
||||
}
|
||||
};
|
||||
|
||||
let FakeSvc = {
|
||||
// Private Browsing
|
||||
"@mozilla.org/privatebrowsing;1": {
|
||||
autoStarted: false,
|
||||
privateBrowsingEnabled: false
|
||||
},
|
||||
// Session Restore
|
||||
"@mozilla.org/browser/sessionstore;1": {
|
||||
setTabValue: function(tab, key, value) {
|
||||
if (!tab.__SS_extdata)
|
||||
tab.__SS_extdata = {};
|
||||
tab.__SS_extData[key] = value;
|
||||
},
|
||||
getBrowserState: function() {
|
||||
// Fennec should have only one window. Not more, not less.
|
||||
let state = { windows: [{ tabs: [] }] };
|
||||
let window = Svc.WinMediator.getMostRecentWindow("navigator:browser");
|
||||
|
||||
// Extract various pieces of tab data
|
||||
window.Browser._tabs.forEach(function(tab) {
|
||||
let tabState = { entries: [{}] };
|
||||
let browser = tab.browser;
|
||||
|
||||
// Cases when we want to skip the tab. Could come up if we get
|
||||
// state as a tab is opening or closing.
|
||||
if (!browser || !browser.currentURI || !browser.sessionHistory)
|
||||
return;
|
||||
|
||||
let history = browser.sessionHistory;
|
||||
if (history.count > 0) {
|
||||
// We're only grabbing the current history entry for now.
|
||||
let entry = history.getEntryAtIndex(history.index, false);
|
||||
tabState.entries[0].url = entry.URI.spec;
|
||||
// Like SessionStore really does it...
|
||||
if (entry.title && entry.title != entry.url)
|
||||
tabState.entries[0].title = entry.title;
|
||||
}
|
||||
// index is 1-based
|
||||
tabState.index = 1;
|
||||
|
||||
// Get the image for the tab. Fennec doesn't quite work the same
|
||||
// way as Firefox, so we'll just get this from the browser object.
|
||||
tabState.attributes = { image: browser.mIconURL };
|
||||
|
||||
// Collect the extdata
|
||||
if (tab.__SS_extdata) {
|
||||
tabState.extData = {};
|
||||
for (let key in tab.__SS_extdata)
|
||||
tabState.extData[key] = tab.__SS_extdata[key];
|
||||
}
|
||||
|
||||
// Add the tab to the window
|
||||
state.windows[0].tabs.push(tabState);
|
||||
});
|
||||
return JSON.stringify(state);
|
||||
}
|
||||
},
|
||||
// A fake service only used for testing
|
||||
"@labs.mozilla.com/Fake/Thing;1": {
|
||||
isFake: true
|
||||
}
|
||||
};
|
||||
|
||||
// Use the binary WeaveCrypto (;1) if the js-ctypes version (;2) fails to load
|
||||
// by adding an alias on FakeSvc from ;2 to ;1
|
||||
Utils.lazySvc(FakeSvc, "@labs.mozilla.com/Weave/Crypto;2",
|
||||
"@labs.mozilla.com/Weave/Crypto;1", "IWeaveCrypto");
|
||||
|
||||
/*
|
||||
* Commonly-used services
|
||||
*/
|
||||
let Svc = {};
|
||||
Svc.Prefs = new Preferences(PREFS_BRANCH);
|
||||
Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true});
|
||||
Svc.Obs = Observers;
|
||||
[["Annos", "@mozilla.org/browser/annotation-service;1", "nsIAnnotationService"],
|
||||
["AppInfo", "@mozilla.org/xre/app-info;1", "nsIXULAppInfo"],
|
||||
["Bookmark", "@mozilla.org/browser/nav-bookmarks-service;1", "nsINavBookmarksService"],
|
||||
["Crypto", "@labs.mozilla.com/Weave/Crypto;2", "IWeaveCrypto"],
|
||||
["Directory", "@mozilla.org/file/directory_service;1", "nsIProperties"],
|
||||
["Env", "@mozilla.org/process/environment;1", "nsIEnvironment"],
|
||||
["Favicon", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"],
|
||||
["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"],
|
||||
["History", "@mozilla.org/browser/nav-history-service;1", "nsPIPlacesDatabase"],
|
||||
["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
|
||||
["IO", "@mozilla.org/network/io-service;1", "nsIIOService"],
|
||||
["KeyFactory", "@mozilla.org/security/keyobjectfactory;1", "nsIKeyObjectFactory"],
|
||||
["Login", "@mozilla.org/login-manager;1", "nsILoginManager"],
|
||||
["Memory", "@mozilla.org/xpcom/memory-service;1", "nsIMemory"],
|
||||
["Private", "@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService"],
|
||||
["Profiles", "@mozilla.org/toolkit/profile-service;1", "nsIToolkitProfileService"],
|
||||
["Prompt", "@mozilla.org/embedcomp/prompt-service;1", "nsIPromptService"],
|
||||
["Script", "@mozilla.org/moz/jssubscript-loader;1", "mozIJSSubScriptLoader"],
|
||||
["SysInfo", "@mozilla.org/system-info;1", "nsIPropertyBag2"],
|
||||
["Version", "@mozilla.org/xpcom/version-comparator;1", "nsIVersionComparator"],
|
||||
["WinMediator", "@mozilla.org/appshell/window-mediator;1", "nsIWindowMediator"],
|
||||
["WinWatcher", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"],
|
||||
["Session", "@mozilla.org/browser/sessionstore;1", "nsISessionStore"],
|
||||
].forEach(function(lazy) Utils.lazySvc(Svc, lazy[0], lazy[1], lazy[2]));
|
||||
|
||||
let Str = {};
|
||||
["errors", "sync"]
|
||||
.forEach(function(lazy) Utils.lazy2(Str, lazy, Utils.lazyStrings(lazy)));
|
32
services/sync/services-sync.js
Normal file
32
services/sync/services-sync.js
Normal file
@ -0,0 +1,32 @@
|
||||
pref("services.sync.serverURL", "https://auth.services.mozilla.com/");
|
||||
pref("services.sync.storageAPI", "1.0");
|
||||
pref("services.sync.userURL", "user/");
|
||||
pref("services.sync.miscURL", "misc/");
|
||||
pref("services.sync.termsURL", "https://services.mozilla.com/tos/");
|
||||
pref("services.sync.privacyURL", "https://services.mozilla.com/privacy-policy/");
|
||||
|
||||
pref("services.sync.lastversion", "firstrun");
|
||||
pref("services.sync.autoconnect", true);
|
||||
|
||||
pref("services.sync.engine.bookmarks", true);
|
||||
pref("services.sync.engine.history", true);
|
||||
pref("services.sync.engine.passwords", true);
|
||||
pref("services.sync.engine.prefs", true);
|
||||
pref("services.sync.engine.tabs", true);
|
||||
pref("services.sync.engine.tabs.filteredUrls", "^(about:.*|chrome://weave/.*|wyciwyg:.*|file:.*)$");
|
||||
|
||||
pref("services.sync.log.appender.console", "Warn");
|
||||
pref("services.sync.log.appender.dump", "Error");
|
||||
pref("services.sync.log.appender.debugLog", "Trace");
|
||||
pref("services.sync.log.rootLogger", "Debug");
|
||||
pref("services.sync.log.logger.service.main", "Debug");
|
||||
pref("services.sync.log.logger.authenticator", "Debug");
|
||||
pref("services.sync.log.logger.network.resources", "Debug");
|
||||
pref("services.sync.log.logger.engine.bookmarks", "Debug");
|
||||
pref("services.sync.log.logger.engine.clients", "Debug");
|
||||
pref("services.sync.log.logger.engine.forms", "Debug");
|
||||
pref("services.sync.log.logger.engine.history", "Debug");
|
||||
pref("services.sync.log.logger.engine.passwords", "Debug");
|
||||
pref("services.sync.log.logger.engine.prefs", "Debug");
|
||||
pref("services.sync.log.logger.engine.tabs", "Debug");
|
||||
pref("services.sync.log.cryptoDebug", false);
|
38
services/sync/tests/unit/fake_login_manager.js
Normal file
38
services/sync/tests/unit/fake_login_manager.js
Normal file
@ -0,0 +1,38 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// ----------------------------------------
|
||||
// Fake Sample Data
|
||||
// ----------------------------------------
|
||||
|
||||
let fakeSampleLogins = [
|
||||
// Fake nsILoginInfo object.
|
||||
{hostname: "www.boogle.com",
|
||||
formSubmitURL: "http://www.boogle.com/search",
|
||||
httpRealm: "",
|
||||
username: "",
|
||||
password: "",
|
||||
usernameField: "test_person",
|
||||
passwordField: "test_password"}
|
||||
];
|
||||
|
||||
// ----------------------------------------
|
||||
// Fake Login Manager
|
||||
// ----------------------------------------
|
||||
|
||||
function FakeLoginManager(fakeLogins) {
|
||||
this.fakeLogins = fakeLogins;
|
||||
|
||||
let self = this;
|
||||
|
||||
// Use a fake nsILoginManager object.
|
||||
delete Svc.Login;
|
||||
Svc.Login = {
|
||||
removeAllLogins: function() { self.fakeLogins = []; },
|
||||
getAllLogins: function() { return self.fakeLogins; },
|
||||
addLogin: function(login) {
|
||||
getTestLogger().info("nsILoginManager.addLogin() called " +
|
||||
"with hostname '" + login.hostname + "'.");
|
||||
self.fakeLogins.push(login);
|
||||
}
|
||||
};
|
||||
}
|
64
services/sync/tests/unit/head_appinfo.js
Normal file
64
services/sync/tests/unit/head_appinfo.js
Normal file
@ -0,0 +1,64 @@
|
||||
// Load httpd from the add-on test harness or from xpcshell and declare Cc, etc.
|
||||
// without a var/const so they don't get hoisted and conflict with load_httpd.
|
||||
if (this.do_load_httpd_js == null) {
|
||||
Cc = Components.classes;
|
||||
Ci = Components.interfaces;
|
||||
Cr = Components.results;
|
||||
Cu = Components.utils;
|
||||
Cu.import("resource://harness/modules/httpd.js");
|
||||
}
|
||||
else {
|
||||
do_load_httpd_js();
|
||||
do_get_profile();
|
||||
}
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
try {
|
||||
// In the context of xpcshell tests, there won't be a default AppInfo
|
||||
Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
|
||||
}
|
||||
catch(ex) {
|
||||
|
||||
// Make sure to provide the right OS so crypto loads the right binaries
|
||||
let OS = "XPCShell";
|
||||
if ("@mozilla.org/windows-registry-key;1" in Cc)
|
||||
OS = "WINNT";
|
||||
else if ("nsILocalFileMac" in Ci)
|
||||
OS = "Darwin";
|
||||
else
|
||||
OS = "Linux";
|
||||
|
||||
let XULAppInfo = {
|
||||
vendor: "Mozilla",
|
||||
name: "XPCShell",
|
||||
ID: "{3e3ba16c-1675-4e88-b9c8-afef81b3d2ef}",
|
||||
version: "1",
|
||||
appBuildID: "20100621",
|
||||
platformVersion: "",
|
||||
platformBuildID: "20100621",
|
||||
inSafeMode: false,
|
||||
logConsoleErrors: true,
|
||||
OS: OS,
|
||||
XPCOMABI: "noarch-spidermonkey",
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime])
|
||||
};
|
||||
|
||||
let XULAppInfoFactory = {
|
||||
createInstance: function (outer, iid) {
|
||||
if (outer != null)
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
return XULAppInfo.QueryInterface(iid);
|
||||
}
|
||||
};
|
||||
|
||||
let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
|
||||
"XULAppInfo", "@mozilla.org/xre/app-info;1",
|
||||
XULAppInfoFactory);
|
||||
|
||||
}
|
||||
|
||||
// Provide resource://services-sync if it isn't already available
|
||||
let weaveService = Cc["@mozilla.org/weave/service;1"].getService();
|
||||
weaveService.wrappedJSObject.addResourceAlias();
|
408
services/sync/tests/unit/head_helpers.js
Normal file
408
services/sync/tests/unit/head_helpers.js
Normal file
@ -0,0 +1,408 @@
|
||||
// initialize nss
|
||||
let ch = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
|
||||
let ds = Cc["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Ci.nsIProperties);
|
||||
|
||||
let provider = {
|
||||
getFile: function(prop, persistent) {
|
||||
persistent.value = true;
|
||||
if (prop == "ExtPrefDL")
|
||||
return [ds.get("CurProcD", Ci.nsIFile)];
|
||||
else if (prop == "ProfD")
|
||||
return ds.get("CurProcD", Ci.nsIFile);
|
||||
throw Cr.NS_ERROR_FAILURE;
|
||||
},
|
||||
QueryInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsIDirectoryServiceProvider) ||
|
||||
iid.equals(Ci.nsISupports)) {
|
||||
return this;
|
||||
}
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
};
|
||||
ds.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
|
||||
|
||||
function loadInSandbox(aUri) {
|
||||
var sandbox = Components.utils.Sandbox(this);
|
||||
var request = Components.
|
||||
classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
|
||||
createInstance();
|
||||
|
||||
request.open("GET", aUri, false);
|
||||
request.overrideMimeType("application/javascript");
|
||||
request.send(null);
|
||||
Components.utils.evalInSandbox(request.responseText, sandbox, "1.8");
|
||||
|
||||
return sandbox;
|
||||
}
|
||||
|
||||
function FakeTimerService() {
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
this.callbackQueue = [];
|
||||
|
||||
var self = this;
|
||||
|
||||
this.__proto__ = {
|
||||
makeTimerForCall: function FTS_makeTimerForCall(cb) {
|
||||
// Just add the callback to our queue and we'll call it later, so
|
||||
// as to simulate a real nsITimer.
|
||||
self.callbackQueue.push(cb);
|
||||
return "fake nsITimer";
|
||||
},
|
||||
processCallback: function FTS_processCallbacks() {
|
||||
var cb = self.callbackQueue.pop();
|
||||
if (cb) {
|
||||
cb();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
Utils.makeTimerForCall = self.makeTimerForCall;
|
||||
};
|
||||
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
function getTestLogger(component) {
|
||||
return Log4Moz.repository.getLogger("Testing");
|
||||
}
|
||||
|
||||
function initTestLogging(level) {
|
||||
function LogStats() {
|
||||
this.errorsLogged = 0;
|
||||
}
|
||||
LogStats.prototype = {
|
||||
format: function BF_format(message) {
|
||||
if (message.level == Log4Moz.Level.Error)
|
||||
this.errorsLogged += 1;
|
||||
return message.loggerName + "\t" + message.levelDesc + "\t" +
|
||||
message.message + "\n";
|
||||
}
|
||||
};
|
||||
LogStats.prototype.__proto__ = new Log4Moz.Formatter();
|
||||
|
||||
var log = Log4Moz.repository.rootLogger;
|
||||
var logStats = new LogStats();
|
||||
var appender = new Log4Moz.DumpAppender(logStats);
|
||||
|
||||
if (typeof(level) == "undefined")
|
||||
level = "Debug";
|
||||
getTestLogger().level = Log4Moz.Level[level];
|
||||
|
||||
log.level = Log4Moz.Level.Trace;
|
||||
appender.level = Log4Moz.Level.Trace;
|
||||
// Overwrite any other appenders (e.g. from previous incarnations)
|
||||
log._appenders = [appender];
|
||||
|
||||
return logStats;
|
||||
}
|
||||
|
||||
function FakePrefService(contents) {
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
this.fakeContents = contents;
|
||||
Utils.__prefs = this;
|
||||
}
|
||||
|
||||
FakePrefService.prototype = {
|
||||
_getPref: function fake__getPref(pref) {
|
||||
getTestLogger().trace("Getting pref: " + pref);
|
||||
return this.fakeContents[pref];
|
||||
},
|
||||
getCharPref: function fake_getCharPref(pref) {
|
||||
return this._getPref(pref);
|
||||
},
|
||||
getBoolPref: function fake_getBoolPref(pref) {
|
||||
return this._getPref(pref);
|
||||
},
|
||||
getIntPref: function fake_getIntPref(pref) {
|
||||
return this._getPref(pref);
|
||||
},
|
||||
addObserver: function fake_addObserver() {}
|
||||
};
|
||||
|
||||
function FakePasswordService(contents) {
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
this.fakeContents = contents;
|
||||
let self = this;
|
||||
|
||||
Utils.findPassword = function fake_findPassword(realm, username) {
|
||||
getTestLogger().trace("Password requested for " +
|
||||
realm + ":" + username);
|
||||
if (realm in self.fakeContents && username in self.fakeContents[realm])
|
||||
return self.fakeContents[realm][username];
|
||||
else
|
||||
return null;
|
||||
};
|
||||
};
|
||||
|
||||
function FakeFilesystemService(contents) {
|
||||
this.fakeContents = contents;
|
||||
|
||||
let self = this;
|
||||
|
||||
Utils.getProfileFile = function fake_getProfileFile(arg) {
|
||||
let fakeNsILocalFile = {
|
||||
exists: function() {
|
||||
return this._fakeFilename in self.fakeContents;
|
||||
},
|
||||
_fakeFilename: (typeof(arg) == "object") ? arg.path : arg
|
||||
};
|
||||
return fakeNsILocalFile;
|
||||
};
|
||||
|
||||
Utils.readStream = function fake_readStream(stream) {
|
||||
getTestLogger().info("Reading from stream.");
|
||||
return stream._fakeData;
|
||||
};
|
||||
|
||||
Utils.open = function fake_open(file, mode) {
|
||||
switch (mode) {
|
||||
case "<":
|
||||
mode = "reading";
|
||||
break;
|
||||
case ">":
|
||||
mode = "writing";
|
||||
break;
|
||||
default:
|
||||
throw new Error("Unexpected mode: " + mode);
|
||||
}
|
||||
|
||||
getTestLogger().info("Opening '" + file._fakeFilename + "' for " +
|
||||
mode + ".");
|
||||
var contents = "";
|
||||
if (file._fakeFilename in self.fakeContents && mode == "reading")
|
||||
contents = self.fakeContents[file._fakeFilename];
|
||||
let fakeStream = {
|
||||
writeString: function(data) {
|
||||
contents += data;
|
||||
getTestLogger().info("Writing data to local file '" +
|
||||
file._fakeFilename +"': " + data);
|
||||
},
|
||||
close: function() {
|
||||
self.fakeContents[file._fakeFilename] = contents;
|
||||
},
|
||||
get _fakeData() { return contents; }
|
||||
};
|
||||
return [fakeStream];
|
||||
};
|
||||
};
|
||||
|
||||
function FakeGUIDService() {
|
||||
let latestGUID = 0;
|
||||
|
||||
Utils.makeGUID = function fake_makeGUID() {
|
||||
return "fake-guid-" + latestGUID++;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Mock implementation of IWeaveCrypto. It does not encrypt or
|
||||
* decrypt, just returns the input verbatimly.
|
||||
*/
|
||||
function FakeCryptoService() {
|
||||
this.counter = 0;
|
||||
|
||||
delete Svc.Crypto; // get rid of the getter first
|
||||
Svc.Crypto = this;
|
||||
Utils.sha256HMAC = this.sha256HMAC;
|
||||
}
|
||||
FakeCryptoService.prototype = {
|
||||
|
||||
sha256HMAC: function(message, key) {
|
||||
message = message.substr(0, 64);
|
||||
while (message.length < 64) {
|
||||
message += " ";
|
||||
}
|
||||
return message;
|
||||
},
|
||||
|
||||
encrypt: function(aClearText, aSymmetricKey, aIV) {
|
||||
return aClearText;
|
||||
},
|
||||
|
||||
decrypt: function(aCipherText, aSymmetricKey, aIV) {
|
||||
return aCipherText;
|
||||
},
|
||||
|
||||
generateKeypair: function(aPassphrase, aSalt, aIV,
|
||||
aEncodedPublicKey, aWrappedPrivateKey) {
|
||||
aEncodedPublicKey.value = aPassphrase;
|
||||
aWrappedPrivateKey.value = aPassphrase;
|
||||
},
|
||||
|
||||
generateRandomKey: function() {
|
||||
return "fake-symmetric-key-" + this.counter++;
|
||||
},
|
||||
|
||||
generateRandomIV: function() {
|
||||
// A base64-encoded IV is 24 characters long
|
||||
return "fake-fake-fake-random-iv";
|
||||
},
|
||||
|
||||
generateRandomBytes: function(aByteCount) {
|
||||
return "not-so-random-now-are-we-HA-HA-HA! >:)".slice(aByteCount);
|
||||
},
|
||||
|
||||
wrapSymmetricKey: function(aSymmetricKey, aEncodedPublicKey) {
|
||||
return aSymmetricKey;
|
||||
},
|
||||
|
||||
unwrapSymmetricKey: function(aWrappedSymmetricKey, aWrappedPrivateKey,
|
||||
aPassphrase, aSalt, aIV) {
|
||||
if (!this.verifyPassphrase(aWrappedPrivateKey, aPassphrase)) {
|
||||
throw Components.Exception("Unwrapping the private key failed",
|
||||
Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
return aWrappedSymmetricKey;
|
||||
},
|
||||
|
||||
rewrapPrivateKey: function(aWrappedPrivateKey, aPassphrase, aSalt, aIV,
|
||||
aNewPassphrase) {
|
||||
return aNewPassphrase;
|
||||
},
|
||||
|
||||
verifyPassphrase: function(aWrappedPrivateKey, aPassphrase, aSalt, aIV) {
|
||||
return aWrappedPrivateKey == aPassphrase;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
function SyncTestingInfrastructure(engineFactory) {
|
||||
let __fakePasswords = {
|
||||
'Mozilla Services Password': {foo: "bar"},
|
||||
'Mozilla Services Encryption Passphrase': {foo: "passphrase"}
|
||||
};
|
||||
|
||||
let __fakePrefs = {
|
||||
"encryption" : "none",
|
||||
"log.logger.service.crypto" : "Debug",
|
||||
"log.logger.service.engine" : "Debug",
|
||||
"log.logger.async" : "Debug",
|
||||
"xmpp.enabled" : false
|
||||
};
|
||||
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
ID.set('WeaveID',
|
||||
new Identity('Mozilla Services Encryption Passphrase', 'foo'));
|
||||
ID.set('WeaveCryptoID',
|
||||
new Identity('Mozilla Services Encryption Passphrase', 'foo'));
|
||||
|
||||
this.fakePasswordService = new FakePasswordService(__fakePasswords);
|
||||
this.fakePrefService = new FakePrefService(__fakePrefs);
|
||||
this.fakeTimerService = new FakeTimerService();
|
||||
this.logStats = initTestLogging();
|
||||
this.fakeFilesystem = new FakeFilesystemService({});
|
||||
this.fakeGUIDService = new FakeGUIDService();
|
||||
this.fakeCryptoService = new FakeCryptoService();
|
||||
|
||||
this._logger = getTestLogger();
|
||||
this._engineFactory = engineFactory;
|
||||
this._clientStates = [];
|
||||
|
||||
this.saveClientState = function pushClientState(label) {
|
||||
let state = Utils.deepCopy(this.fakeFilesystem.fakeContents);
|
||||
let currContents = this.fakeFilesystem.fakeContents;
|
||||
this.fakeFilesystem.fakeContents = [];
|
||||
let engine = this._engineFactory();
|
||||
let snapshot = Utils.deepCopy(engine._store.wrap());
|
||||
this._clientStates[label] = {state: state, snapshot: snapshot};
|
||||
this.fakeFilesystem.fakeContents = currContents;
|
||||
};
|
||||
|
||||
this.restoreClientState = function restoreClientState(label) {
|
||||
let state = this._clientStates[label].state;
|
||||
let snapshot = this._clientStates[label].snapshot;
|
||||
|
||||
function _restoreState() {
|
||||
let self = yield;
|
||||
|
||||
this.fakeFilesystem.fakeContents = [];
|
||||
let engine = this._engineFactory();
|
||||
engine._store.wipe();
|
||||
let originalSnapshot = Utils.deepCopy(engine._store.wrap());
|
||||
|
||||
engine._core.detectUpdates(self.cb, originalSnapshot, snapshot);
|
||||
let commands = yield;
|
||||
|
||||
engine._store.applyCommands.async(engine._store, self.cb, commands);
|
||||
yield;
|
||||
|
||||
this.fakeFilesystem.fakeContents = Utils.deepCopy(state);
|
||||
}
|
||||
|
||||
let self = this;
|
||||
|
||||
function restoreState(cb) {
|
||||
_restoreState.async(self, cb);
|
||||
}
|
||||
|
||||
this.runAsyncFunc("restore client state of " + label,
|
||||
restoreState);
|
||||
};
|
||||
|
||||
this.__makeCallback = function __makeCallback() {
|
||||
this.__callbackCalled = false;
|
||||
let self = this;
|
||||
return function callback() {
|
||||
self.__callbackCalled = true;
|
||||
};
|
||||
};
|
||||
|
||||
this.doSync = function doSync(name) {
|
||||
let self = this;
|
||||
|
||||
function freshEngineSync(cb) {
|
||||
let engine = self._engineFactory();
|
||||
engine.sync(cb);
|
||||
}
|
||||
|
||||
this.runAsyncFunc(name, freshEngineSync);
|
||||
};
|
||||
|
||||
this.runAsyncFunc = function runAsyncFunc(name, func) {
|
||||
let logger = this._logger;
|
||||
|
||||
logger.info("-----------------------------------------");
|
||||
logger.info("Step '" + name + "' starting.");
|
||||
logger.info("-----------------------------------------");
|
||||
func(this.__makeCallback());
|
||||
while (this.fakeTimerService.processCallback()) {}
|
||||
do_check_true(this.__callbackCalled);
|
||||
for (name in Async.outstandingGenerators)
|
||||
logger.warn("Outstanding generator exists: " + name);
|
||||
do_check_eq(this.logStats.errorsLogged, 0);
|
||||
do_check_eq(Async.outstandingGenerators.length, 0);
|
||||
logger.info("Step '" + name + "' succeeded.");
|
||||
};
|
||||
|
||||
this.resetClientState = function resetClientState() {
|
||||
this.fakeFilesystem.fakeContents = {};
|
||||
let engine = this._engineFactory();
|
||||
engine._store.wipe();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Print some debug message to the console. All arguments will be printed,
|
||||
* separated by spaces.
|
||||
*
|
||||
* @param [arg0, arg1, arg2, ...]
|
||||
* Any number of arguments to print out
|
||||
* @usage _("Hello World") -> prints "Hello World"
|
||||
* @usage _(1, 2, 3) -> prints "1 2 3"
|
||||
*/
|
||||
let _ = function(some, debug, text, to) print(Array.slice(arguments).join(" "));
|
||||
|
||||
_("Setting the identity for passphrase");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
let passphrase = ID.set("WeaveCryptoID", new Identity());
|
||||
passphrase.password = "passphrase";
|
||||
|
234
services/sync/tests/unit/head_http_server.js
Normal file
234
services/sync/tests/unit/head_http_server.js
Normal file
@ -0,0 +1,234 @@
|
||||
function httpd_setup (handlers) {
|
||||
let server = new nsHttpServer();
|
||||
for (let path in handlers) {
|
||||
server.registerPathHandler(path, handlers[path]);
|
||||
}
|
||||
server.start(8080);
|
||||
return server;
|
||||
}
|
||||
|
||||
function httpd_basic_auth_handler(body, metadata, response) {
|
||||
// no btoa() in xpcshell. it's guest:guest
|
||||
if (metadata.hasHeader("Authorization") &&
|
||||
metadata.getHeader("Authorization") == "Basic Z3Vlc3Q6Z3Vlc3Q=") {
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
} else {
|
||||
body = "This path exists and is protected - failed";
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
}
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read bytes string from an nsIInputStream. If 'count' is omitted,
|
||||
* all available input is read.
|
||||
*/
|
||||
function readBytesFromInputStream(inputStream, count) {
|
||||
var BinaryInputStream = Components.Constructor(
|
||||
"@mozilla.org/binaryinputstream;1",
|
||||
"nsIBinaryInputStream",
|
||||
"setInputStream");
|
||||
if (!count) {
|
||||
count = inputStream.available();
|
||||
}
|
||||
return new BinaryInputStream(inputStream).readBytes(count);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Represent a WBO on the server
|
||||
*/
|
||||
function ServerWBO(id, initialPayload) {
|
||||
this.id = id;
|
||||
if (!initialPayload) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof initialPayload == "object") {
|
||||
initialPayload = JSON.stringify(initialPayload);
|
||||
}
|
||||
this.payload = initialPayload;
|
||||
this.modified = Date.now() / 1000;
|
||||
}
|
||||
ServerWBO.prototype = {
|
||||
|
||||
get data() {
|
||||
return JSON.parse(this.payload);
|
||||
},
|
||||
|
||||
get: function() {
|
||||
return JSON.stringify(this, ['id', 'modified', 'payload']);
|
||||
},
|
||||
|
||||
put: function(input) {
|
||||
input = JSON.parse(input);
|
||||
this.payload = input.payload;
|
||||
this.modified = Date.now() / 1000;
|
||||
},
|
||||
|
||||
delete: function() {
|
||||
delete this.payload;
|
||||
delete this.modified;
|
||||
},
|
||||
|
||||
handler: function() {
|
||||
let self = this;
|
||||
|
||||
return function(request, response) {
|
||||
var statusCode = 200;
|
||||
var status = "OK";
|
||||
var body;
|
||||
|
||||
switch(request.method) {
|
||||
case "GET":
|
||||
if (self.payload) {
|
||||
body = self.get();
|
||||
} else {
|
||||
statusCode = 404;
|
||||
status = "Not Found";
|
||||
body = "Not Found";
|
||||
}
|
||||
break;
|
||||
|
||||
case "PUT":
|
||||
self.put(readBytesFromInputStream(request.bodyInputStream));
|
||||
body = JSON.stringify(self.modified);
|
||||
break;
|
||||
|
||||
case "DELETE":
|
||||
self.delete();
|
||||
body = JSON.stringify(Date.now() / 1000);
|
||||
break;
|
||||
}
|
||||
response.setHeader('X-Weave-Timestamp', ''+Date.now()/1000, false);
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
};
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Represent a collection on the server. The 'wbo' attribute is a
|
||||
* mapping of id -> ServerWBO objects.
|
||||
*
|
||||
* Note that if you want these records to be accessible individually,
|
||||
* you need to register their handlers with the server separately!
|
||||
*/
|
||||
function ServerCollection(wbos) {
|
||||
this.wbos = wbos || {};
|
||||
}
|
||||
ServerCollection.prototype = {
|
||||
|
||||
_inResultSet: function(wbo, options) {
|
||||
return ((!options.ids || (options.ids.indexOf(wbo.id) != -1))
|
||||
&& (!options.newer || (wbo.modified > options.newer)));
|
||||
},
|
||||
|
||||
get: function(options) {
|
||||
let result;
|
||||
if (options.full) {
|
||||
let data = [wbo.get() for ([id, wbo] in Iterator(this.wbos))
|
||||
if (this._inResultSet(wbo, options))];
|
||||
if (options.limit) {
|
||||
data = data.slice(0, options.limit);
|
||||
}
|
||||
// Our implementation of application/newlines
|
||||
result = data.join("\n") + "\n";
|
||||
} else {
|
||||
let data = [id for ([id, wbo] in Iterator(this.wbos))
|
||||
if (this._inResultSet(wbo, options))];
|
||||
if (options.limit) {
|
||||
data = data.slice(0, options.limit);
|
||||
}
|
||||
result = JSON.stringify(data);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
post: function(input) {
|
||||
input = JSON.parse(input);
|
||||
let success = [];
|
||||
let failed = [];
|
||||
|
||||
// This will count records where we have an existing ServerWBO
|
||||
// registered with us as successful and all other records as failed.
|
||||
for each (let record in input) {
|
||||
let wbo = this.wbos[record.id];
|
||||
if (wbo) {
|
||||
wbo.payload = record.payload;
|
||||
wbo.modified = Date.now() / 1000;
|
||||
success.push(record.id);
|
||||
} else {
|
||||
failed.push(record.id);
|
||||
}
|
||||
}
|
||||
return {success: success,
|
||||
failed: failed};
|
||||
},
|
||||
|
||||
delete: function(options) {
|
||||
for (let [id, wbo] in Iterator(this.wbos)) {
|
||||
if (this._inResultSet(wbo, options)) {
|
||||
wbo.delete();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handler: function() {
|
||||
let self = this;
|
||||
|
||||
return function(request, response) {
|
||||
var statusCode = 200;
|
||||
var status = "OK";
|
||||
var body;
|
||||
|
||||
// Parse queryString
|
||||
let options = {};
|
||||
for each (let chunk in request.queryString.split('&')) {
|
||||
if (!chunk) {
|
||||
continue;
|
||||
}
|
||||
chunk = chunk.split('=');
|
||||
if (chunk.length == 1) {
|
||||
options[chunk[0]] = "";
|
||||
} else {
|
||||
options[chunk[0]] = chunk[1];
|
||||
}
|
||||
}
|
||||
if (options.ids) {
|
||||
options.ids = options.ids.split(',');
|
||||
}
|
||||
if (options.newer) {
|
||||
options.newer = parseFloat(options.newer);
|
||||
}
|
||||
if (options.limit) {
|
||||
options.limit = parseInt(options.limit, 10);
|
||||
}
|
||||
|
||||
switch(request.method) {
|
||||
case "GET":
|
||||
body = self.get(options);
|
||||
break;
|
||||
|
||||
case "POST":
|
||||
let res = self.post(readBytesFromInputStream(request.bodyInputStream));
|
||||
body = JSON.stringify(res);
|
||||
break;
|
||||
|
||||
case "DELETE":
|
||||
self.delete(options);
|
||||
body = JSON.stringify(Date.now() / 1000);
|
||||
break;
|
||||
}
|
||||
response.setHeader('X-Weave-Timestamp', ''+Date.now()/1000, false);
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
};
|
||||
}
|
||||
|
||||
};
|
45
services/sync/tests/unit/test_auth_manager.js
Normal file
45
services/sync/tests/unit/test_auth_manager.js
Normal file
@ -0,0 +1,45 @@
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
let logger;
|
||||
|
||||
function server_handler(metadata, response) {
|
||||
let body;
|
||||
|
||||
// no btoa() in xpcshell. it's guest:guest
|
||||
if (metadata.hasHeader("Authorization") &&
|
||||
metadata.getHeader("Authorization") == "Basic Z3Vlc3Q6Z3Vlc3Q=") {
|
||||
body = "This path exists and is protected";
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
|
||||
} else {
|
||||
body = "This path exists and is protected - failed";
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
}
|
||||
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
logger = Log4Moz.repository.getLogger('Test');
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let server = new nsHttpServer();
|
||||
server.registerPathHandler("/foo", server_handler);
|
||||
server.start(8080);
|
||||
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
Auth.defaultAuthenticator = auth;
|
||||
|
||||
let res = new Resource("http://localhost:8080/foo");
|
||||
let content = res.get();
|
||||
do_check_eq(content, "This path exists and is protected");
|
||||
do_check_eq(content.status, 200);
|
||||
|
||||
server.stop(function() {});
|
||||
}
|
19
services/sync/tests/unit/test_bookmark_batch_fail.js
Normal file
19
services/sync/tests/unit/test_bookmark_batch_fail.js
Normal file
@ -0,0 +1,19 @@
|
||||
_("Making sure a failing sync reports a useful error");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
|
||||
function run_test() {
|
||||
let engine = new BookmarksEngine();
|
||||
engine._syncStartup = function() {
|
||||
throw "FAIL!";
|
||||
};
|
||||
|
||||
try {
|
||||
_("Try calling the sync that should throw right away");
|
||||
engine._sync();
|
||||
do_throw("Should have failed sync!");
|
||||
}
|
||||
catch(ex) {
|
||||
_("Making sure what we threw ended up as the exception:", ex);
|
||||
do_check_eq(ex, "FAIL!");
|
||||
}
|
||||
}
|
171
services/sync/tests/unit/test_bookmark_order.js
Normal file
171
services/sync/tests/unit/test_bookmark_order.js
Normal file
@ -0,0 +1,171 @@
|
||||
_("Making sure after processing incoming bookmarks, they show up in the right order");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
Cu.import("resource://services-sync/type_records/bookmark.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function getBookmarks(folderId) {
|
||||
let bookmarks = [];
|
||||
|
||||
let pos = 0;
|
||||
while (true) {
|
||||
let itemId = Svc.Bookmark.getIdForItemAt(folderId, pos);
|
||||
_("Got itemId", itemId, "under", folderId, "at", pos);
|
||||
if (itemId == -1)
|
||||
break;
|
||||
|
||||
switch (Svc.Bookmark.getItemType(itemId)) {
|
||||
case Svc.Bookmark.TYPE_BOOKMARK:
|
||||
bookmarks.push(Svc.Bookmark.getItemTitle(itemId));
|
||||
break;
|
||||
case Svc.Bookmark.TYPE_FOLDER:
|
||||
bookmarks.push(getBookmarks(itemId));
|
||||
break;
|
||||
default:
|
||||
_("Unsupported item type..");
|
||||
}
|
||||
|
||||
pos++;
|
||||
}
|
||||
|
||||
return bookmarks;
|
||||
}
|
||||
|
||||
function check(expected) {
|
||||
let bookmarks = getBookmarks(Svc.Bookmark.unfiledBookmarksFolder);
|
||||
|
||||
_("Checking if the bookmark structure is", JSON.stringify(expected));
|
||||
_("Got bookmarks:", JSON.stringify(bookmarks));
|
||||
do_check_true(Utils.deepEquals(bookmarks, expected));
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
_("Starting with a clean slate of no bookmarks");
|
||||
let store = new (new BookmarksEngine())._storeObj();
|
||||
store.wipe();
|
||||
check([]);
|
||||
|
||||
function $B(name, parent, pred) {
|
||||
let bookmark = new Bookmark();
|
||||
bookmark.id = name;
|
||||
bookmark.title = name;
|
||||
bookmark.bmkUri = "http://uri/";
|
||||
bookmark.parentid = parent || "unfiled";
|
||||
bookmark.predecessorid = pred;
|
||||
bookmark.tags = [];
|
||||
store.applyIncoming(bookmark);
|
||||
}
|
||||
|
||||
function $F(name, parent, pred) {
|
||||
let folder = new BookmarkFolder();
|
||||
folder.id = name;
|
||||
folder.title = name;
|
||||
folder.parentid = parent || "unfiled";
|
||||
folder.predecessorid = pred;
|
||||
store.applyIncoming(folder);
|
||||
}
|
||||
|
||||
_("basic add first bookmark");
|
||||
$B("10", "");
|
||||
check(["10"]);
|
||||
|
||||
_("basic append behind 10");
|
||||
$B("20", "", "10");
|
||||
check(["10", "20"]);
|
||||
|
||||
_("basic create in folder");
|
||||
$F("f30", "", "20");
|
||||
$B("31", "f30");
|
||||
check(["10", "20", ["31"]]);
|
||||
|
||||
_("insert missing predecessor -> append");
|
||||
$B("50", "", "f40");
|
||||
check(["10", "20", ["31"], "50"]);
|
||||
|
||||
_("insert missing parent -> append");
|
||||
$B("41", "f40");
|
||||
check(["10", "20", ["31"], "50", "41"]);
|
||||
|
||||
_("insert another missing parent -> append");
|
||||
$B("42", "f40", "41");
|
||||
check(["10", "20", ["31"], "50", "41", "42"]);
|
||||
|
||||
_("insert folder -> move children and followers");
|
||||
$F("f40", "", "f30");
|
||||
check(["10", "20", ["31"], ["41", "42"], "50"]);
|
||||
|
||||
_("Moving 10 behind 50 -> update 10, 20");
|
||||
$B("10", "", "50");
|
||||
$B("20", "");
|
||||
check(["20", ["31"], ["41", "42"], "50", "10"]);
|
||||
|
||||
_("Moving 10 back to front -> update 10, 20");
|
||||
$B("10", "");
|
||||
$B("20", "", "10");
|
||||
check(["10", "20", ["31"], ["41", "42"], "50"]);
|
||||
|
||||
_("Moving 10 behind 50 in different order -> update 20, 10");
|
||||
$B("20", "");
|
||||
$B("10", "", "50");
|
||||
check(["20", ["31"], ["41", "42"], "50", "10"]);
|
||||
|
||||
_("Moving 10 back to front in different order -> update 20, 10");
|
||||
$B("20", "", "10");
|
||||
$B("10", "");
|
||||
check(["10", "20", ["31"], ["41", "42"], "50"]);
|
||||
|
||||
_("Moving 50 behind 42 in f40 -> update 50");
|
||||
$B("50", "f40", "42");
|
||||
check(["10", "20", ["31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Moving 10 in front of 31 in f30 -> update 10, 20, 31");
|
||||
$B("10", "f30");
|
||||
$B("20", "");
|
||||
$B("31", "f30", "10");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Moving 20 between 10 and 31 -> update 20, f30, 31");
|
||||
$B("20", "f30", "10");
|
||||
$F("f30", "");
|
||||
$B("31", "f30", "20");
|
||||
check([["10", "20", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 20 back to front -> update 20, f30, 31");
|
||||
$B("20", "");
|
||||
$F("f30", "", "20");
|
||||
$B("31", "f30", "10");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Moving 20 between 10 and 31 different order -> update f30, 20, 31");
|
||||
$F("f30", "");
|
||||
$B("20", "f30", "10");
|
||||
$B("31", "f30", "20");
|
||||
check([["10", "20", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 20 back to front different order -> update f30, 31, 20");
|
||||
$F("f30", "", "20");
|
||||
$B("31", "f30", "10");
|
||||
$B("20", "");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Moving 20 between 10 and 31 different order 2 -> update 31, f30, 20");
|
||||
$B("31", "f30", "20");
|
||||
$F("f30", "");
|
||||
$B("20", "f30", "10");
|
||||
check([["10", "20", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 20 back to front different order 2 -> update 31, f30, 20");
|
||||
$B("31", "f30", "10");
|
||||
$F("f30", "", "20");
|
||||
$B("20", "");
|
||||
check(["20", ["10", "31"], ["41", "42", "50"]]);
|
||||
|
||||
_("Move 10, 31 to f40 but update in reverse -> update 41, 31, 10");
|
||||
$B("41", "f40", "31");
|
||||
$B("31", "f40", "10");
|
||||
$B("10", "f40");
|
||||
check(["20", [], ["10", "31", "41", "42", "50"]]);
|
||||
|
||||
_("Reparent f40 into f30");
|
||||
$F("f40", "f30");
|
||||
check(["20", [["10", "31", "41", "42", "50"]]]);
|
||||
}
|
40
services/sync/tests/unit/test_bookmark_predecessor.js
Normal file
40
services/sync/tests/unit/test_bookmark_predecessor.js
Normal file
@ -0,0 +1,40 @@
|
||||
_("Make sure bad bookmarks can still get their predecessors");
|
||||
Cu.import("resource://services-sync/engines/bookmarks.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Starting with a clean slate of no bookmarks");
|
||||
let store = new (new BookmarksEngine())._storeObj();
|
||||
store.wipe();
|
||||
|
||||
let uri = Utils.makeURI("http://uri/");
|
||||
function insert(pos, folder) {
|
||||
folder = folder || Svc.Bookmark.toolbarFolder;
|
||||
let name = "pos" + pos;
|
||||
let bmk = Svc.Bookmark.insertBookmark(folder, uri, pos, name);
|
||||
Svc.Bookmark.setItemGUID(bmk, name);
|
||||
return bmk;
|
||||
}
|
||||
|
||||
_("Creating a couple bookmarks that create holes");
|
||||
let first = insert(5);
|
||||
let second = insert(10);
|
||||
|
||||
_("Making sure the record created for the first has no predecessor");
|
||||
let pos5 = store.createRecord("pos5");
|
||||
do_check_eq(pos5.predecessorid, undefined);
|
||||
|
||||
_("Making sure the second record has the first as its predecessor");
|
||||
let pos10 = store.createRecord("pos10");
|
||||
do_check_eq(pos10.predecessorid, "pos5");
|
||||
|
||||
_("Make sure the index of item gets fixed");
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(first), 0);
|
||||
do_check_eq(Svc.Bookmark.getItemIndex(second), 1);
|
||||
|
||||
_("Make sure things that are in unsorted don't set the predecessor");
|
||||
insert(0, Svc.Bookmark.unfiledBookmarksFolder);
|
||||
insert(1, Svc.Bookmark.unfiledBookmarksFolder);
|
||||
do_check_eq(store.createRecord("pos0").predecessorid, undefined);
|
||||
do_check_eq(store.createRecord("pos1").predecessorid, undefined);
|
||||
}
|
54
services/sync/tests/unit/test_clients_escape.js
Normal file
54
services/sync/tests/unit/test_clients_escape.js
Normal file
@ -0,0 +1,54 @@
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/engines/clients.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let baseUri = "http://fakebase/";
|
||||
let pubUri = baseUri + "pubkey";
|
||||
let privUri = baseUri + "privkey";
|
||||
let cryptoUri = baseUri + "crypto";
|
||||
|
||||
_("Setting up fake pub/priv keypair and symkey for encrypt/decrypt");
|
||||
PubKeys.defaultKeyUri = baseUri + "pubkey";
|
||||
let {pubkey, privkey} = PubKeys.createKeypair(passphrase, pubUri, privUri);
|
||||
PubKeys.set(pubUri, pubkey);
|
||||
PrivKeys.set(privUri, privkey);
|
||||
let cryptoMeta = new CryptoMeta(cryptoUri);
|
||||
cryptoMeta.addUnwrappedKey(pubkey, Svc.Crypto.generateRandomKey());
|
||||
CryptoMetas.set(cryptoUri, cryptoMeta);
|
||||
|
||||
_("Test that serializing client records results in uploadable ascii");
|
||||
Clients.__defineGetter__("cryptoMetaURL", function() cryptoUri);
|
||||
Clients.localID = "ascii";
|
||||
Clients.localName = "wéävê";
|
||||
|
||||
_("Make sure we have the expected record");
|
||||
let record = Clients._createRecord("ascii");
|
||||
do_check_eq(record.id, "ascii");
|
||||
do_check_eq(record.name, "wéävê");
|
||||
|
||||
record.encrypt(passphrase)
|
||||
let serialized = JSON.stringify(record);
|
||||
let checkCount = 0;
|
||||
_("Checking for all ASCII:", serialized);
|
||||
Array.forEach(serialized, function(ch) {
|
||||
let code = ch.charCodeAt(0);
|
||||
_("Checking asciiness of '", ch, "'=", code);
|
||||
do_check_true(code < 128);
|
||||
checkCount++;
|
||||
});
|
||||
|
||||
_("Processed", checkCount, "characters out of", serialized.length);
|
||||
do_check_eq(checkCount, serialized.length);
|
||||
|
||||
_("Making sure the record still looks like it did before");
|
||||
record.decrypt(passphrase)
|
||||
do_check_eq(record.id, "ascii");
|
||||
do_check_eq(record.name, "wéävê");
|
||||
|
||||
_("Sanity check that creating the record also gives the same");
|
||||
record = Clients._createRecord("ascii");
|
||||
do_check_eq(record.id, "ascii");
|
||||
do_check_eq(record.name, "wéävê");
|
||||
}
|
173
services/sync/tests/unit/test_collection_inc_get.js
Normal file
173
services/sync/tests/unit/test_collection_inc_get.js
Normal file
@ -0,0 +1,173 @@
|
||||
_("Make sure Collection can correctly incrementally parse GET requests");
|
||||
Cu.import("resource://services-sync/base_records/collection.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
|
||||
function run_test() {
|
||||
let coll = new Collection("", WBORecord);
|
||||
let stream = { _data: "" };
|
||||
let called, recCount, sum;
|
||||
|
||||
_("Not-JSON, string payloads are strings");
|
||||
called = false;
|
||||
stream._data = '{"payload":"hello"}\n';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
_("Got record:", JSON.stringify(rec));
|
||||
do_check_eq(rec.payload, "hello");
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
do_check_eq(stream._data, '');
|
||||
do_check_true(called);
|
||||
_("\n");
|
||||
|
||||
|
||||
_("Parse record with payload");
|
||||
called = false;
|
||||
stream._data = '{"payload":"{\\"value\\":123}"}\n';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
_("Got record:", JSON.stringify(rec));
|
||||
do_check_eq(rec.payload.value, 123);
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
do_check_eq(stream._data, '');
|
||||
do_check_true(called);
|
||||
_("\n");
|
||||
|
||||
|
||||
_("Parse multiple records in one go");
|
||||
called = false;
|
||||
recCount = 0;
|
||||
sum = 0;
|
||||
stream._data = '{"payload":"{\\"value\\":100}"}\n{"payload":"{\\"value\\":10}"}\n{"payload":"{\\"value\\":1}"}\n';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
_("Got record:", JSON.stringify(rec));
|
||||
recCount++;
|
||||
sum += rec.payload.value;
|
||||
_("Incremental status: count", recCount, "sum", sum);
|
||||
switch (recCount) {
|
||||
case 1:
|
||||
do_check_eq(rec.payload.value, 100);
|
||||
do_check_eq(sum, 100);
|
||||
break;
|
||||
case 2:
|
||||
do_check_eq(rec.payload.value, 10);
|
||||
do_check_eq(sum, 110);
|
||||
break;
|
||||
case 3:
|
||||
do_check_eq(rec.payload.value, 1);
|
||||
do_check_eq(sum, 111);
|
||||
break;
|
||||
default:
|
||||
do_throw("unexpected number of record counts", recCount);
|
||||
break;
|
||||
}
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
do_check_eq(recCount, 3);
|
||||
do_check_eq(sum, 111);
|
||||
do_check_eq(stream._data, '');
|
||||
do_check_true(called);
|
||||
_("\n");
|
||||
|
||||
|
||||
_("Handle incremental data incoming");
|
||||
called = false;
|
||||
recCount = 0;
|
||||
sum = 0;
|
||||
stream._data = '{"payl';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
do_throw("shouldn't have gotten a record..");
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
_("shouldn't have gotten anything yet");
|
||||
do_check_eq(recCount, 0);
|
||||
do_check_eq(sum, 0);
|
||||
_("leading array bracket should have been trimmed");
|
||||
do_check_eq(stream._data, '{"payl');
|
||||
do_check_false(called);
|
||||
_();
|
||||
|
||||
_("adding more data enough for one record..");
|
||||
called = false;
|
||||
stream._data += 'oad":"{\\"value\\":100}"}\n';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
_("Got record:", JSON.stringify(rec));
|
||||
recCount++;
|
||||
sum += rec.payload.value;
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
_("should have 1 record with sum 100");
|
||||
do_check_eq(recCount, 1);
|
||||
do_check_eq(sum, 100);
|
||||
_("all data should have been consumed including trailing comma");
|
||||
do_check_eq(stream._data, '');
|
||||
do_check_true(called);
|
||||
_();
|
||||
|
||||
_("adding more data..");
|
||||
called = false;
|
||||
stream._data += '{"payload":"{\\"value\\":10}"';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
do_throw("shouldn't have gotten a record..");
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
_("should still have 1 record with sum 100");
|
||||
do_check_eq(recCount, 1);
|
||||
do_check_eq(sum, 100);
|
||||
_("should almost have a record");
|
||||
do_check_eq(stream._data, '{"payload":"{\\"value\\":10}"');
|
||||
do_check_false(called);
|
||||
_();
|
||||
|
||||
_("add data for two records..");
|
||||
called = false;
|
||||
stream._data += '}\n{"payload":"{\\"value\\":1}"}\n';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
_("Got record:", JSON.stringify(rec));
|
||||
recCount++;
|
||||
sum += rec.payload.value;
|
||||
switch (recCount) {
|
||||
case 2:
|
||||
do_check_eq(rec.payload.value, 10);
|
||||
do_check_eq(sum, 110);
|
||||
break;
|
||||
case 3:
|
||||
do_check_eq(rec.payload.value, 1);
|
||||
do_check_eq(sum, 111);
|
||||
break;
|
||||
default:
|
||||
do_throw("unexpected number of record counts", recCount);
|
||||
break;
|
||||
}
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
_("should have gotten all 3 records with sum 111");
|
||||
do_check_eq(recCount, 3);
|
||||
do_check_eq(sum, 111);
|
||||
_("should have consumed all data");
|
||||
do_check_eq(stream._data, '');
|
||||
do_check_true(called);
|
||||
_();
|
||||
|
||||
_("add no extra data");
|
||||
called = false;
|
||||
stream._data += '';
|
||||
coll.recordHandler = function(rec) {
|
||||
called = true;
|
||||
do_throw("shouldn't have gotten a record..");
|
||||
};
|
||||
coll._onProgress.call(stream);
|
||||
_("should still have 3 records with sum 111");
|
||||
do_check_eq(recCount, 3);
|
||||
do_check_eq(sum, 111);
|
||||
_("should have consumed nothing but still have nothing");
|
||||
do_check_eq(stream._data, "");
|
||||
do_check_false(called);
|
||||
_("\n");
|
||||
}
|
158
services/sync/tests/unit/test_crypto_crypt.js
Normal file
158
services/sync/tests/unit/test_crypto_crypt.js
Normal file
@ -0,0 +1,158 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc = Svc.Crypto;
|
||||
|
||||
// First, do a normal run with expected usage... Generate a random key and
|
||||
// iv, encrypt and decrypt a string.
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
var key = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(key.length, 44);
|
||||
|
||||
var mySecret = "bacon is a vegetable";
|
||||
var cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
do_check_eq(cipherText.length, 44);
|
||||
|
||||
var clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(clearText.length, 20);
|
||||
|
||||
// Did the text survive the encryption round-trip?
|
||||
do_check_eq(clearText, mySecret);
|
||||
do_check_neq(cipherText, mySecret); // just to be explicit
|
||||
|
||||
|
||||
// Do some more tests with a fixed key/iv, to check for reproducable results.
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
|
||||
key = "St1tFCor7vQEJNug/465dQ==";
|
||||
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
|
||||
|
||||
// Test small input sizes
|
||||
mySecret = "";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "OGQjp6mK1a3fs9k9Ml4L3w==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "x";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "96iMl4vhOxFUW/lVHHzVqg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "xx";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "olpPbETRYROCSqFWcH2SWg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "xxx";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "rRbpHGyVSZizLX/x43Wm+Q==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "xxxx";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "HeC7miVGDcpxae9RmiIKAw==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
// Test non-ascii input
|
||||
// ("testuser1" using similar-looking glyphs)
|
||||
mySecret = String.fromCharCode(355, 277, 349, 357, 533, 537, 101, 345, 185);
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "Pj4ixByXoH3SU3JkOXaEKPgwRAWplAWFLQZkpJd5Kr4=");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
// Tests input spanning a block boundary (AES block size is 16 bytes)
|
||||
mySecret = "123456789012345";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "e6c5hwphe45/3VN/M0bMUA==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "1234567890123456";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP1JOIQF87E2vTUvBUQnyV04=");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
mySecret = "12345678901234567";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "V6aaOZw8pWlYkoIHNkhsP5GvxWJ9+GIAS6lXw+5fHTI=");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
|
||||
// Test with 192 bit key.
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
|
||||
key = "iz35tuIMq4/H+IYw2KTgow==";
|
||||
iv = "TJYrvva2KxvkM8hvOIvWp3xgjTXgq5Ss";
|
||||
mySecret = "i like pie";
|
||||
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "DLGx8BWqSCLGG7i/xwvvxg==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
// Test with 256 bit key.
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_256_CBC;
|
||||
key = "c5hG3YG+NC61FFy8NOHQak1ZhMEWO79bwiAfar2euzI=";
|
||||
iv = "gsgLRDaxWvIfKt75RjuvFWERt83FFsY2A0TW+0b2iVk=";
|
||||
mySecret = "i like pie";
|
||||
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, iv);
|
||||
do_check_eq(cipherText, "o+ADtdMd8ubzNWurS6jt0Q==");
|
||||
do_check_eq(clearText, mySecret);
|
||||
|
||||
|
||||
// Test with bogus inputs
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
|
||||
key = "St1tFCor7vQEJNug/465dQ==";
|
||||
iv = "oLjkfrLIOnK2bDRvW4kXYA==";
|
||||
mySecret = "does thunder read testcases?";
|
||||
cipherText = cryptoSvc.encrypt(mySecret, key, iv);
|
||||
do_check_eq(cipherText, "T6fik9Ros+DB2ablH9zZ8FWZ0xm/szSwJjIHZu7sjPs=");
|
||||
|
||||
var badkey = "badkeybadkeybadkeybadk==";
|
||||
var badiv = "badivbadivbadivbadivbad==";
|
||||
var badcipher = "crapinputcrapinputcrapinputcrapinputcrapinp=";
|
||||
var failure;
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(cipherText, badkey, iv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(cipherText, key, badiv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(cipherText, badkey, badiv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
try {
|
||||
failure = false;
|
||||
clearText = cryptoSvc.decrypt(badcipher, key, iv);
|
||||
} catch (e) {
|
||||
failure = true;
|
||||
}
|
||||
do_check_true(failure);
|
||||
|
||||
}
|
64
services/sync/tests/unit/test_crypto_keypair.js
Normal file
64
services/sync/tests/unit/test_crypto_keypair.js
Normal file
@ -0,0 +1,64 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc = Svc.Crypto;
|
||||
|
||||
var salt = cryptoSvc.generateRandomBytes(16);
|
||||
do_check_eq(salt.length, 24);
|
||||
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
var symKey = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(symKey.length, 44);
|
||||
|
||||
|
||||
// Tests with a 2048 bit key (the default)
|
||||
do_check_eq(cryptoSvc.keypairBits, 2048)
|
||||
|
||||
var pubOut = {};
|
||||
var privOut = {};
|
||||
cryptoSvc.generateKeypair("my passphrase", salt, iv, pubOut, privOut);
|
||||
var pubKey = pubOut.value;
|
||||
var privKey = privOut.value;
|
||||
do_check_true(!!pubKey);
|
||||
do_check_true(!!privKey);
|
||||
do_check_eq(pubKey.length, 392);
|
||||
do_check_eq(privKey.length, 1644);
|
||||
|
||||
// do some key wrapping
|
||||
var wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
|
||||
do_check_eq(wrappedKey.length, 344);
|
||||
|
||||
var unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
|
||||
"my passphrase", salt, iv);
|
||||
do_check_eq(unwrappedKey.length, 44);
|
||||
|
||||
// The acid test... Is our unwrapped key the same thing we started with?
|
||||
do_check_eq(unwrappedKey, symKey);
|
||||
|
||||
|
||||
// Tests with a 1024 bit key
|
||||
cryptoSvc.keypairBits = 1024;
|
||||
do_check_eq(cryptoSvc.keypairBits, 1024)
|
||||
|
||||
cryptoSvc.generateKeypair("my passphrase", salt, iv, pubOut, privOut);
|
||||
var pubKey = pubOut.value;
|
||||
var privKey = privOut.value;
|
||||
do_check_true(!!pubKey);
|
||||
do_check_true(!!privKey);
|
||||
do_check_eq(pubKey.length, 216);
|
||||
do_check_eq(privKey.length, 856);
|
||||
|
||||
// do some key wrapping
|
||||
wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
|
||||
do_check_eq(wrappedKey.length, 172);
|
||||
unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
|
||||
"my passphrase", salt, iv);
|
||||
do_check_eq(unwrappedKey.length, 44);
|
||||
|
||||
// The acid test... Is our unwrapped key the same thing we started with?
|
||||
do_check_eq(unwrappedKey, symKey);
|
||||
|
||||
|
||||
}
|
66
services/sync/tests/unit/test_crypto_random.js
Normal file
66
services/sync/tests/unit/test_crypto_random.js
Normal file
@ -0,0 +1,66 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc = Svc.Crypto;
|
||||
|
||||
// Test salt generation.
|
||||
var salt;
|
||||
|
||||
salt = cryptoSvc.generateRandomBytes(0);
|
||||
do_check_eq(salt.length, 0);
|
||||
salt = cryptoSvc.generateRandomBytes(1);
|
||||
do_check_eq(salt.length, 4);
|
||||
salt = cryptoSvc.generateRandomBytes(2);
|
||||
do_check_eq(salt.length, 4);
|
||||
salt = cryptoSvc.generateRandomBytes(3);
|
||||
do_check_eq(salt.length, 4);
|
||||
salt = cryptoSvc.generateRandomBytes(4);
|
||||
do_check_eq(salt.length, 8);
|
||||
salt = cryptoSvc.generateRandomBytes(8);
|
||||
do_check_eq(salt.length, 12);
|
||||
|
||||
// sanity check to make sure salts seem random
|
||||
var salt2 = cryptoSvc.generateRandomBytes(8);
|
||||
do_check_eq(salt2.length, 12);
|
||||
do_check_neq(salt, salt2);
|
||||
|
||||
salt = cryptoSvc.generateRandomBytes(16);
|
||||
do_check_eq(salt.length, 24);
|
||||
salt = cryptoSvc.generateRandomBytes(1024);
|
||||
do_check_eq(salt.length, 1368);
|
||||
|
||||
|
||||
// Test random key generation
|
||||
var keydata, keydata2, iv;
|
||||
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 44);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_256_CBC;
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 44);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_192_CBC;
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 32);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
|
||||
cryptoSvc.algorithm = Ci.IWeaveCrypto.AES_128_CBC;
|
||||
keydata = cryptoSvc.generateRandomKey();
|
||||
do_check_eq(keydata.length, 24);
|
||||
keydata2 = cryptoSvc.generateRandomKey();
|
||||
do_check_neq(keydata, keydata2); // sanity check for randomness
|
||||
iv = cryptoSvc.generateRandomIV();
|
||||
do_check_eq(iv.length, 24);
|
||||
}
|
36
services/sync/tests/unit/test_crypto_rewrap.js
Normal file
36
services/sync/tests/unit/test_crypto_rewrap.js
Normal file
@ -0,0 +1,36 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc = Svc.Crypto;
|
||||
|
||||
var salt = cryptoSvc.generateRandomBytes(16);
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
var symKey = cryptoSvc.generateRandomKey();
|
||||
|
||||
// Tests with a 2048 bit key (the default)
|
||||
do_check_eq(cryptoSvc.keypairBits, 2048)
|
||||
var pubOut = {};
|
||||
var privOut = {};
|
||||
cryptoSvc.generateKeypair("old passphrase", salt, iv, pubOut, privOut);
|
||||
var pubKey = pubOut.value;
|
||||
var privKey = privOut.value;
|
||||
|
||||
// do some key wrapping
|
||||
var wrappedKey = cryptoSvc.wrapSymmetricKey(symKey, pubKey);
|
||||
var unwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, privKey,
|
||||
"old passphrase", salt, iv);
|
||||
|
||||
// Is our unwrapped key the same thing we started with?
|
||||
do_check_eq(unwrappedKey, symKey);
|
||||
|
||||
// Rewrap key with a new passphrase
|
||||
var newPrivKey = cryptoSvc.rewrapPrivateKey(privKey, "old passphrase",
|
||||
salt, iv, "new passphrase");
|
||||
|
||||
// Unwrap symkey with new symkey
|
||||
var newUnwrappedKey = cryptoSvc.unwrapSymmetricKey(wrappedKey, newPrivKey,
|
||||
"new passphrase", salt, iv);
|
||||
|
||||
// The acid test... Is this unwrapped symkey the same as before?
|
||||
do_check_eq(newUnwrappedKey, unwrappedKey);
|
||||
}
|
24
services/sync/tests/unit/test_crypto_verify.js
Normal file
24
services/sync/tests/unit/test_crypto_verify.js
Normal file
@ -0,0 +1,24 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let cryptoSvc = Svc.Crypto;
|
||||
|
||||
var salt = cryptoSvc.generateRandomBytes(16);
|
||||
var iv = cryptoSvc.generateRandomIV();
|
||||
|
||||
// Tests with a 2048 bit key (the default)
|
||||
do_check_eq(cryptoSvc.keypairBits, 2048)
|
||||
var privOut = {};
|
||||
cryptoSvc.generateKeypair("passphrase", salt, iv, {}, privOut);
|
||||
var privKey = privOut.value;
|
||||
|
||||
// Check with correct passphrase
|
||||
var shouldBeTrue = cryptoSvc.verifyPassphrase(privKey, "passphrase",
|
||||
salt, iv);
|
||||
do_check_eq(shouldBeTrue, true);
|
||||
|
||||
// Check with incorrect passphrase
|
||||
var shouldBeFalse = cryptoSvc.verifyPassphrase(privKey, "NotPassphrase",
|
||||
salt, iv);
|
||||
do_check_eq(shouldBeFalse, false);
|
||||
}
|
172
services/sync/tests/unit/test_engine.js
Normal file
172
services/sync/tests/unit/test_engine.js
Normal file
@ -0,0 +1,172 @@
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/stores.js");
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
|
||||
function SteamStore() {
|
||||
Store.call(this, "Steam");
|
||||
this.wasWiped = false;
|
||||
}
|
||||
SteamStore.prototype = {
|
||||
__proto__: Store.prototype,
|
||||
|
||||
wipe: function() {
|
||||
this.wasWiped = true;
|
||||
}
|
||||
};
|
||||
|
||||
function SteamTracker() {
|
||||
Tracker.call(this, "Steam");
|
||||
}
|
||||
SteamTracker.prototype = {
|
||||
__proto__: Tracker.prototype
|
||||
};
|
||||
|
||||
function SteamEngine() {
|
||||
Engine.call(this, "Steam");
|
||||
this.wasReset = false;
|
||||
this.wasSynced = false;
|
||||
}
|
||||
SteamEngine.prototype = {
|
||||
__proto__: Engine.prototype,
|
||||
_storeObj: SteamStore,
|
||||
_trackerObj: SteamTracker,
|
||||
|
||||
_resetClient: function () {
|
||||
this.wasReset = true;
|
||||
},
|
||||
|
||||
_sync: function () {
|
||||
this.wasSynced = true;
|
||||
}
|
||||
};
|
||||
|
||||
let engineObserver = {
|
||||
topics: [],
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
do_check_eq(subject, "steam");
|
||||
this.topics.push(topic);
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this.topics = [];
|
||||
}
|
||||
};
|
||||
Observers.add("weave:engine:reset-client:start", engineObserver);
|
||||
Observers.add("weave:engine:reset-client:finish", engineObserver);
|
||||
Observers.add("weave:engine:wipe-client:start", engineObserver);
|
||||
Observers.add("weave:engine:wipe-client:finish", engineObserver);
|
||||
Observers.add("weave:engine:sync:start", engineObserver);
|
||||
Observers.add("weave:engine:sync:finish", engineObserver);
|
||||
|
||||
|
||||
function test_members() {
|
||||
_("Engine object members");
|
||||
let engine = new SteamEngine();
|
||||
do_check_eq(engine.Name, "Steam");
|
||||
do_check_eq(engine.prefName, "steam");
|
||||
do_check_true(engine._store instanceof SteamStore);
|
||||
do_check_true(engine._tracker instanceof SteamTracker);
|
||||
}
|
||||
|
||||
function test_score() {
|
||||
_("Engine.score corresponds to tracker.score and is readonly");
|
||||
let engine = new SteamEngine();
|
||||
do_check_eq(engine.score, 0);
|
||||
engine._tracker.score += 5;
|
||||
do_check_eq(engine.score, 5);
|
||||
|
||||
try {
|
||||
engine.score = 10;
|
||||
} catch(ex) {
|
||||
// Setting an attribute that has a getter produces an error in
|
||||
// Firefox <= 3.6 and is ignored in later versions. Either way,
|
||||
// the attribute's value won't change.
|
||||
}
|
||||
do_check_eq(engine.score, 5);
|
||||
}
|
||||
|
||||
function test_resetClient() {
|
||||
_("Engine.resetClient calls _resetClient");
|
||||
let engine = new SteamEngine();
|
||||
do_check_false(engine.wasReset);
|
||||
|
||||
engine.resetClient();
|
||||
do_check_true(engine.wasReset);
|
||||
do_check_eq(engineObserver.topics[0], "weave:engine:reset-client:start");
|
||||
do_check_eq(engineObserver.topics[1], "weave:engine:reset-client:finish");
|
||||
|
||||
engine.wasReset = false;
|
||||
engineObserver.reset();
|
||||
}
|
||||
|
||||
function test_wipeClient() {
|
||||
_("Engine.wipeClient calls resetClient, wipes store, clears changed IDs");
|
||||
let engine = new SteamEngine();
|
||||
do_check_false(engine.wasReset);
|
||||
do_check_false(engine._store.wasWiped);
|
||||
do_check_true(engine._tracker.addChangedID("a-changed-id"));
|
||||
do_check_true("a-changed-id" in engine._tracker.changedIDs);
|
||||
|
||||
engine.wipeClient();
|
||||
do_check_true(engine.wasReset);
|
||||
do_check_true(engine._store.wasWiped);
|
||||
do_check_eq(JSON.stringify(engine._tracker.changedIDs), "{}");
|
||||
do_check_eq(engineObserver.topics[0], "weave:engine:wipe-client:start");
|
||||
do_check_eq(engineObserver.topics[1], "weave:engine:reset-client:start");
|
||||
do_check_eq(engineObserver.topics[2], "weave:engine:reset-client:finish");
|
||||
do_check_eq(engineObserver.topics[3], "weave:engine:wipe-client:finish");
|
||||
|
||||
engine.wasReset = false;
|
||||
engine._store.wasWiped = false;
|
||||
engineObserver.reset();
|
||||
}
|
||||
|
||||
function test_enabled() {
|
||||
_("Engine.enabled corresponds to preference");
|
||||
let engine = new SteamEngine();
|
||||
try {
|
||||
do_check_false(engine.enabled);
|
||||
Svc.Prefs.set("engine.steam", true);
|
||||
do_check_true(engine.enabled);
|
||||
|
||||
engine.enabled = false;
|
||||
do_check_false(Svc.Prefs.get("engine.steam"));
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
function test_sync() {
|
||||
let engine = new SteamEngine();
|
||||
try {
|
||||
_("Engine.sync doesn't call _sync if it's not enabled");
|
||||
do_check_false(engine.enabled);
|
||||
do_check_false(engine.wasSynced);
|
||||
engine.sync();
|
||||
do_check_false(engine.wasSynced);
|
||||
|
||||
_("Engine.sync calls _sync if it's enabled");
|
||||
engine.enabled = true;
|
||||
engine.sync();
|
||||
do_check_true(engine.wasSynced);
|
||||
do_check_eq(engineObserver.topics[0], "weave:engine:sync:start");
|
||||
do_check_eq(engineObserver.topics[1], "weave:engine:sync:finish");
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
engine.wasSynced = false;
|
||||
engineObserver.reset();
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_members();
|
||||
test_score();
|
||||
test_resetClient();
|
||||
test_wipeClient();
|
||||
test_enabled();
|
||||
test_sync();
|
||||
}
|
80
services/sync/tests/unit/test_enginemanager.js
Normal file
80
services/sync/tests/unit/test_enginemanager.js
Normal file
@ -0,0 +1,80 @@
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
|
||||
function run_test() {
|
||||
_("We start out with a clean slate");
|
||||
let engines = Engines.getAll();
|
||||
do_check_eq(engines.length, 0);
|
||||
do_check_eq(Engines.get('dummy'), undefined);
|
||||
|
||||
_("Register an engine");
|
||||
function DummyEngine() {}
|
||||
DummyEngine.prototype.name = "dummy";
|
||||
Engines.register(DummyEngine);
|
||||
let dummy = Engines.get('dummy');
|
||||
do_check_true(dummy instanceof DummyEngine);
|
||||
|
||||
engines = Engines.getAll();
|
||||
do_check_eq(engines.length, 1);
|
||||
do_check_eq(engines[0], dummy);
|
||||
|
||||
_("Register an already registered engine is ignored");
|
||||
Engines.register(DummyEngine);
|
||||
do_check_eq(Engines.get('dummy'), dummy);
|
||||
|
||||
_("Register multiple engines in one go");
|
||||
function PetrolEngine() {}
|
||||
PetrolEngine.prototype.name = "petrol";
|
||||
function DieselEngine() {}
|
||||
DieselEngine.prototype.name = "diesel";
|
||||
|
||||
Engines.register([PetrolEngine, DieselEngine]);
|
||||
let petrol = Engines.get('petrol');
|
||||
let diesel = Engines.get('diesel');
|
||||
do_check_true(petrol instanceof PetrolEngine);
|
||||
do_check_true(diesel instanceof DieselEngine);
|
||||
|
||||
engines = Engines.getAll();
|
||||
do_check_eq(engines.length, 3);
|
||||
do_check_neq(engines.indexOf(petrol), -1);
|
||||
do_check_neq(engines.indexOf(diesel), -1);
|
||||
|
||||
_("Retrieve multiple engines in one go");
|
||||
engines = Engines.get(["dummy", "diesel"]);
|
||||
do_check_eq(engines.length, 2);
|
||||
do_check_neq(engines.indexOf(dummy), -1);
|
||||
do_check_neq(engines.indexOf(diesel), -1);
|
||||
|
||||
_("getEnabled() only returns enabled engines");
|
||||
engines = Engines.getEnabled();
|
||||
do_check_eq(engines.length, 0);
|
||||
|
||||
petrol.enabled = true;
|
||||
engines = Engines.getEnabled();
|
||||
do_check_eq(engines.length, 1);
|
||||
do_check_eq(engines[0], petrol);
|
||||
|
||||
dummy.enabled = true;
|
||||
diesel.enabled = true;
|
||||
engines = Engines.getEnabled();
|
||||
do_check_eq(engines.length, 3);
|
||||
|
||||
_("Unregister an engine by name");
|
||||
Engines.unregister('dummy');
|
||||
do_check_eq(Engines.get('dummy'), undefined);
|
||||
engines = Engines.getAll();
|
||||
do_check_eq(engines.length, 2);
|
||||
do_check_eq(engines.indexOf(dummy), -1);
|
||||
|
||||
_("Unregister an engine by value");
|
||||
// Engines.unregister() checks for instanceof Engine, so let's make one:
|
||||
function ActualEngine() {}
|
||||
ActualEngine.prototype = {__proto__: Engine.prototype,
|
||||
name: 'actual'};
|
||||
Engines.register(ActualEngine);
|
||||
let actual = Engines.get('actual');
|
||||
do_check_true(actual instanceof ActualEngine);
|
||||
do_check_true(actual instanceof Engine);
|
||||
|
||||
Engines.unregister(actual);
|
||||
do_check_eq(Engines.get('actual'), undefined);
|
||||
}
|
81
services/sync/tests/unit/test_engines_forms_store.js
Normal file
81
services/sync/tests/unit/test_engines_forms_store.js
Normal file
@ -0,0 +1,81 @@
|
||||
_("Make sure the form store follows the Store api and correctly accesses the backend form storage");
|
||||
Cu.import("resource://services-sync/engines/forms.js");
|
||||
Cu.import("resource://services-sync/type_records/forms.js");
|
||||
|
||||
function run_test() {
|
||||
let store = new FormEngine()._store;
|
||||
|
||||
_("Remove any existing entries");
|
||||
store.wipe();
|
||||
for (let id in store.getAllIDs()) {
|
||||
do_throw("Shouldn't get any ids!");
|
||||
}
|
||||
|
||||
_("Add a form entry");
|
||||
store.create({
|
||||
name: "name!!",
|
||||
value: "value??"
|
||||
});
|
||||
|
||||
_("Should have 1 entry now");
|
||||
let id = "";
|
||||
for (let _id in store.getAllIDs()) {
|
||||
if (id == "")
|
||||
id = _id;
|
||||
else
|
||||
do_throw("Should have only gotten one!");
|
||||
}
|
||||
do_check_true(store.itemExists(id));
|
||||
|
||||
let rec = store.createRecord(id);
|
||||
_("Got record for id", id, rec);
|
||||
do_check_eq(rec.name, "name!!");
|
||||
do_check_eq(rec.value, "value??");
|
||||
|
||||
_("Create a non-existant id for delete");
|
||||
do_check_true(store.createRecord("deleted!!").deleted);
|
||||
|
||||
_("Try updating.. doesn't do anything yet");
|
||||
store.update({});
|
||||
|
||||
_("Remove all entries");
|
||||
store.wipe();
|
||||
for (let id in store.getAllIDs()) {
|
||||
do_throw("Shouldn't get any ids!");
|
||||
}
|
||||
|
||||
_("Add another entry");
|
||||
store.create({
|
||||
name: "another",
|
||||
value: "entry"
|
||||
});
|
||||
id = "";
|
||||
for (let _id in store.getAllIDs()) {
|
||||
if (id == "")
|
||||
id = _id;
|
||||
else
|
||||
do_throw("Should have only gotten one!");
|
||||
}
|
||||
|
||||
_("Change the id of the new entry to something else");
|
||||
store.changeItemID(id, "newid");
|
||||
|
||||
_("Make sure it's there");
|
||||
do_check_true(store.itemExists("newid"));
|
||||
|
||||
_("Remove the entry");
|
||||
store.remove({
|
||||
id: "newid"
|
||||
});
|
||||
for (let id in store.getAllIDs()) {
|
||||
do_throw("Shouldn't get any ids!");
|
||||
}
|
||||
|
||||
_("Removing the entry again shouldn't matter");
|
||||
store.remove({
|
||||
id: "newid"
|
||||
});
|
||||
for (let id in store.getAllIDs()) {
|
||||
do_throw("Shouldn't get any ids!");
|
||||
}
|
||||
}
|
41
services/sync/tests/unit/test_load_modules.js
Normal file
41
services/sync/tests/unit/test_load_modules.js
Normal file
@ -0,0 +1,41 @@
|
||||
const modules = [
|
||||
"auth.js",
|
||||
"base_records/collection.js",
|
||||
"base_records/crypto.js",
|
||||
"base_records/keys.js",
|
||||
"base_records/wbo.js",
|
||||
"constants.js",
|
||||
"engines/bookmarks.js",
|
||||
"engines/clients.js",
|
||||
"engines/forms.js",
|
||||
"engines/history.js",
|
||||
"engines/passwords.js",
|
||||
"engines/prefs.js",
|
||||
"engines/tabs.js",
|
||||
"engines.js",
|
||||
"ext/Observers.js",
|
||||
"ext/Preferences.js",
|
||||
"identity.js",
|
||||
"log4moz.js",
|
||||
"notifications.js",
|
||||
"resource.js",
|
||||
"service.js",
|
||||
"stores.js",
|
||||
"trackers.js",
|
||||
"type_records/bookmark.js",
|
||||
"type_records/clients.js",
|
||||
"type_records/forms.js",
|
||||
"type_records/history.js",
|
||||
"type_records/passwords.js",
|
||||
"type_records/prefs.js",
|
||||
"type_records/tabs.js",
|
||||
"util.js",
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for each (let m in modules) {
|
||||
_("Attempting to load resource://services-sync/" + m);
|
||||
Cu.import("resource://services-sync/" + m, {});
|
||||
}
|
||||
}
|
||||
|
45
services/sync/tests/unit/test_log4moz.js
Normal file
45
services/sync/tests/unit/test_log4moz.js
Normal file
@ -0,0 +1,45 @@
|
||||
Components.utils.import("resource://services-sync/log4moz.js");
|
||||
|
||||
function MockAppender(formatter) {
|
||||
this._formatter = formatter;
|
||||
this.messages = [];
|
||||
}
|
||||
MockAppender.prototype = {
|
||||
doAppend: function DApp_doAppend(message) {
|
||||
this.messages.push(message);
|
||||
}
|
||||
};
|
||||
MockAppender.prototype.__proto__ = new Log4Moz.Appender();
|
||||
|
||||
function run_test() {
|
||||
var log = Log4Moz.repository.rootLogger;
|
||||
var appender = new MockAppender(new Log4Moz.BasicFormatter());
|
||||
|
||||
log.level = Log4Moz.Level.Debug;
|
||||
appender.level = Log4Moz.Level.Info;
|
||||
log.addAppender(appender);
|
||||
log.info("info test");
|
||||
log.debug("this should be logged but not appended.");
|
||||
|
||||
do_check_eq(appender.messages.length, 1);
|
||||
do_check_true(appender.messages[0].indexOf("info test") > 0);
|
||||
do_check_true(appender.messages[0].indexOf("INFO") > 0);
|
||||
|
||||
// Test - check whether parenting is correct
|
||||
let grandparentLog = Log4Moz.repository.getLogger("grandparent");
|
||||
let childLog = Log4Moz.repository.getLogger("grandparent.parent.child");
|
||||
do_check_eq(childLog.parent.name, "grandparent");
|
||||
|
||||
let parentLog = Log4Moz.repository.getLogger("grandparent.parent");
|
||||
do_check_eq(childLog.parent.name, "grandparent.parent");
|
||||
|
||||
// Test - check that appends are exactly in scope
|
||||
let gpAppender = new MockAppender(new Log4Moz.BasicFormatter());
|
||||
gpAppender.level = Log4Moz.Level.Info;
|
||||
grandparentLog.addAppender(gpAppender);
|
||||
childLog.info("child info test");
|
||||
log.info("this shouldn't show up in gpAppender");
|
||||
|
||||
do_check_eq(gpAppender.messages.length, 1);
|
||||
do_check_true(gpAppender.messages[0].indexOf("child info test") > 0);
|
||||
}
|
32
services/sync/tests/unit/test_notifications.js
Normal file
32
services/sync/tests/unit/test_notifications.js
Normal file
@ -0,0 +1,32 @@
|
||||
Cu.import("resource://services-sync/notifications.js");
|
||||
|
||||
function run_test() {
|
||||
var logStats = initTestLogging("Info");
|
||||
|
||||
var blah = 0;
|
||||
|
||||
function callback(i) {
|
||||
blah = i;
|
||||
}
|
||||
|
||||
let button = new NotificationButton("label", "accessKey", callback);
|
||||
|
||||
button.callback(5);
|
||||
|
||||
do_check_eq(blah, 5);
|
||||
do_check_eq(logStats.errorsLogged, 0);
|
||||
|
||||
function badCallback() {
|
||||
throw new Error("oops");
|
||||
}
|
||||
|
||||
button = new NotificationButton("label", "accessKey", badCallback);
|
||||
|
||||
try {
|
||||
button.callback();
|
||||
} catch (e) {
|
||||
do_check_eq(e.message, "oops");
|
||||
}
|
||||
|
||||
do_check_eq(logStats.errorsLogged, 1);
|
||||
}
|
123
services/sync/tests/unit/test_records_crypto.js
Normal file
123
services/sync/tests/unit/test_records_crypto.js
Normal file
@ -0,0 +1,123 @@
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
let keys, cryptoMeta, cryptoWrap;
|
||||
|
||||
function pubkey_handler(metadata, response) {
|
||||
let obj = {id: "ignore-me",
|
||||
modified: keys.pubkey.modified,
|
||||
payload: JSON.stringify(keys.pubkey.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function privkey_handler(metadata, response) {
|
||||
let obj = {id: "ignore-me-2",
|
||||
modified: keys.privkey.modified,
|
||||
payload: JSON.stringify(keys.privkey.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function crypted_resource_handler(metadata, response) {
|
||||
let obj = {id: "ignore-me-3",
|
||||
modified: cryptoWrap.modified,
|
||||
payload: JSON.stringify(cryptoWrap.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function crypto_meta_handler(metadata, response) {
|
||||
let obj = {id: "ignore-me-4",
|
||||
modified: cryptoMeta.modified,
|
||||
payload: JSON.stringify(cryptoMeta.payload)};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let server;
|
||||
|
||||
try {
|
||||
let log = Log4Moz.repository.getLogger();
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
log.info("Setting up server and authenticator");
|
||||
|
||||
server = httpd_setup({"/pubkey": pubkey_handler,
|
||||
"/privkey": privkey_handler,
|
||||
"/crypted-resource": crypted_resource_handler,
|
||||
"/crypto-meta": crypto_meta_handler});
|
||||
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
Auth.defaultAuthenticator = auth;
|
||||
|
||||
log.info("Generating keypair + symmetric key");
|
||||
|
||||
PubKeys.defaultKeyUri = "http://localhost:8080/pubkey";
|
||||
keys = PubKeys.createKeypair(passphrase,
|
||||
"http://localhost:8080/pubkey",
|
||||
"http://localhost:8080/privkey");
|
||||
let crypto = Svc.Crypto;
|
||||
keys.symkey = crypto.generateRandomKey();
|
||||
keys.wrappedkey = crypto.wrapSymmetricKey(keys.symkey, keys.pubkey.keyData);
|
||||
|
||||
log.info("Setting up keyring");
|
||||
|
||||
cryptoMeta = new CryptoMeta("http://localhost:8080/crypto-meta", auth);
|
||||
cryptoMeta.addUnwrappedKey(keys.pubkey, keys.symkey);
|
||||
CryptoMetas.set(cryptoMeta.uri, cryptoMeta);
|
||||
|
||||
log.info("Creating and encrypting a record");
|
||||
|
||||
cryptoWrap = new CryptoWrapper("http://localhost:8080/crypted-resource", auth);
|
||||
cryptoWrap.encryption = "http://localhost:8080/crypto-meta";
|
||||
cryptoWrap.cleartext.stuff = "my payload here";
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
let firstIV = cryptoWrap.IV;
|
||||
|
||||
log.info("Decrypting the record");
|
||||
|
||||
let payload = cryptoWrap.decrypt(passphrase);
|
||||
do_check_eq(payload.stuff, "my payload here");
|
||||
do_check_neq(payload, cryptoWrap.payload); // wrap.data.payload is the encrypted one
|
||||
|
||||
log.info("Re-encrypting the record with alternate payload");
|
||||
|
||||
cryptoWrap.cleartext.stuff = "another payload";
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
let secondIV = cryptoWrap.IV;
|
||||
payload = cryptoWrap.decrypt(passphrase);
|
||||
do_check_eq(payload.stuff, "another payload");
|
||||
|
||||
log.info("Make sure multiple encrypts use different IVs");
|
||||
do_check_neq(firstIV, secondIV);
|
||||
|
||||
log.info("Make sure differing ids cause failures");
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
cryptoWrap.data.id = "other";
|
||||
let error = "";
|
||||
try {
|
||||
cryptoWrap.decrypt(passphrase);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Record id mismatch: crypted-resource,other");
|
||||
|
||||
log.info("Make sure wrong hmacs cause failures");
|
||||
cryptoWrap.encrypt(passphrase);
|
||||
cryptoWrap.hmac = "foo";
|
||||
error = "";
|
||||
try {
|
||||
cryptoWrap.decrypt(passphrase);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Record SHA256 HMAC mismatch: foo");
|
||||
|
||||
log.info("Done!");
|
||||
}
|
||||
finally { server.stop(function() {}); }
|
||||
}
|
43
services/sync/tests/unit/test_records_cryptometa.js
Normal file
43
services/sync/tests/unit/test_records_cryptometa.js
Normal file
@ -0,0 +1,43 @@
|
||||
Cu.import("resource://services-sync/base_records/crypto.js");
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Generating keypair to encrypt/decrypt symkeys");
|
||||
let {pubkey, privkey} = PubKeys.createKeypair(
|
||||
passphrase,
|
||||
"http://site/pubkey",
|
||||
"http://site/privkey"
|
||||
);
|
||||
PubKeys.set(pubkey.uri, pubkey);
|
||||
PrivKeys.set(privkey.uri, privkey);
|
||||
|
||||
_("Generating a crypto meta with a random key");
|
||||
let crypto = new CryptoMeta("http://site/crypto");
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
crypto.addUnwrappedKey(pubkey, symkey);
|
||||
|
||||
_("Verifying correct HMAC by getting the key");
|
||||
crypto.getKey(privkey, passphrase);
|
||||
|
||||
_("Generating a new crypto meta as the previous caches the unwrapped key");
|
||||
let crypto = new CryptoMeta("http://site/crypto");
|
||||
let symkey = Svc.Crypto.generateRandomKey();
|
||||
crypto.addUnwrappedKey(pubkey, symkey);
|
||||
|
||||
_("Changing the HMAC to force a mismatch");
|
||||
let goodHMAC = crypto.keyring[pubkey.uri.spec].hmac;
|
||||
crypto.keyring[pubkey.uri.spec].hmac = "failme!";
|
||||
let error = "";
|
||||
try {
|
||||
crypto.getKey(privkey, passphrase);
|
||||
}
|
||||
catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error, "Key SHA256 HMAC mismatch: failme!");
|
||||
|
||||
_("Switching back to the correct HMAC and trying again");
|
||||
crypto.keyring[pubkey.uri.spec].hmac = goodHMAC;
|
||||
crypto.getKey(privkey, passphrase);
|
||||
}
|
58
services/sync/tests/unit/test_records_keys.js
Normal file
58
services/sync/tests/unit/test_records_keys.js
Normal file
@ -0,0 +1,58 @@
|
||||
try {
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
} catch (e) { do_throw(e); }
|
||||
|
||||
function pubkey_handler(metadata, response) {
|
||||
let obj = {id: "asdf-1234-asdf-1234",
|
||||
modified: "2454725.98283",
|
||||
payload: JSON.stringify({type: "pubkey",
|
||||
privateKeyUri: "http://localhost:8080/privkey",
|
||||
keyData: "asdfasdfasf..."})};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function privkey_handler(metadata, response) {
|
||||
let obj = {id: "asdf-1234-asdf-1234-2",
|
||||
modified: "2454725.98283",
|
||||
payload: JSON.stringify({type: "privkey",
|
||||
publicKeyUri: "http://localhost:8080/pubkey",
|
||||
keyData: "asdfasdfasf..."})};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let server;
|
||||
|
||||
try {
|
||||
let log = Log4Moz.repository.getLogger();
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
log.info("Setting up server and authenticator");
|
||||
|
||||
server = httpd_setup({"/pubkey": pubkey_handler,
|
||||
"/privkey": privkey_handler});
|
||||
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
Auth.defaultAuthenticator = auth;
|
||||
|
||||
log.info("Getting a public key");
|
||||
|
||||
let pubkey = PubKeys.get("http://localhost:8080/pubkey");
|
||||
do_check_eq(pubkey.data.payload.type, "pubkey");
|
||||
do_check_eq(PubKeys.response.status, 200);
|
||||
|
||||
log.info("Getting matching private key");
|
||||
|
||||
let privkey = PrivKeys.get(pubkey.privateKeyUri);
|
||||
do_check_eq(privkey.data.payload.type, "privkey");
|
||||
do_check_eq(PrivKeys.response.status, 200);
|
||||
|
||||
log.info("Done!");
|
||||
}
|
||||
catch (e) { do_throw(e); }
|
||||
finally { server.stop(function() {}); }
|
||||
}
|
78
services/sync/tests/unit/test_records_wbo.js
Normal file
78
services/sync/tests/unit/test_records_wbo.js
Normal file
@ -0,0 +1,78 @@
|
||||
try {
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/base_records/wbo.js");
|
||||
Cu.import("resource://services-sync/base_records/collection.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
} catch (e) { do_throw(e); }
|
||||
|
||||
function record_handler(metadata, response) {
|
||||
let obj = {id: "asdf-1234-asdf-1234",
|
||||
modified: 2454725.98283,
|
||||
payload: JSON.stringify({cheese: "roquefort"})};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function record_handler2(metadata, response) {
|
||||
let obj = {id: "record2",
|
||||
modified: 2454725.98284,
|
||||
payload: JSON.stringify({cheese: "gruyere"})};
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function coll_handler(metadata, response) {
|
||||
let obj = [{id: "record2",
|
||||
modified: 2454725.98284,
|
||||
payload: JSON.stringify({cheese: "gruyere"})}];
|
||||
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let server;
|
||||
|
||||
try {
|
||||
let log = Log4Moz.repository.getLogger('Test');
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
log.info("Setting up server and authenticator");
|
||||
|
||||
server = httpd_setup({"/record": record_handler,
|
||||
"/record2": record_handler2,
|
||||
"/coll": coll_handler});
|
||||
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
Auth.defaultAuthenticator = auth;
|
||||
|
||||
log.info("Getting a WBO record");
|
||||
|
||||
let res = new Resource("http://localhost:8080/record");
|
||||
let resp = res.get();
|
||||
|
||||
let rec = new WBORecord();
|
||||
rec.deserialize(res.data);
|
||||
do_check_eq(rec.id, "asdf-1234-asdf-1234"); // NOT "record"!
|
||||
|
||||
rec.uri = res.uri;
|
||||
do_check_eq(rec.id, "record"); // NOT "asdf-1234-asdf-1234"!
|
||||
|
||||
do_check_eq(rec.modified, 2454725.98283);
|
||||
do_check_eq(typeof(rec.payload), "object");
|
||||
do_check_eq(rec.payload.cheese, "roquefort");
|
||||
do_check_eq(resp.status, 200);
|
||||
|
||||
log.info("Getting a WBO record using the record manager");
|
||||
|
||||
let rec2 = Records.get("http://localhost:8080/record2");
|
||||
do_check_eq(rec2.id, "record2");
|
||||
do_check_eq(rec2.modified, 2454725.98284);
|
||||
do_check_eq(typeof(rec2.payload), "object");
|
||||
do_check_eq(rec2.payload.cheese, "gruyere");
|
||||
do_check_eq(Records.response.status, 200);
|
||||
|
||||
log.info("Done!");
|
||||
}
|
||||
catch (e) { do_throw(e); }
|
||||
finally { server.stop(function() {}); }
|
||||
}
|
380
services/sync/tests/unit/test_resource.js
Normal file
380
services/sync/tests/unit/test_resource.js
Normal file
@ -0,0 +1,380 @@
|
||||
Cu.import("resource://services-sync/auth.js");
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/resource.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
let logger;
|
||||
|
||||
function server_open(metadata, response) {
|
||||
let body;
|
||||
if (metadata.method == "GET") {
|
||||
body = "This path exists";
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
} else {
|
||||
body = "Wrong request method";
|
||||
response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed");
|
||||
}
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function server_protected(metadata, response) {
|
||||
let body;
|
||||
|
||||
// no btoa() in xpcshell. it's guest:guest
|
||||
if (metadata.hasHeader("Authorization") &&
|
||||
metadata.getHeader("Authorization") == "Basic Z3Vlc3Q6Z3Vlc3Q=") {
|
||||
body = "This path exists and is protected";
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
} else {
|
||||
body = "This path exists and is protected - failed";
|
||||
response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
|
||||
response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
|
||||
}
|
||||
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function server_404(metadata, response) {
|
||||
let body = "File not found";
|
||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
|
||||
let sample_data = {
|
||||
some: "sample_data",
|
||||
injson: "format",
|
||||
number: 42
|
||||
};
|
||||
|
||||
function server_upload(metadata, response) {
|
||||
let body;
|
||||
|
||||
let input = readBytesFromInputStream(metadata.bodyInputStream);
|
||||
if (input == JSON.stringify(sample_data)) {
|
||||
body = "Valid data upload via " + metadata.method;
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
} else {
|
||||
body = "Invalid data upload via " + metadata.method + ': ' + input;
|
||||
response.setStatusLine(metadata.httpVersion, 500, "Internal Server Error");
|
||||
}
|
||||
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function server_delete(metadata, response) {
|
||||
let body;
|
||||
if (metadata.method == "DELETE") {
|
||||
body = "This resource has been deleted";
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
} else {
|
||||
body = "Wrong request method";
|
||||
response.setStatusLine(metadata.httpVersion, 405, "Method Not Allowed");
|
||||
}
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function server_json(metadata, response) {
|
||||
let body = JSON.stringify(sample_data);
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
const TIMESTAMP = 1274380461;
|
||||
|
||||
function server_timestamp(metadata, response) {
|
||||
let body = "Thank you for your request";
|
||||
response.setHeader("X-Weave-Timestamp", ''+TIMESTAMP, false);
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function server_backoff(metadata, response) {
|
||||
let body = "Hey, back off!";
|
||||
response.setHeader("X-Weave-Backoff", '600', false);
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
|
||||
function server_headers(metadata, response) {
|
||||
let ignore_headers = ["host", "user-agent", "accept", "accept-language",
|
||||
"accept-encoding", "accept-charset", "keep-alive",
|
||||
"connection", "pragma", "cache-control",
|
||||
"content-length"];
|
||||
let headers = metadata.headers;
|
||||
let header_names = [];
|
||||
while (headers.hasMoreElements()) {
|
||||
let header = headers.getNext().toString();
|
||||
if (ignore_headers.indexOf(header) == -1) {
|
||||
header_names.push(header);
|
||||
}
|
||||
}
|
||||
header_names = header_names.sort();
|
||||
|
||||
headers = {};
|
||||
for each (let header in header_names) {
|
||||
headers[header] = metadata.getHeader(header);
|
||||
}
|
||||
let body = JSON.stringify(headers);
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
|
||||
function run_test() {
|
||||
logger = Log4Moz.repository.getLogger('Test');
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let server = new nsHttpServer();
|
||||
server.registerPathHandler("/open", server_open);
|
||||
server.registerPathHandler("/protected", server_protected);
|
||||
server.registerPathHandler("/404", server_404);
|
||||
server.registerPathHandler("/upload", server_upload);
|
||||
server.registerPathHandler("/delete", server_delete);
|
||||
server.registerPathHandler("/json", server_json);
|
||||
server.registerPathHandler("/timestamp", server_timestamp);
|
||||
server.registerPathHandler("/headers", server_headers);
|
||||
server.registerPathHandler("/backoff", server_backoff);
|
||||
server.start(8080);
|
||||
|
||||
Utils.prefs.setIntPref("network.numRetries", 1); // speed up test
|
||||
|
||||
_("Resource object memebers");
|
||||
let res = new Resource("http://localhost:8080/open");
|
||||
do_check_true(res.uri instanceof Ci.nsIURI);
|
||||
do_check_eq(res.uri.spec, "http://localhost:8080/open");
|
||||
do_check_eq(res.spec, "http://localhost:8080/open");
|
||||
do_check_eq(typeof res.headers, "object");
|
||||
do_check_eq(typeof res.authenticator, "object");
|
||||
// Initially res.data is null since we haven't performed a GET or
|
||||
// PUT/POST request yet.
|
||||
do_check_eq(res.data, null);
|
||||
|
||||
_("GET a non-password-protected resource");
|
||||
let content = res.get();
|
||||
do_check_eq(content, "This path exists");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_true(content.success);
|
||||
// res.data has been updated with the result from the request
|
||||
do_check_eq(res.data, content);
|
||||
|
||||
// Since we didn't receive proper JSON data, accessing content.obj
|
||||
// will result in a SyntaxError from JSON.parse
|
||||
let didThrow = false;
|
||||
try {
|
||||
content.obj;
|
||||
} catch (ex) {
|
||||
didThrow = true;
|
||||
}
|
||||
do_check_true(didThrow);
|
||||
|
||||
let did401 = false;
|
||||
Observers.add("weave:resource:status:401", function() did401 = true);
|
||||
|
||||
_("GET a password protected resource (test that it'll fail w/o pass, no throw)");
|
||||
let res2 = new Resource("http://localhost:8080/protected");
|
||||
content = res2.get();
|
||||
do_check_true(did401);
|
||||
do_check_eq(content, "This path exists and is protected - failed")
|
||||
do_check_eq(content.status, 401);
|
||||
do_check_false(content.success);
|
||||
|
||||
_("GET a password protected resource");
|
||||
let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest"));
|
||||
let res3 = new Resource("http://localhost:8080/protected");
|
||||
res3.authenticator = auth;
|
||||
do_check_eq(res3.authenticator, auth);
|
||||
content = res3.get();
|
||||
do_check_eq(content, "This path exists and is protected");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_true(content.success);
|
||||
|
||||
_("GET a non-existent resource (test that it'll fail, but not throw)");
|
||||
let res4 = new Resource("http://localhost:8080/404");
|
||||
content = res4.get();
|
||||
do_check_eq(content, "File not found");
|
||||
do_check_eq(content.status, 404);
|
||||
do_check_false(content.success);
|
||||
|
||||
// Check some headers of the 404 response
|
||||
do_check_eq(content.headers.connection, "close");
|
||||
do_check_eq(content.headers.server, "httpd.js");
|
||||
do_check_eq(content.headers["content-length"], 14);
|
||||
|
||||
_("PUT to a resource (string)");
|
||||
let res5 = new Resource("http://localhost:8080/upload");
|
||||
content = res5.put(JSON.stringify(sample_data));
|
||||
do_check_eq(content, "Valid data upload via PUT");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("PUT to a resource (object)");
|
||||
content = res5.put(sample_data);
|
||||
do_check_eq(content, "Valid data upload via PUT");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("PUT without data arg (uses resource.data) (string)");
|
||||
res5.data = JSON.stringify(sample_data);
|
||||
content = res5.put();
|
||||
do_check_eq(content, "Valid data upload via PUT");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("PUT without data arg (uses resource.data) (object)");
|
||||
res5.data = sample_data;
|
||||
content = res5.put();
|
||||
do_check_eq(content, "Valid data upload via PUT");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("POST to a resource (string)");
|
||||
content = res5.post(JSON.stringify(sample_data));
|
||||
do_check_eq(content, "Valid data upload via POST");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("POST to a resource (object)");
|
||||
content = res5.post(sample_data);
|
||||
do_check_eq(content, "Valid data upload via POST");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("POST without data arg (uses resource.data) (string)");
|
||||
res5.data = JSON.stringify(sample_data);
|
||||
content = res5.post();
|
||||
do_check_eq(content, "Valid data upload via POST");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("POST without data arg (uses resource.data) (object)");
|
||||
res5.data = sample_data;
|
||||
content = res5.post();
|
||||
do_check_eq(content, "Valid data upload via POST");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(res5.data, content);
|
||||
|
||||
_("DELETE a resource");
|
||||
let res6 = new Resource("http://localhost:8080/delete");
|
||||
content = res6.delete();
|
||||
do_check_eq(content, "This resource has been deleted")
|
||||
do_check_eq(content.status, 200);
|
||||
|
||||
_("JSON conversion of response body");
|
||||
let res7 = new Resource("http://localhost:8080/json");
|
||||
content = res7.get();
|
||||
do_check_eq(content, JSON.stringify(sample_data));
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_eq(JSON.stringify(content.obj), JSON.stringify(sample_data));
|
||||
|
||||
_("X-Weave-Timestamp header updates Resource.serverTime");
|
||||
// Before having received any response containing the
|
||||
// X-Weave-Timestamp header, Resource.serverTime is null.
|
||||
do_check_eq(Resource.serverTime, null);
|
||||
let res8 = new Resource("http://localhost:8080/timestamp");
|
||||
content = res8.get();
|
||||
do_check_eq(Resource.serverTime, TIMESTAMP);
|
||||
|
||||
|
||||
_("GET: no special request headers");
|
||||
let res9 = new Resource("http://localhost:8080/headers");
|
||||
content = res9.get();
|
||||
do_check_eq(content, '{}');
|
||||
|
||||
_("PUT: Content-Type defaults to text/plain");
|
||||
content = res9.put('data');
|
||||
do_check_eq(content, JSON.stringify({"content-type": "text/plain"}));
|
||||
|
||||
_("POST: Content-Type defaults to text/plain");
|
||||
content = res9.post('data');
|
||||
do_check_eq(content, JSON.stringify({"content-type": "text/plain"}));
|
||||
|
||||
_("setHeader(): setting simple header");
|
||||
res9.setHeader('X-What-Is-Weave', 'awesome');
|
||||
do_check_eq(res9.headers['x-what-is-weave'], 'awesome');
|
||||
content = res9.get();
|
||||
do_check_eq(content, JSON.stringify({"x-what-is-weave": "awesome"}));
|
||||
|
||||
_("setHeader(): setting multiple headers, overwriting existing header");
|
||||
res9.setHeader('X-WHAT-is-Weave', 'more awesomer',
|
||||
'X-Another-Header', 'hello world');
|
||||
do_check_eq(res9.headers['x-what-is-weave'], 'more awesomer');
|
||||
do_check_eq(res9.headers['x-another-header'], 'hello world');
|
||||
content = res9.get();
|
||||
do_check_eq(content, JSON.stringify({"x-another-header": "hello world",
|
||||
"x-what-is-weave": "more awesomer"}));
|
||||
|
||||
_("Setting headers object");
|
||||
res9.headers = {};
|
||||
content = res9.get();
|
||||
do_check_eq(content, "{}");
|
||||
|
||||
_("PUT/POST: override default Content-Type");
|
||||
res9.setHeader('Content-Type', 'application/foobar');
|
||||
do_check_eq(res9.headers['content-type'], 'application/foobar');
|
||||
content = res9.put('data');
|
||||
do_check_eq(content, JSON.stringify({"content-type": "application/foobar"}));
|
||||
content = res9.post('data');
|
||||
do_check_eq(content, JSON.stringify({"content-type": "application/foobar"}));
|
||||
|
||||
|
||||
_("X-Weave-Backoff header notifies observer");
|
||||
let backoffInterval;
|
||||
function onBackoff(subject, data) {
|
||||
backoffInterval = subject;
|
||||
}
|
||||
Observers.add("weave:service:backoff:interval", onBackoff);
|
||||
|
||||
let res10 = new Resource("http://localhost:8080/backoff");
|
||||
content = res10.get();
|
||||
do_check_eq(backoffInterval, 600);
|
||||
|
||||
_("Error handling in _request() preserves exception information");
|
||||
let error;
|
||||
let res11 = new Resource("http://localhost:12345/does/not/exist");
|
||||
try {
|
||||
content = res11.get();
|
||||
} catch(ex) {
|
||||
error = ex;
|
||||
}
|
||||
do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED");
|
||||
do_check_eq(typeof error.stack, "string");
|
||||
|
||||
let redirRequest;
|
||||
let redirToOpen = function(subject) {
|
||||
subject.newUri = "http://localhost:8080/open";
|
||||
redirRequest = subject;
|
||||
};
|
||||
Observers.add("weave:resource:status:401", redirToOpen);
|
||||
|
||||
_("Notification of 401 can redirect to another uri");
|
||||
did401 = false;
|
||||
let res12 = new Resource("http://localhost:8080/protected");
|
||||
content = res12.get();
|
||||
do_check_eq(res12.spec, "http://localhost:8080/open");
|
||||
do_check_eq(content, "This path exists");
|
||||
do_check_eq(content.status, 200);
|
||||
do_check_true(content.success);
|
||||
do_check_eq(res.data, content);
|
||||
do_check_true(did401);
|
||||
do_check_eq(redirRequest.response, "This path exists and is protected - failed")
|
||||
do_check_eq(redirRequest.response.status, 401);
|
||||
do_check_false(redirRequest.response.success);
|
||||
|
||||
Observers.remove("weave:resource:status:401", redirToOpen);
|
||||
|
||||
_("Removing the observer should result in the original 401");
|
||||
did401 = false;
|
||||
let res13 = new Resource("http://localhost:8080/protected");
|
||||
content = res13.get();
|
||||
do_check_true(did401);
|
||||
do_check_eq(content, "This path exists and is protected - failed")
|
||||
do_check_eq(content.status, 401);
|
||||
do_check_false(content.success);
|
||||
|
||||
server.stop(function() {});
|
||||
}
|
202
services/sync/tests/unit/test_service_attributes.js
Normal file
202
services/sync/tests/unit/test_service_attributes.js
Normal file
@ -0,0 +1,202 @@
|
||||
Cu.import("resource://services-sync/base_records/keys.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function test_urlsAndIdentities() {
|
||||
_("Various Weave.Service properties correspond to preference settings and update other object properties upon being set.");
|
||||
|
||||
try {
|
||||
_("Verify initial state");
|
||||
do_check_eq(Svc.Prefs.get("username"), undefined);
|
||||
do_check_eq(PubKeys.defaultKeyUri, undefined);
|
||||
do_check_eq(PrivKeys.defaultKeyUri, undefined);
|
||||
do_check_eq(ID.get("WeaveID").username, "");
|
||||
do_check_eq(ID.get("WeaveCryptoID").username, "");
|
||||
|
||||
do_check_true(!!Weave.Service.serverURL); // actual value may change
|
||||
do_check_eq(Weave.Service.clusterURL, "");
|
||||
do_check_eq(Weave.Service.infoURL, undefined);
|
||||
do_check_eq(Weave.Service.storageURL, undefined);
|
||||
do_check_eq(Weave.Service.metaURL, undefined);
|
||||
|
||||
_("The 'username' attribute is normalized to lower case, updates preferences and identities.");
|
||||
Weave.Service.username = "TarZan";
|
||||
do_check_eq(Weave.Service.username, "tarzan");
|
||||
do_check_eq(Svc.Prefs.get("username"), "tarzan");
|
||||
do_check_eq(ID.get("WeaveID").username, "tarzan");
|
||||
do_check_eq(ID.get("WeaveCryptoID").username, "tarzan");
|
||||
|
||||
// Since we don't have a cluster URL yet, these will still not be defined.
|
||||
do_check_eq(Weave.Service.infoURL, undefined);
|
||||
do_check_eq(Weave.Service.storageURL, undefined);
|
||||
do_check_eq(Weave.Service.metaURL, undefined);
|
||||
do_check_eq(PubKeys.defaultKeyUri, undefined);
|
||||
do_check_eq(PrivKeys.defaultKeyUri, undefined);
|
||||
|
||||
_("Tabs are stripped from the 'username' attribute as they can't be part of a URI.");
|
||||
Weave.Service.username = "jo\thn\tdoe";
|
||||
|
||||
do_check_eq(Weave.Service.username, "johndoe");
|
||||
do_check_eq(Svc.Prefs.get("username"), "johndoe");
|
||||
do_check_eq(ID.get("WeaveID").username, "johndoe");
|
||||
do_check_eq(ID.get("WeaveCryptoID").username, "johndoe");
|
||||
|
||||
_("The 'clusterURL' attribute updates preferences and cached URLs.");
|
||||
Weave.Service.serverURL = "http://weave.server/";
|
||||
Weave.Service.clusterURL = "http://weave.cluster/";
|
||||
do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/");
|
||||
|
||||
do_check_eq(Weave.Service.infoURL,
|
||||
"http://weave.cluster/1.0/johndoe/info/collections");
|
||||
do_check_eq(Weave.Service.storageURL,
|
||||
"http://weave.cluster/1.0/johndoe/storage/");
|
||||
do_check_eq(Weave.Service.metaURL,
|
||||
"http://weave.cluster/1.0/johndoe/storage/meta/global");
|
||||
do_check_eq(PubKeys.defaultKeyUri,
|
||||
"http://weave.cluster/1.0/johndoe/storage/keys/pubkey");
|
||||
do_check_eq(PrivKeys.defaultKeyUri,
|
||||
"http://weave.cluster/1.0/johndoe/storage/keys/privkey");
|
||||
|
||||
_("The 'miscURL' and 'userURL' attributes can be relative to 'serverURL' or absolute.");
|
||||
Svc.Prefs.set("miscURL", "relative/misc/");
|
||||
Svc.Prefs.set("userURL", "relative/user/");
|
||||
do_check_eq(Weave.Service.miscAPI,
|
||||
"http://weave.server/relative/misc/1.0/");
|
||||
do_check_eq(Weave.Service.userAPI,
|
||||
"http://weave.server/relative/user/1.0/");
|
||||
|
||||
Svc.Prefs.set("miscURL", "http://weave.misc.services/");
|
||||
Svc.Prefs.set("userURL", "http://weave.user.services/");
|
||||
do_check_eq(Weave.Service.miscAPI, "http://weave.misc.services/1.0/");
|
||||
do_check_eq(Weave.Service.userAPI, "http://weave.user.services/1.0/");
|
||||
|
||||
do_check_eq(Weave.Service.pwResetURL,
|
||||
"http://weave.server/weave-password-reset");
|
||||
|
||||
_("Empty/false value for 'username' resets preference.");
|
||||
Weave.Service.username = "";
|
||||
do_check_eq(Svc.Prefs.get("username"), undefined);
|
||||
do_check_eq(ID.get("WeaveID").username, "");
|
||||
do_check_eq(ID.get("WeaveCryptoID").username, "");
|
||||
|
||||
_("The 'serverURL' attributes updates/resets preferences.");
|
||||
// Identical value doesn't do anything
|
||||
Weave.Service.serverURL = Weave.Service.serverURL;
|
||||
do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/");
|
||||
|
||||
Weave.Service.serverURL = "http://different.auth.node/";
|
||||
do_check_eq(Svc.Prefs.get("serverURL"), "http://different.auth.node/");
|
||||
do_check_eq(Svc.Prefs.get("clusterURL"), undefined);
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test_syncID() {
|
||||
_("Weave.Service.syncID is auto-generated, corresponds to preference.");
|
||||
new FakeGUIDService();
|
||||
|
||||
try {
|
||||
// Ensure pristine environment
|
||||
do_check_eq(Svc.Prefs.get("client.syncID"), undefined);
|
||||
|
||||
// Performing the first get on the attribute will generate a new GUID.
|
||||
do_check_eq(Weave.Service.syncID, "fake-guid-0");
|
||||
do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-0");
|
||||
|
||||
Svc.Prefs.set("client.syncID", Utils.makeGUID());
|
||||
do_check_eq(Svc.Prefs.get("client.syncID"), "fake-guid-1");
|
||||
do_check_eq(Weave.Service.syncID, "fake-guid-1");
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
new FakeGUIDService();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test_prefAttributes() {
|
||||
_("Test various attributes corresponding to preferences.");
|
||||
|
||||
const TIMESTAMP1 = 1275493471649;
|
||||
const TIMESTAMP2 = 1275493741122;
|
||||
const INTERVAL = 42 * 60 * 1000; // 42 minutes
|
||||
const THRESHOLD = 3142;
|
||||
const SCORE = 2718;
|
||||
const NUMCLIENTS = 42;
|
||||
|
||||
try {
|
||||
_("The 'nextSync' and 'nextHeartbeat' attributes store a millisecond timestamp to the nearest second.");
|
||||
do_check_eq(Weave.Service.nextSync, 0);
|
||||
do_check_eq(Weave.Service.nextHeartbeat, 0);
|
||||
Weave.Service.nextSync = TIMESTAMP1;
|
||||
Weave.Service.nextHeartbeat = TIMESTAMP2;
|
||||
do_check_eq(Weave.Service.nextSync, Math.floor(TIMESTAMP1/1000)*1000);
|
||||
do_check_eq(Weave.Service.nextHeartbeat, Math.floor(TIMESTAMP2/1000)*1000);
|
||||
|
||||
_("'syncInterval' has a non-zero default value.");
|
||||
do_check_eq(Svc.Prefs.get('syncInterval'), undefined);
|
||||
do_check_true(Weave.Service.syncInterval > 0);
|
||||
|
||||
_("'syncInterval' corresponds to a preference setting.");
|
||||
Weave.Service.syncInterval = INTERVAL;
|
||||
do_check_eq(Weave.Service.syncInterval, INTERVAL);
|
||||
do_check_eq(Svc.Prefs.get('syncInterval'), INTERVAL);
|
||||
|
||||
_("'syncInterval' ignored preference setting after partial sync..");
|
||||
Status.partial = true;
|
||||
do_check_eq(Weave.Service.syncInterval, PARTIAL_DATA_SYNC);
|
||||
|
||||
_("'syncThreshold' corresponds to preference, has non-zero default.");
|
||||
do_check_eq(Svc.Prefs.get('syncThreshold'), undefined);
|
||||
do_check_true(Weave.Service.syncThreshold > 0);
|
||||
Weave.Service.syncThreshold = THRESHOLD;
|
||||
do_check_eq(Weave.Service.syncThreshold, THRESHOLD);
|
||||
do_check_eq(Svc.Prefs.get('syncThreshold'), THRESHOLD);
|
||||
|
||||
_("'globalScore' corresponds to preference, defaults to zero.");
|
||||
do_check_eq(Svc.Prefs.get('globalScore'), undefined);
|
||||
do_check_eq(Weave.Service.globalScore, 0);
|
||||
Weave.Service.globalScore = SCORE;
|
||||
do_check_eq(Weave.Service.globalScore, SCORE);
|
||||
do_check_eq(Svc.Prefs.get('globalScore'), SCORE);
|
||||
|
||||
_("'numClients' corresponds to preference, defaults to zero.");
|
||||
do_check_eq(Svc.Prefs.get('numClients'), undefined);
|
||||
do_check_eq(Weave.Service.numClients, 0);
|
||||
Weave.Service.numClients = NUMCLIENTS;
|
||||
do_check_eq(Weave.Service.numClients, NUMCLIENTS);
|
||||
do_check_eq(Svc.Prefs.get('numClients'), NUMCLIENTS);
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test_locked() {
|
||||
_("The 'locked' attribute can be toggled with lock() and unlock()");
|
||||
|
||||
// Defaults to false
|
||||
do_check_eq(Weave.Service.locked, false);
|
||||
|
||||
do_check_eq(Weave.Service.lock(), true);
|
||||
do_check_eq(Weave.Service.locked, true);
|
||||
|
||||
// Locking again will return false
|
||||
do_check_eq(Weave.Service.lock(), false);
|
||||
|
||||
Weave.Service.unlock();
|
||||
do_check_eq(Weave.Service.locked, false);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_urlsAndIdentities();
|
||||
test_syncID();
|
||||
test_prefAttributes();
|
||||
test_locked();
|
||||
}
|
57
services/sync/tests/unit/test_service_changePassword.js
Normal file
57
services/sync/tests/unit/test_service_changePassword.js
Normal file
@ -0,0 +1,57 @@
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
|
||||
function run_test() {
|
||||
var requestBody;
|
||||
function send(statusCode, status, body) {
|
||||
return function(request, response) {
|
||||
requestBody = readBytesFromInputStream(request.bodyInputStream);
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
};
|
||||
}
|
||||
|
||||
let server;
|
||||
|
||||
try {
|
||||
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.username = "johndoe";
|
||||
Weave.Service.password = "ilovejane";
|
||||
|
||||
_("changePassword() returns false for a network error, the password won't change.");
|
||||
let res = Weave.Service.changePassword("ILoveJane83");
|
||||
do_check_false(res);
|
||||
do_check_eq(Weave.Service.password, "ilovejane");
|
||||
|
||||
_("Let's fire up the server and actually change the password.");
|
||||
server = httpd_setup({
|
||||
"/user/1.0/johndoe/password": send(200, "OK", ""),
|
||||
"/user/1.0/janedoe/password": send(401, "Unauthorized", "Forbidden!")
|
||||
});
|
||||
|
||||
res = Weave.Service.changePassword("ILoveJane83");
|
||||
do_check_true(res);
|
||||
do_check_eq(Weave.Service.password, "ILoveJane83");
|
||||
|
||||
_("Make sure the password has been persisted in the login manager.");
|
||||
let logins = Weave.Svc.Login.findLogins({}, PWDMGR_HOST, null,
|
||||
PWDMGR_PASSWORD_REALM);
|
||||
do_check_eq(logins[0].password, "ILoveJane83");
|
||||
|
||||
_("changePassword() returns false for a server error, the password won't change.");
|
||||
Weave.Svc.Login.removeAllLogins();
|
||||
Weave.Service.username = "janedoe";
|
||||
Weave.Service.password = "ilovejohn";
|
||||
res = Weave.Service.changePassword("ILoveJohn86");
|
||||
do_check_false(res);
|
||||
do_check_eq(Weave.Service.password, "ilovejohn");
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
Weave.Svc.Login.removeAllLogins();
|
||||
if (server) {
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
||||
}
|
31
services/sync/tests/unit/test_service_checkUsername.js
Normal file
31
services/sync/tests/unit/test_service_checkUsername.js
Normal file
@ -0,0 +1,31 @@
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
|
||||
function send(statusCode, status, body) {
|
||||
return function(request, response) {
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
};
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let server = httpd_setup({
|
||||
"/user/1.0/johndoe": send(200, "OK", "1"),
|
||||
"/user/1.0/janedoe": send(200, "OK", "0")
|
||||
});
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
|
||||
_("A 404 will be recorded as 'generic-server-error'");
|
||||
do_check_eq(Weave.Service.checkUsername("jimdoe"), "generic-server-error");
|
||||
|
||||
_("Username that's not available.");
|
||||
do_check_eq(Weave.Service.checkUsername("johndoe"), "notAvailable");
|
||||
|
||||
_("Username that's available.");
|
||||
do_check_eq(Weave.Service.checkUsername("janedoe"), "available");
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
152
services/sync/tests/unit/test_service_cluster.js
Normal file
152
services/sync/tests/unit/test_service_cluster.js
Normal file
@ -0,0 +1,152 @@
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function do_check_throws(func) {
|
||||
var raised = false;
|
||||
try {
|
||||
func();
|
||||
} catch (ex) {
|
||||
raised = true;
|
||||
}
|
||||
do_check_true(raised);
|
||||
}
|
||||
|
||||
function send(statusCode, status, body) {
|
||||
return function(request, response) {
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
};
|
||||
}
|
||||
|
||||
function test_findCluster() {
|
||||
_("Test Weave.Service._findCluster()");
|
||||
let server;
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.username = "johndoe";
|
||||
|
||||
_("_findCluster() throws on network errors (e.g. connection refused).");
|
||||
do_check_throws(function() {
|
||||
Weave.Service._findCluster();
|
||||
});
|
||||
|
||||
server = httpd_setup({
|
||||
"/user/1.0/johndoe/node/weave": send(200, "OK", "http://weave.user.node/"),
|
||||
"/user/1.0/jimdoe/node/weave": send(200, "OK", "null"),
|
||||
"/user/1.0/janedoe/node/weave": send(404, "Not Found", "Not Found"),
|
||||
"/user/1.0/juliadoe/node/weave": send(400, "Bad Request", "Bad Request"),
|
||||
"/user/1.0/joedoe/node/weave": send(500, "Server Error", "Server Error")
|
||||
});
|
||||
|
||||
_("_findCluster() returns the user's cluster node");
|
||||
let cluster = Weave.Service._findCluster();
|
||||
do_check_eq(cluster, "http://weave.user.node/");
|
||||
|
||||
_("A 'null' response is converted to null.");
|
||||
Weave.Service.username = "jimdoe";
|
||||
cluster = Weave.Service._findCluster();
|
||||
do_check_eq(cluster, null);
|
||||
|
||||
_("If a 404 is encountered, the server URL is taken as the cluster URL");
|
||||
Weave.Service.username = "janedoe";
|
||||
cluster = Weave.Service._findCluster();
|
||||
do_check_eq(cluster, Weave.Service.serverURL);
|
||||
|
||||
_("A 400 response will throw an error.");
|
||||
Weave.Service.username = "juliadoe";
|
||||
do_check_throws(function() {
|
||||
Weave.Service._findCluster();
|
||||
});
|
||||
|
||||
_("Any other server response (e.g. 500) will throw an error.");
|
||||
Weave.Service.username = "joedoe";
|
||||
do_check_throws(function() {
|
||||
Weave.Service._findCluster();
|
||||
});
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
if (server) {
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function test_setCluster() {
|
||||
_("Test Weave.Service._setCluster()");
|
||||
let server = httpd_setup({
|
||||
"/user/1.0/johndoe/node/weave": send(200, "OK", "http://weave.user.node/"),
|
||||
"/user/1.0/jimdoe/node/weave": send(200, "OK", "null")
|
||||
});
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.username = "johndoe";
|
||||
|
||||
_("Check initial state.");
|
||||
do_check_eq(Weave.Service.clusterURL, "");
|
||||
|
||||
_("Set the cluster URL.");
|
||||
do_check_true(Weave.Service._setCluster());
|
||||
do_check_eq(Weave.Service.clusterURL, "http://weave.user.node/");
|
||||
|
||||
_("Setting it again won't make a difference if it's the same one.");
|
||||
do_check_false(Weave.Service._setCluster());
|
||||
do_check_eq(Weave.Service.clusterURL, "http://weave.user.node/");
|
||||
|
||||
_("A 'null' response won't make a difference either.");
|
||||
Weave.Service.username = "jimdoe";
|
||||
do_check_false(Weave.Service._setCluster());
|
||||
do_check_eq(Weave.Service.clusterURL, "http://weave.user.node/");
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
||||
|
||||
function test_updateCluster() {
|
||||
_("Test Weave.Service._updateCluster()");
|
||||
let server = httpd_setup({
|
||||
"/user/1.0/johndoe/node/weave": send(200, "OK", "http://weave.user.node/"),
|
||||
"/user/1.0/janedoe/node/weave": send(200, "OK", "http://weave.cluster.url/")
|
||||
});
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.username = "johndoe";
|
||||
|
||||
_("Check initial state.");
|
||||
do_check_eq(Weave.Service.clusterURL, "");
|
||||
do_check_eq(Svc.Prefs.get("lastClusterUpdate"), null);
|
||||
|
||||
_("Set the cluster URL.");
|
||||
do_check_true(Weave.Service._updateCluster());
|
||||
do_check_eq(Weave.Service.clusterURL, "http://weave.user.node/");
|
||||
let lastUpdate = parseInt(Svc.Prefs.get("lastClusterUpdate"), 10);
|
||||
do_check_true(lastUpdate > Date.now() - 1000);
|
||||
|
||||
_("Trying to update the cluster URL within the backoff timeout won't do anything.");
|
||||
do_check_false(Weave.Service._updateCluster());
|
||||
do_check_eq(Weave.Service.clusterURL, "http://weave.user.node/");
|
||||
do_check_eq(parseInt(Svc.Prefs.get("lastClusterUpdate"), 10), lastUpdate);
|
||||
|
||||
_("Time travel 30 mins into the past and the update will work.");
|
||||
Weave.Service.username = "janedoe";
|
||||
Svc.Prefs.set("lastClusterUpdate", (lastUpdate - 30*60*1000).toString());
|
||||
|
||||
do_check_true(Weave.Service._updateCluster());
|
||||
do_check_eq(Weave.Service.clusterURL, "http://weave.cluster.url/");
|
||||
lastUpdate = parseInt(Svc.Prefs.get("lastClusterUpdate"), 10);
|
||||
do_check_true(lastUpdate > Date.now() - 1000);
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_findCluster();
|
||||
test_setCluster();
|
||||
test_updateCluster();
|
||||
}
|
56
services/sync/tests/unit/test_service_createAccount.js
Normal file
56
services/sync/tests/unit/test_service_createAccount.js
Normal file
@ -0,0 +1,56 @@
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
|
||||
function run_test() {
|
||||
var requestBody;
|
||||
var secretHeader;
|
||||
function send(statusCode, status, body) {
|
||||
return function(request, response) {
|
||||
requestBody = readBytesFromInputStream(request.bodyInputStream);
|
||||
if (request.hasHeader("X-Weave-Secret")) {
|
||||
secretHeader = request.getHeader("X-Weave-Secret");
|
||||
}
|
||||
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
};
|
||||
}
|
||||
|
||||
let server = httpd_setup({
|
||||
"/user/1.0/johndoe": send(200, "OK", "0"),
|
||||
"/user/1.0/janedoe": send(400, "Bad Request", "2"),
|
||||
"/user/1.0/jimdoe": send(500, "Server Error", "Server Error")
|
||||
});
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
|
||||
_("Create an account.");
|
||||
let res = Weave.Service.createAccount("johndoe", "mysecretpw", "john@doe",
|
||||
"challenge", "response");
|
||||
do_check_eq(res, null);
|
||||
let payload = JSON.parse(requestBody);
|
||||
do_check_eq(payload.password, "mysecretpw");
|
||||
do_check_eq(payload.email, "john@doe");
|
||||
do_check_eq(payload["captcha-challenge"], "challenge");
|
||||
do_check_eq(payload["captcha-response"], "response");
|
||||
|
||||
_("Invalid captcha or other user-friendly error.");
|
||||
res = Weave.Service.createAccount("janedoe", "anothersecretpw", "jane@doe",
|
||||
"challenge", "response");
|
||||
do_check_eq(res, "invalid-captcha");
|
||||
|
||||
_("Generic server error.");
|
||||
res = Weave.Service.createAccount("jimdoe", "preciousss", "jim@doe",
|
||||
"challenge", "response");
|
||||
do_check_eq(res, "generic-server-error");
|
||||
|
||||
_("Admin secret preference is passed as HTTP header token.");
|
||||
Weave.Svc.Prefs.set("admin-secret", "my-server-secret");
|
||||
res = Weave.Service.createAccount("johndoe", "mysecretpw", "john@doe",
|
||||
"challenge", "response");
|
||||
do_check_eq(secretHeader, "my-server-secret");
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
94
services/sync/tests/unit/test_service_login.js
Normal file
94
services/sync/tests/unit/test_service_login.js
Normal file
@ -0,0 +1,94 @@
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function login_handler(request, response) {
|
||||
// btoa('johndoe:ilovejane') == am9obmRvZTppbG92ZWphbmU=
|
||||
// btoa('janedoe:ilovejohn') == amFuZWRvZTppbG92ZWpvaG4=
|
||||
let body;
|
||||
let header = request.getHeader("Authorization");
|
||||
if (header == "Basic am9obmRvZTppbG92ZWphbmU="
|
||||
|| header == "Basic amFuZWRvZTppbG92ZWpvaG4=") {
|
||||
body = "{}";
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
} else {
|
||||
body = "Unauthorized";
|
||||
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
|
||||
}
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let logger = Log4Moz.repository.rootLogger;
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/info/collections": login_handler,
|
||||
"/1.0/janedoe/info/collections": login_handler
|
||||
});
|
||||
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.clusterURL = "http://localhost:8080/";
|
||||
Svc.Prefs.set("autoconnect", false);
|
||||
|
||||
_("Initial state is ok.");
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
|
||||
_("Try logging in. It wont' work because we're not configured yet.");
|
||||
Weave.Service.login();
|
||||
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
|
||||
do_check_false(Weave.Service.isLoggedIn);
|
||||
do_check_false(Svc.Prefs.get("autoconnect"));
|
||||
|
||||
_("Try again with username and password set.");
|
||||
Weave.Service.username = "johndoe";
|
||||
Weave.Service.password = "ilovejane";
|
||||
Weave.Service.login();
|
||||
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
|
||||
do_check_false(Weave.Service.isLoggedIn);
|
||||
do_check_false(Svc.Prefs.get("autoconnect"));
|
||||
|
||||
_("Success if passphrase is set.");
|
||||
Weave.Service.passphrase = "foo";
|
||||
Weave.Service.login();
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
do_check_eq(Status.login, LOGIN_SUCCEEDED);
|
||||
do_check_true(Weave.Service.isLoggedIn);
|
||||
do_check_true(Svc.Prefs.get("autoconnect"));
|
||||
|
||||
_("We can also pass username, password and passphrase to login().");
|
||||
Weave.Service.login("janedoe", "incorrectpassword", "bar");
|
||||
do_check_eq(Weave.Service.username, "janedoe");
|
||||
do_check_eq(Weave.Service.password, "incorrectpassword");
|
||||
do_check_eq(Weave.Service.passphrase, "bar");
|
||||
do_check_eq(Status.service, LOGIN_FAILED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED);
|
||||
do_check_false(Weave.Service.isLoggedIn);
|
||||
|
||||
_("Try again with correct password.");
|
||||
Weave.Service.login("janedoe", "ilovejohn");
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
do_check_eq(Status.login, LOGIN_SUCCEEDED);
|
||||
do_check_true(Weave.Service.isLoggedIn);
|
||||
do_check_true(Svc.Prefs.get("autoconnect"));
|
||||
|
||||
_("Logout.");
|
||||
Weave.Service.logout();
|
||||
do_check_false(Weave.Service.isLoggedIn);
|
||||
do_check_false(Svc.Prefs.get("autoconnect"));
|
||||
|
||||
_("Logging out again won't do any harm.");
|
||||
Weave.Service.logout();
|
||||
do_check_false(Weave.Service.isLoggedIn);
|
||||
do_check_false(Svc.Prefs.get("autoconnect"));
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
48
services/sync/tests/unit/test_service_migratePrefs.js
Normal file
48
services/sync/tests/unit/test_service_migratePrefs.js
Normal file
@ -0,0 +1,48 @@
|
||||
Cu.import("resource://services-sync/ext/Preferences.js");
|
||||
|
||||
function run_test() {
|
||||
_("Set some prefs on the old branch");
|
||||
let globalPref = new Preferences("");
|
||||
globalPref.set("extensions.weave.hello", "world");
|
||||
globalPref.set("extensions.weave.number", 42);
|
||||
globalPref.set("extensions.weave.yes", true);
|
||||
globalPref.set("extensions.weave.no", false);
|
||||
|
||||
_("Make sure the old prefs are there");
|
||||
do_check_eq(globalPref.get("extensions.weave.hello"), "world");
|
||||
do_check_eq(globalPref.get("extensions.weave.number"), 42);
|
||||
do_check_eq(globalPref.get("extensions.weave.yes"), true);
|
||||
do_check_eq(globalPref.get("extensions.weave.no"), false);
|
||||
|
||||
_("New prefs shouldn't exist yet");
|
||||
do_check_eq(globalPref.get("services.sync.hello"), null);
|
||||
do_check_eq(globalPref.get("services.sync.number"), null);
|
||||
do_check_eq(globalPref.get("services.sync.yes"), null);
|
||||
do_check_eq(globalPref.get("services.sync.no"), null);
|
||||
|
||||
_("Loading service should migrate");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
do_check_eq(globalPref.get("services.sync.hello"), "world");
|
||||
do_check_eq(globalPref.get("services.sync.number"), 42);
|
||||
do_check_eq(globalPref.get("services.sync.yes"), true);
|
||||
do_check_eq(globalPref.get("services.sync.no"), false);
|
||||
do_check_eq(globalPref.get("extensions.weave.hello"), null);
|
||||
do_check_eq(globalPref.get("extensions.weave.number"), null);
|
||||
do_check_eq(globalPref.get("extensions.weave.yes"), null);
|
||||
do_check_eq(globalPref.get("extensions.weave.no"), null);
|
||||
|
||||
_("Migrating should set a pref to make sure to not re-migrate");
|
||||
do_check_true(globalPref.get("services.sync.migrated"));
|
||||
|
||||
_("Make sure re-migrate doesn't happen");
|
||||
globalPref.set("extensions.weave.tooLate", "already migrated!");
|
||||
do_check_eq(globalPref.get("extensions.weave.tooLate"), "already migrated!");
|
||||
do_check_eq(globalPref.get("services.sync.tooLate"), null);
|
||||
Weave.Service._migratePrefs();
|
||||
do_check_eq(globalPref.get("extensions.weave.tooLate"), "already migrated!");
|
||||
do_check_eq(globalPref.get("services.sync.tooLate"), null);
|
||||
|
||||
_("Clearing out pref changes for other tests");
|
||||
globalPref.resetBranch("extensions.weave.");
|
||||
globalPref.resetBranch("services.sync.");
|
||||
}
|
39
services/sync/tests/unit/test_service_persistLogin.js
Normal file
39
services/sync/tests/unit/test_service_persistLogin.js
Normal file
@ -0,0 +1,39 @@
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
|
||||
function run_test() {
|
||||
try {
|
||||
Weave.Service.username = "johndoe";
|
||||
Weave.Service.password = "ilovejane";
|
||||
Weave.Service.passphrase = "my preciousss";
|
||||
|
||||
_("Confirm initial environment is empty.");
|
||||
let logins = Weave.Svc.Login.findLogins({}, PWDMGR_HOST, null,
|
||||
PWDMGR_PASSWORD_REALM);
|
||||
do_check_eq(logins.length, 0);
|
||||
logins = Weave.Svc.Login.findLogins({}, PWDMGR_HOST, null,
|
||||
PWDMGR_PASSPHRASE_REALM);
|
||||
do_check_eq(logins.length, 0);
|
||||
|
||||
_("Persist logins to the login service");
|
||||
Weave.Service.persistLogin();
|
||||
|
||||
_("The password has been persisted in the login service.");
|
||||
logins = Weave.Svc.Login.findLogins({}, PWDMGR_HOST, null,
|
||||
PWDMGR_PASSWORD_REALM);
|
||||
do_check_eq(logins.length, 1);
|
||||
do_check_eq(logins[0].username, "johndoe");
|
||||
do_check_eq(logins[0].password, "ilovejane");
|
||||
|
||||
_("The passphrase has been persisted in the login service.");
|
||||
logins = Weave.Svc.Login.findLogins({}, PWDMGR_HOST, null,
|
||||
PWDMGR_PASSPHRASE_REALM);
|
||||
do_check_eq(logins.length, 1);
|
||||
do_check_eq(logins[0].username, "johndoe");
|
||||
do_check_eq(logins[0].password, "my preciousss");
|
||||
|
||||
} finally {
|
||||
Weave.Svc.Prefs.resetBranch("");
|
||||
Weave.Svc.Login.removeAllLogins();
|
||||
}
|
||||
}
|
40
services/sync/tests/unit/test_service_startup.js
Normal file
40
services/sync/tests/unit/test_service_startup.js
Normal file
@ -0,0 +1,40 @@
|
||||
Cu.import("resource://services-sync/ext/Observers.js");
|
||||
Cu.import("resource://services-sync/ext/Sync.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("When imported, Weave.Service.onStartup is called");
|
||||
|
||||
// Test fixtures
|
||||
let observerCalled = false;
|
||||
Observers.add("weave:service:ready", function (subject, data) {
|
||||
observerCalled = true;
|
||||
});
|
||||
Svc.Prefs.set("registerEngines", "Tab,Bookmarks,Form,History");
|
||||
Svc.Prefs.set("username", "johndoe");
|
||||
|
||||
try {
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
|
||||
_("Service is enabled.");
|
||||
do_check_eq(Weave.Service.enabled, true);
|
||||
|
||||
_("Engines are registered.");
|
||||
let engines = Weave.Engines.getAll();
|
||||
do_check_true(Utils.deepEquals([engine.name for each (engine in engines)],
|
||||
['tabs', 'bookmarks', 'forms', 'history']));
|
||||
|
||||
_("Identities are registered.");
|
||||
do_check_eq(ID.get('WeaveID').username, "johndoe");
|
||||
do_check_eq(ID.get('WeaveCryptoID').username, "johndoe");
|
||||
|
||||
_("Observers are notified of startup");
|
||||
// Synchronize with Weave.Service.onStartup's async notification
|
||||
Sync.sleep(0);
|
||||
do_check_true(observerCalled);
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
59
services/sync/tests/unit/test_service_verifyLogin.js
Normal file
59
services/sync/tests/unit/test_service_verifyLogin.js
Normal file
@ -0,0 +1,59 @@
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/service.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function login_handler(request, response) {
|
||||
// btoa('johndoe:ilovejane') == am9obmRvZTppbG92ZWphbmU=
|
||||
let body;
|
||||
if (request.hasHeader("Authorization") &&
|
||||
request.getHeader("Authorization") == "Basic am9obmRvZTppbG92ZWphbmU=") {
|
||||
body = "{}";
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
} else {
|
||||
body = "Unauthorized";
|
||||
response.setStatusLine(request.httpVersion, 401, "Unauthorized");
|
||||
}
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
let logger = Log4Moz.repository.rootLogger;
|
||||
Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender());
|
||||
|
||||
let server = httpd_setup({
|
||||
"/1.0/johndoe/info/collections": login_handler
|
||||
});
|
||||
|
||||
try {
|
||||
Weave.Service.serverURL = "http://localhost:8080/";
|
||||
Weave.Service.clusterURL = "http://localhost:8080/";
|
||||
|
||||
_("Initial state is ok.");
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
|
||||
_("Credentials won't check out because we're not configured yet.");
|
||||
do_check_false(Weave.Service.verifyLogin());
|
||||
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME);
|
||||
|
||||
_("Try again with username and password set.");
|
||||
Weave.Service.username = "johndoe";
|
||||
Weave.Service.password = "ilovejane";
|
||||
do_check_false(Weave.Service.verifyLogin());
|
||||
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
|
||||
do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
|
||||
|
||||
_("Success if passphrase is set.");
|
||||
Weave.Service.passphrase = "foo";
|
||||
Weave.Service.login();
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
do_check_eq(Status.login, LOGIN_SUCCEEDED);
|
||||
do_check_true(Weave.Service.isLoggedIn);
|
||||
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
server.stop(function() {});
|
||||
}
|
||||
}
|
91
services/sync/tests/unit/test_status.js
Normal file
91
services/sync/tests/unit/test_status.js
Normal file
@ -0,0 +1,91 @@
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/status.js");
|
||||
|
||||
function run_test() {
|
||||
|
||||
// Check initial states
|
||||
do_check_false(Status.enforceBackoff);
|
||||
do_check_eq(Status.backoffInterval, 0);
|
||||
do_check_eq(Status.minimumNextSync, 0);
|
||||
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
do_check_eq(Status.sync, SYNC_SUCCEEDED);
|
||||
do_check_eq(Status.login, LOGIN_SUCCEEDED);
|
||||
for (let name in Status.engines) {
|
||||
do_throw('Status.engines should be empty.');
|
||||
}
|
||||
do_check_eq(Status.partial, false);
|
||||
|
||||
|
||||
// Check login status
|
||||
for each (let code in [LOGIN_FAILED_NO_USERNAME,
|
||||
LOGIN_FAILED_NO_PASSWORD,
|
||||
LOGIN_FAILED_NO_PASSPHRASE]) {
|
||||
Status.login = code;
|
||||
do_check_eq(Status.login, code);
|
||||
do_check_eq(Status.service, CLIENT_NOT_CONFIGURED);
|
||||
Status.resetSync();
|
||||
}
|
||||
|
||||
Status.login = LOGIN_FAILED;
|
||||
do_check_eq(Status.login, LOGIN_FAILED);
|
||||
do_check_eq(Status.service, LOGIN_FAILED);
|
||||
Status.resetSync();
|
||||
|
||||
Status.login = LOGIN_SUCCEEDED;
|
||||
do_check_eq(Status.login, LOGIN_SUCCEEDED);
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
Status.resetSync();
|
||||
|
||||
|
||||
// Check sync status
|
||||
Status.sync = SYNC_FAILED;
|
||||
do_check_eq(Status.sync, SYNC_FAILED);
|
||||
do_check_eq(Status.service, SYNC_FAILED);
|
||||
|
||||
Status.sync = SYNC_SUCCEEDED;
|
||||
do_check_eq(Status.sync, SYNC_SUCCEEDED);
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
|
||||
Status.resetSync();
|
||||
|
||||
|
||||
// Check engine status
|
||||
Status.engines = ["testEng1", ENGINE_SUCCEEDED];
|
||||
do_check_eq(Status.engines["testEng1"], ENGINE_SUCCEEDED);
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
|
||||
Status.engines = ["testEng2", ENGINE_DOWNLOAD_FAIL];
|
||||
do_check_eq(Status.engines["testEng1"], ENGINE_SUCCEEDED);
|
||||
do_check_eq(Status.engines["testEng2"], ENGINE_DOWNLOAD_FAIL);
|
||||
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
|
||||
|
||||
Status.engines = ["testEng3", ENGINE_SUCCEEDED];
|
||||
do_check_eq(Status.engines["testEng1"], ENGINE_SUCCEEDED);
|
||||
do_check_eq(Status.engines["testEng2"], ENGINE_DOWNLOAD_FAIL);
|
||||
do_check_eq(Status.engines["testEng3"], ENGINE_SUCCEEDED);
|
||||
do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
|
||||
|
||||
|
||||
// Check resetSync
|
||||
Status.sync = SYNC_FAILED;
|
||||
Status.resetSync();
|
||||
|
||||
do_check_eq(Status.service, STATUS_OK);
|
||||
do_check_eq(Status.sync, SYNC_SUCCEEDED);
|
||||
for (name in Status.engines) {
|
||||
do_throw('Status.engines should be empty.');
|
||||
}
|
||||
|
||||
|
||||
// Check resetBackoff
|
||||
Status.enforceBackoff = true;
|
||||
Status.backOffInterval = 4815162342;
|
||||
Status.backOffInterval = 42;
|
||||
Status.resetBackoff();
|
||||
|
||||
do_check_false(Status.enforceBackoff);
|
||||
do_check_eq(Status.backoffInterval, 0);
|
||||
do_check_eq(Status.minimumNextSync, 0);
|
||||
|
||||
}
|
121
services/sync/tests/unit/test_syncengine.js
Normal file
121
services/sync/tests/unit/test_syncengine.js
Normal file
@ -0,0 +1,121 @@
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function makeSteamEngine() {
|
||||
return new SyncEngine('Steam');
|
||||
}
|
||||
|
||||
var syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
|
||||
function test_url_attributes() {
|
||||
_("SyncEngine url attributes");
|
||||
Svc.Prefs.set("clusterURL", "https://cluster/");
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
do_check_eq(engine.storageURL, "https://cluster/1.0/foo/storage/");
|
||||
do_check_eq(engine.engineURL, "https://cluster/1.0/foo/storage/steam");
|
||||
do_check_eq(engine.cryptoMetaURL,
|
||||
"https://cluster/1.0/foo/storage/crypto/steam");
|
||||
do_check_eq(engine.metaURL, "https://cluster/1.0/foo/storage/meta/global");
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
function test_syncID() {
|
||||
_("SyncEngine.syncID corresponds to preference");
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
// Ensure pristine environment
|
||||
do_check_eq(Svc.Prefs.get("steam.syncID"), undefined);
|
||||
|
||||
// Performing the first get on the attribute will generate a new GUID.
|
||||
do_check_eq(engine.syncID, "fake-guid-0");
|
||||
do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-0");
|
||||
|
||||
Svc.Prefs.set("steam.syncID", Utils.makeGUID());
|
||||
do_check_eq(Svc.Prefs.get("steam.syncID"), "fake-guid-1");
|
||||
do_check_eq(engine.syncID, "fake-guid-1");
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
||||
function test_lastSync() {
|
||||
_("SyncEngine.lastSync corresponds to preference and stores floats");
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
// Ensure pristine environment
|
||||
do_check_eq(Svc.Prefs.get("steam.lastSync"), undefined);
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
|
||||
// Floats are properly stored as floats and synced with the preference
|
||||
engine.lastSync = 123.45;
|
||||
do_check_eq(engine.lastSync, 123.45);
|
||||
do_check_eq(Svc.Prefs.get("steam.lastSync"), "123.45");
|
||||
|
||||
// resetLastSync() resets the value (and preference) to 0
|
||||
engine.resetLastSync();
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
do_check_eq(Svc.Prefs.get("steam.lastSync"), "0");
|
||||
} finally {
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
function test_toFetch() {
|
||||
_("SyncEngine.toFetch corresponds to file on disk");
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
// Ensure pristine environment
|
||||
do_check_eq(engine.toFetch.length, 0);
|
||||
|
||||
// Write file to disk
|
||||
let toFetch = [Utils.makeGUID(), Utils.makeGUID(), Utils.makeGUID()];
|
||||
engine.toFetch = toFetch;
|
||||
do_check_eq(engine.toFetch, toFetch);
|
||||
let fakefile = syncTesting.fakeFilesystem.fakeContents[
|
||||
"weave/toFetch/steam.json"];
|
||||
do_check_eq(fakefile, JSON.stringify(toFetch));
|
||||
|
||||
// Read file from disk
|
||||
toFetch = [Utils.makeGUID(), Utils.makeGUID()];
|
||||
syncTesting.fakeFilesystem.fakeContents["weave/toFetch/steam.json"]
|
||||
= JSON.stringify(toFetch);
|
||||
engine.loadToFetch();
|
||||
do_check_eq(engine.toFetch.length, 2);
|
||||
do_check_eq(engine.toFetch[0], toFetch[0]);
|
||||
do_check_eq(engine.toFetch[1], toFetch[1]);
|
||||
} finally {
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
}
|
||||
}
|
||||
|
||||
function test_resetClient() {
|
||||
_("SyncEngine.resetClient resets lastSync and toFetch");
|
||||
let engine = makeSteamEngine();
|
||||
try {
|
||||
// Ensure pristine environment
|
||||
do_check_eq(Svc.Prefs.get("steam.lastSync"), undefined);
|
||||
do_check_eq(engine.toFetch.length, 0);
|
||||
|
||||
engine.toFetch = [Utils.makeGUID(), Utils.makeGUID(), Utils.makeGUID()];
|
||||
engine.lastSync = 123.45;
|
||||
|
||||
engine.resetClient();
|
||||
do_check_eq(engine.lastSync, 0);
|
||||
do_check_eq(engine.toFetch.length, 0);
|
||||
} finally {
|
||||
syncTesting = new SyncTestingInfrastructure(makeSteamEngine);
|
||||
Svc.Prefs.resetBranch("");
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
test_url_attributes();
|
||||
test_syncID();
|
||||
test_lastSync();
|
||||
test_toFetch();
|
||||
test_resetClient();
|
||||
}
|
1027
services/sync/tests/unit/test_syncengine_sync.js
Normal file
1027
services/sync/tests/unit/test_syncengine_sync.js
Normal file
File diff suppressed because it is too large
Load Diff
25
services/sync/tests/unit/test_tracker_addChanged.js
Normal file
25
services/sync/tests/unit/test_tracker_addChanged.js
Normal file
@ -0,0 +1,25 @@
|
||||
Cu.import("resource://services-sync/trackers.js");
|
||||
|
||||
function run_test() {
|
||||
let tracker = new Tracker();
|
||||
let id = "the_id!";
|
||||
|
||||
_("Make sure nothing exists yet..");
|
||||
do_check_eq(tracker.changedIDs[id], null);
|
||||
|
||||
_("Make sure adding of time 0 works");
|
||||
tracker.addChangedID(id, 0);
|
||||
do_check_eq(tracker.changedIDs[id], 0);
|
||||
|
||||
_("A newer time will replace the old 0");
|
||||
tracker.addChangedID(id, 10);
|
||||
do_check_eq(tracker.changedIDs[id], 10);
|
||||
|
||||
_("An older time will not replace the newer 10");
|
||||
tracker.addChangedID(id, 5);
|
||||
do_check_eq(tracker.changedIDs[id], 10);
|
||||
|
||||
_("Adding without time defaults to current time");
|
||||
tracker.addChangedID(id);
|
||||
do_check_true(tracker.changedIDs[id] > 10);
|
||||
}
|
41
services/sync/tests/unit/test_utils_anno.js
Normal file
41
services/sync/tests/unit/test_utils_anno.js
Normal file
@ -0,0 +1,41 @@
|
||||
_("Make sure various combinations of anno arguments do the right get/set for pages/items");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("create a bookmark to a url so it exists");
|
||||
let url = "about:";
|
||||
let bmkid = Svc.Bookmark.insertBookmark(Svc.Bookmark.unfiledBookmarksFolder,
|
||||
Utils.makeURI(url), -1, "");
|
||||
|
||||
_("set an anno on the bookmark ");
|
||||
Utils.anno(bmkid, "anno", "hi");
|
||||
do_check_eq(Utils.anno(bmkid, "anno"), "hi");
|
||||
|
||||
_("set an anno on a url");
|
||||
Utils.anno(url, "tation", "hello");
|
||||
do_check_eq(Utils.anno(url, "tation"), "hello");
|
||||
|
||||
_("make sure getting it also works with a nsIURI");
|
||||
let uri = Utils.makeURI(url);
|
||||
do_check_eq(Utils.anno(uri, "tation"), "hello");
|
||||
|
||||
_("make sure annotations get updated");
|
||||
Utils.anno(uri, "tation", "bye!");
|
||||
do_check_eq(Utils.anno(url, "tation"), "bye!");
|
||||
|
||||
_("sanity check that the item anno is still there");
|
||||
do_check_eq(Utils.anno(bmkid, "anno"), "hi");
|
||||
|
||||
_("invalid uris don't get annos");
|
||||
let didThrow = false;
|
||||
try {
|
||||
Utils.anno("foo/bar/baz", "bad");
|
||||
}
|
||||
catch(ex) {
|
||||
didThrow = true;
|
||||
}
|
||||
do_check_true(didThrow);
|
||||
|
||||
_("cleaning up the bookmark we created");
|
||||
Svc.Bookmark.removeItem(bmkid);
|
||||
}
|
42
services/sync/tests/unit/test_utils_catch.js
Normal file
42
services/sync/tests/unit/test_utils_catch.js
Normal file
@ -0,0 +1,42 @@
|
||||
_("Make sure catch when copied to an object will correctly catch stuff");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let ret, rightThis, didCall, didThrow;
|
||||
let obj = {
|
||||
catch: Utils.catch,
|
||||
_log: {
|
||||
debug: function(str) {
|
||||
didThrow = str.search(/^Exception: /) == 0;
|
||||
}
|
||||
},
|
||||
|
||||
func: function() this.catch(function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
return 5;
|
||||
})(),
|
||||
|
||||
throwy: function() this.catch(function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
throw 10;
|
||||
})()
|
||||
};
|
||||
|
||||
_("Make sure a normal call will call and return");
|
||||
rightThis = didCall = didThrow = false;
|
||||
ret = obj.func();
|
||||
do_check_eq(ret, 5);
|
||||
do_check_true(rightThis);
|
||||
do_check_true(didCall);
|
||||
do_check_false(didThrow);
|
||||
|
||||
_("Make sure catch/throw results in debug call and caller doesn't need to handle exception");
|
||||
rightThis = didCall = didThrow = false;
|
||||
ret = obj.throwy();
|
||||
do_check_eq(ret, undefined);
|
||||
do_check_true(rightThis);
|
||||
do_check_true(didCall);
|
||||
do_check_true(didThrow);
|
||||
}
|
44
services/sync/tests/unit/test_utils_deepEquals.js
Normal file
44
services/sync/tests/unit/test_utils_deepEquals.js
Normal file
@ -0,0 +1,44 @@
|
||||
_("Make sure Utils.deepEquals correctly finds items that are deeply equal");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let data = '[NaN, undefined, null, true, false, Infinity, 0, 1, "a", "b", {a: 1}, {a: "a"}, [{a: 1}], [{a: true}], {a: 1, b: 2}, [1, 2], [1, 2, 3]]';
|
||||
_("Generating two copies of data:", data);
|
||||
let d1 = eval(data);
|
||||
let d2 = eval(data);
|
||||
|
||||
d1.forEach(function(a) {
|
||||
_("Testing", a, typeof a, JSON.stringify([a]));
|
||||
let numMatch = 0;
|
||||
|
||||
d2.forEach(function(b) {
|
||||
if (Utils.deepEquals(a, b)) {
|
||||
numMatch++;
|
||||
_("Found a match", b, typeof b, JSON.stringify([b]));
|
||||
}
|
||||
});
|
||||
|
||||
let expect = 1;
|
||||
if (isNaN(a) && typeof a == "number") {
|
||||
expect = 0;
|
||||
_("Checking NaN should result in no matches");
|
||||
}
|
||||
|
||||
_("Making sure we found the correct # match:", expect);
|
||||
_("Actual matches:", numMatch);
|
||||
do_check_eq(numMatch, expect);
|
||||
});
|
||||
|
||||
_("Make sure adding undefined properties doesn't affect equalness");
|
||||
let a = {};
|
||||
let b = { a: undefined };
|
||||
do_check_true(Utils.deepEquals(a, b));
|
||||
a.b = 5;
|
||||
do_check_false(Utils.deepEquals(a, b));
|
||||
b.b = 5;
|
||||
do_check_true(Utils.deepEquals(a, b));
|
||||
a.c = undefined;
|
||||
do_check_true(Utils.deepEquals(a, b));
|
||||
b.d = undefined;
|
||||
do_check_true(Utils.deepEquals(a, b));
|
||||
}
|
52
services/sync/tests/unit/test_utils_deferGetSet.js
Normal file
52
services/sync/tests/unit/test_utils_deferGetSet.js
Normal file
@ -0,0 +1,52 @@
|
||||
_("Make sure various combinations of deferGetSet arguments correctly defer getting/setting properties to another object");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let base = function() {};
|
||||
base.prototype = {
|
||||
dst: {},
|
||||
|
||||
get a() "a",
|
||||
set b(val) this.dst.b = val + "!!!"
|
||||
};
|
||||
let src = new base();
|
||||
|
||||
_("get/set a single property");
|
||||
Utils.deferGetSet(base, "dst", "foo");
|
||||
src.foo = "bar";
|
||||
do_check_eq(src.dst.foo, "bar");
|
||||
do_check_eq(src.foo, "bar");
|
||||
|
||||
_("editing the target also updates the source");
|
||||
src.dst.foo = "baz";
|
||||
do_check_eq(src.dst.foo, "baz");
|
||||
do_check_eq(src.foo, "baz");
|
||||
|
||||
_("handle multiple properties");
|
||||
Utils.deferGetSet(base, "dst", ["p1", "p2"]);
|
||||
src.p1 = "v1";
|
||||
src.p2 = "v2";
|
||||
do_check_eq(src.p1, "v1");
|
||||
do_check_eq(src.dst.p1, "v1");
|
||||
do_check_eq(src.p2, "v2");
|
||||
do_check_eq(src.dst.p2, "v2");
|
||||
|
||||
_("handle dotted properties");
|
||||
src.dst.nest = {};
|
||||
Utils.deferGetSet(base, "dst.nest", "prop");
|
||||
src.prop = "val";
|
||||
do_check_eq(src.prop, "val");
|
||||
do_check_eq(src.dst.nest.prop, "val");
|
||||
|
||||
_("make sure existing getter keeps its functionality");
|
||||
Utils.deferGetSet(base, "dst", "a");
|
||||
src.a = "not a";
|
||||
do_check_eq(src.dst.a, "not a");
|
||||
do_check_eq(src.a, "a");
|
||||
|
||||
_("make sure existing setter keeps its functionality");
|
||||
Utils.deferGetSet(base, "dst", "b");
|
||||
src.b = "b";
|
||||
do_check_eq(src.dst.b, "b!!!");
|
||||
do_check_eq(src.b, "b!!!");
|
||||
}
|
46
services/sync/tests/unit/test_utils_json.js
Normal file
46
services/sync/tests/unit/test_utils_json.js
Normal file
@ -0,0 +1,46 @@
|
||||
_("Make sure json saves and loads from disk");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Do a simple write of an array to json and read");
|
||||
let foo;
|
||||
Utils.jsonSave("foo", {}, ["v1", "v2"]);
|
||||
Utils.jsonLoad("foo", {}, function(val) {
|
||||
foo = val;
|
||||
});
|
||||
do_check_eq(typeof foo, "object");
|
||||
do_check_eq(foo.length, 2);
|
||||
do_check_eq(foo[0], "v1");
|
||||
do_check_eq(foo[1], "v2");
|
||||
|
||||
_("Use the function callback version of jsonSave");
|
||||
let bar;
|
||||
Utils.jsonSave("bar", {}, function() ["v1", "v2"]);
|
||||
Utils.jsonLoad("bar", {}, function(val) {
|
||||
bar = val;
|
||||
});
|
||||
do_check_eq(typeof bar, "object");
|
||||
do_check_eq(bar.length, 2);
|
||||
do_check_eq(bar[0], "v1");
|
||||
do_check_eq(bar[1], "v2");
|
||||
|
||||
_("Try saving simple strings");
|
||||
let str;
|
||||
Utils.jsonSave("str", {}, "hi");
|
||||
Utils.jsonLoad("str", {}, function(val) {
|
||||
str = val;
|
||||
});
|
||||
do_check_eq(typeof str, "string");
|
||||
do_check_eq(str.length, 2);
|
||||
do_check_eq(str[0], "h");
|
||||
do_check_eq(str[1], "i");
|
||||
|
||||
_("Try saving a number");
|
||||
let num;
|
||||
Utils.jsonSave("num", {}, function() 42);
|
||||
Utils.jsonLoad("num", {}, function(val) {
|
||||
num = val;
|
||||
});
|
||||
do_check_eq(typeof num, "number");
|
||||
do_check_eq(num, 42);
|
||||
}
|
40
services/sync/tests/unit/test_utils_lazy.js
Normal file
40
services/sync/tests/unit/test_utils_lazy.js
Normal file
@ -0,0 +1,40 @@
|
||||
_("Make sure lazy constructor calling/assignment works");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let count = 0;
|
||||
let Foo = function() {
|
||||
this.num = ++count;
|
||||
}
|
||||
|
||||
_("Make a thing instance of Foo but make sure it isn't initialized yet");
|
||||
let obj = {};
|
||||
Utils.lazy(obj, "thing", Foo);
|
||||
do_check_eq(count, 0);
|
||||
|
||||
_("Access the property to make it construct");
|
||||
do_check_eq(typeof obj.thing, "object");
|
||||
do_check_eq(obj.thing.constructor, Foo);
|
||||
do_check_eq(count, 1);
|
||||
do_check_eq(obj.thing.num, 1);
|
||||
|
||||
_("Additional accesses don't construct again (nothing should change");
|
||||
do_check_eq(typeof obj.thing, "object");
|
||||
do_check_eq(obj.thing.constructor, Foo);
|
||||
do_check_eq(count, 1);
|
||||
do_check_eq(obj.thing.num, 1);
|
||||
|
||||
_("More lazy properties will constrct more");
|
||||
do_check_eq(typeof obj.other, "undefined");
|
||||
Utils.lazy(obj, "other", Foo);
|
||||
do_check_eq(typeof obj.other, "object");
|
||||
do_check_eq(obj.other.constructor, Foo);
|
||||
do_check_eq(count, 2);
|
||||
do_check_eq(obj.other.num, 2);
|
||||
|
||||
_("Sanity check that the original property didn't change");
|
||||
do_check_eq(typeof obj.thing, "object");
|
||||
do_check_eq(obj.thing.constructor, Foo);
|
||||
do_check_eq(count, 2);
|
||||
do_check_eq(obj.thing.num, 1);
|
||||
}
|
36
services/sync/tests/unit/test_utils_lazy2.js
Normal file
36
services/sync/tests/unit/test_utils_lazy2.js
Normal file
@ -0,0 +1,36 @@
|
||||
_("Make sure lazy2 function calling/assignment works");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let count = 0;
|
||||
let Foo = function() {
|
||||
return ++count;
|
||||
}
|
||||
|
||||
_("Make a thing instance of Foo but make sure it isn't initialized yet");
|
||||
let obj = {};
|
||||
Utils.lazy2(obj, "thing", Foo);
|
||||
do_check_eq(count, 0);
|
||||
|
||||
_("Access the property to make it evaluates");
|
||||
do_check_eq(typeof obj.thing, "number");
|
||||
do_check_eq(count, 1);
|
||||
do_check_eq(obj.thing, 1);
|
||||
|
||||
_("Additional accesses don't evaluate again (nothing should change");
|
||||
do_check_eq(typeof obj.thing, "number");
|
||||
do_check_eq(count, 1);
|
||||
do_check_eq(obj.thing, 1);
|
||||
|
||||
_("More lazy properties will constrct more");
|
||||
do_check_eq(typeof obj.other, "undefined");
|
||||
Utils.lazy2(obj, "other", Foo);
|
||||
do_check_eq(typeof obj.other, "number");
|
||||
do_check_eq(count, 2);
|
||||
do_check_eq(obj.other, 2);
|
||||
|
||||
_("Sanity check that the original property didn't change");
|
||||
do_check_eq(typeof obj.thing, "number");
|
||||
do_check_eq(count, 2);
|
||||
do_check_eq(obj.thing, 1);
|
||||
}
|
36
services/sync/tests/unit/test_utils_lazySvc.js
Normal file
36
services/sync/tests/unit/test_utils_lazySvc.js
Normal file
@ -0,0 +1,36 @@
|
||||
_("Make sure lazySvc get the desired services");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Load the xul app info service as obj.app");
|
||||
let obj = {}
|
||||
do_check_eq(typeof obj.app, "undefined");
|
||||
Utils.lazySvc(obj, "app", "@mozilla.org/xre/app-info;1", "nsIXULAppInfo");
|
||||
do_check_eq(typeof obj.app.QueryInterface, "function");
|
||||
do_check_eq(typeof obj.app.vendor, "string");
|
||||
do_check_eq(typeof obj.app.name, "string");
|
||||
|
||||
_("Check other types of properties on other services");
|
||||
Utils.lazySvc(obj, "io", "@mozilla.org/network/io-service;1", "nsIIOService");
|
||||
do_check_eq(typeof obj.io.newURI, "function");
|
||||
do_check_eq(typeof obj.io.offline, "boolean");
|
||||
|
||||
Utils.lazySvc(obj, "thread", "@mozilla.org/thread-manager;1", "nsIThreadManager");
|
||||
do_check_true(obj.thread.currentThread instanceof Ci.nsIThread);
|
||||
|
||||
Utils.lazySvc(obj, "net", "@mozilla.org/network/util;1", "nsINetUtil");
|
||||
do_check_eq(typeof obj.net.ESCAPE_ALL, "number");
|
||||
do_check_eq(obj.net.ESCAPE_URL_SCHEME, 1);
|
||||
|
||||
_("Make sure fake services get loaded correctly (private browsing doesnt exist on all platforms)");
|
||||
Utils.lazySvc(obj, "priv", "@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService");
|
||||
do_check_eq(typeof obj.priv.privateBrowsingEnabled, "boolean");
|
||||
|
||||
_("Definitely make sure services that should never exist will use fake service if available");
|
||||
Utils.lazySvc(obj, "fake", "@labs.mozilla.com/Fake/Thing;1", "fake");
|
||||
do_check_eq(obj.fake.isFake, true);
|
||||
|
||||
_("Nonexistant services that aren't fake-implemented will get nothing");
|
||||
Utils.lazySvc(obj, "nonexist", "@something?@", "doesnt exist");
|
||||
do_check_eq(obj.nonexist, undefined);
|
||||
}
|
65
services/sync/tests/unit/test_utils_lock.js
Normal file
65
services/sync/tests/unit/test_utils_lock.js
Normal file
@ -0,0 +1,65 @@
|
||||
_("Make sure lock prevents calling with a shared lock");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let ret, rightThis, didCall;
|
||||
let state, lockState, lockedState, unlockState;
|
||||
let obj = {
|
||||
_lock: Utils.lock,
|
||||
lock: function() {
|
||||
lockState = ++state;
|
||||
if (this._locked) {
|
||||
lockedState = ++state;
|
||||
return false;
|
||||
}
|
||||
this._locked = true;
|
||||
return true;
|
||||
},
|
||||
unlock: function() {
|
||||
unlockState = ++state;
|
||||
this._locked = false;
|
||||
},
|
||||
|
||||
func: function() this._lock(function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
return 5;
|
||||
})(),
|
||||
|
||||
throwy: function() this._lock(function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
this.throwy();
|
||||
})()
|
||||
};
|
||||
|
||||
_("Make sure a normal call will call and return");
|
||||
rightThis = didCall = false;
|
||||
state = 0;
|
||||
ret = obj.func();
|
||||
do_check_eq(ret, 5);
|
||||
do_check_true(rightThis);
|
||||
do_check_true(didCall);
|
||||
do_check_eq(lockState, 1);
|
||||
do_check_eq(unlockState, 2);
|
||||
do_check_eq(state, 2);
|
||||
|
||||
_("Make sure code that calls locked code throws");
|
||||
ret = null;
|
||||
rightThis = didCall = false;
|
||||
try {
|
||||
ret = obj.throwy();
|
||||
do_throw("throwy internal call should have thrown!");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_eq(ex, "Could not acquire lock");
|
||||
}
|
||||
do_check_eq(ret, null);
|
||||
do_check_true(rightThis);
|
||||
do_check_true(didCall);
|
||||
_("Lock should be called twice so state 3 is skipped");
|
||||
do_check_eq(lockState, 4);
|
||||
do_check_eq(lockedState, 5);
|
||||
do_check_eq(unlockState, 6);
|
||||
do_check_eq(state, 6);
|
||||
}
|
15
services/sync/tests/unit/test_utils_makeGUID.js
Normal file
15
services/sync/tests/unit/test_utils_makeGUID.js
Normal file
@ -0,0 +1,15 @@
|
||||
_("Make sure makeGUID makes guids of the right length/characters");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
// XXX this could cause random failures as guids arent always unique...
|
||||
_("Create a bunch of guids to make sure they don't conflict");
|
||||
let guids = [];
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
let newGuid = Utils.makeGUID();
|
||||
_("Making sure guid has the right length without special characters:", newGuid);
|
||||
do_check_eq(encodeURIComponent(newGuid).length, 10);
|
||||
do_check_true(guids.every(function(g) g != newGuid));
|
||||
guids.push(newGuid);
|
||||
}
|
||||
}
|
59
services/sync/tests/unit/test_utils_makeURI.js
Normal file
59
services/sync/tests/unit/test_utils_makeURI.js
Normal file
@ -0,0 +1,59 @@
|
||||
_("Make sure uri strings are converted to nsIURIs");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Check http uris");
|
||||
let uri1 = "http://mozillalabs.com/";
|
||||
do_check_eq(Utils.makeURI(uri1).spec, uri1);
|
||||
let uri2 = "http://www.mozillalabs.com/";
|
||||
do_check_eq(Utils.makeURI(uri2).spec, uri2);
|
||||
let uri3 = "http://mozillalabs.com/path";
|
||||
do_check_eq(Utils.makeURI(uri3).spec, uri3);
|
||||
let uri4 = "http://mozillalabs.com/multi/path";
|
||||
do_check_eq(Utils.makeURI(uri4).spec, uri4);
|
||||
let uri5 = "http://mozillalabs.com/?query";
|
||||
do_check_eq(Utils.makeURI(uri5).spec, uri5);
|
||||
let uri6 = "http://mozillalabs.com/#hash";
|
||||
do_check_eq(Utils.makeURI(uri6).spec, uri6);
|
||||
|
||||
_("Check https uris");
|
||||
let uris1 = "https://mozillalabs.com/";
|
||||
do_check_eq(Utils.makeURI(uris1).spec, uris1);
|
||||
let uris2 = "https://www.mozillalabs.com/";
|
||||
do_check_eq(Utils.makeURI(uris2).spec, uris2);
|
||||
let uris3 = "https://mozillalabs.com/path";
|
||||
do_check_eq(Utils.makeURI(uris3).spec, uris3);
|
||||
let uris4 = "https://mozillalabs.com/multi/path";
|
||||
do_check_eq(Utils.makeURI(uris4).spec, uris4);
|
||||
let uris5 = "https://mozillalabs.com/?query";
|
||||
do_check_eq(Utils.makeURI(uris5).spec, uris5);
|
||||
let uris6 = "https://mozillalabs.com/#hash";
|
||||
do_check_eq(Utils.makeURI(uris6).spec, uris6);
|
||||
|
||||
_("Check chrome uris");
|
||||
let uric1 = "chrome://browser/content/browser.xul";
|
||||
do_check_eq(Utils.makeURI(uric1).spec, uric1);
|
||||
let uric2 = "chrome://browser/skin/browser.css";
|
||||
do_check_eq(Utils.makeURI(uric2).spec, uric2);
|
||||
let uric3 = "chrome://browser/locale/browser.dtd";
|
||||
do_check_eq(Utils.makeURI(uric3).spec, uric3);
|
||||
|
||||
_("Check about uris");
|
||||
let uria1 = "about:weave";
|
||||
do_check_eq(Utils.makeURI(uria1).spec, uria1);
|
||||
let uria2 = "about:weave/";
|
||||
do_check_eq(Utils.makeURI(uria2).spec, uria2);
|
||||
let uria3 = "about:weave/path";
|
||||
do_check_eq(Utils.makeURI(uria3).spec, uria3);
|
||||
let uria4 = "about:weave/multi/path";
|
||||
do_check_eq(Utils.makeURI(uria4).spec, uria4);
|
||||
let uria5 = "about:weave/?query";
|
||||
do_check_eq(Utils.makeURI(uria5).spec, uria5);
|
||||
let uria6 = "about:weave/#hash";
|
||||
do_check_eq(Utils.makeURI(uria6).spec, uria6);
|
||||
|
||||
_("Invalid uris are undefined");
|
||||
do_check_eq(Utils.makeURI("mozillalabs.com"), undefined);
|
||||
do_check_eq(Utils.makeURI("chrome://badstuff"), undefined);
|
||||
do_check_eq(Utils.makeURI("this is a test"), undefined);
|
||||
}
|
90
services/sync/tests/unit/test_utils_notify.js
Normal file
90
services/sync/tests/unit/test_utils_notify.js
Normal file
@ -0,0 +1,90 @@
|
||||
_("Make sure notify sends out the right notifications");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let ret, rightThis, didCall;
|
||||
let obj = {
|
||||
notify: Utils.notify("foo:"),
|
||||
_log: {
|
||||
trace: function() {}
|
||||
},
|
||||
|
||||
func: function() this.notify("bar", "baz", function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
return 5;
|
||||
})(),
|
||||
|
||||
throwy: function() this.notify("bad", "one", function() {
|
||||
rightThis = this == obj;
|
||||
didCall = true;
|
||||
throw 10;
|
||||
})()
|
||||
};
|
||||
|
||||
let state = 0;
|
||||
let makeObs = function(topic) {
|
||||
let obj = {
|
||||
observe: function(subject, topic, data) {
|
||||
this.state = ++state;
|
||||
this.subject = subject;
|
||||
this.topic = topic;
|
||||
this.data = data;
|
||||
}
|
||||
};
|
||||
|
||||
Svc.Obs.add(topic, obj);
|
||||
return obj;
|
||||
};
|
||||
|
||||
_("Make sure a normal call will call and return with notifications");
|
||||
rightThis = didCall = false;
|
||||
let fs = makeObs("foo:bar:start");
|
||||
let ff = makeObs("foo:bar:finish");
|
||||
let fe = makeObs("foo:bar:error");
|
||||
ret = obj.func();
|
||||
do_check_eq(ret, 5);
|
||||
do_check_true(rightThis);
|
||||
do_check_true(didCall);
|
||||
do_check_eq(fs.state, 1);
|
||||
do_check_eq(fs.subject, "baz");
|
||||
do_check_eq(fs.topic, "foo:bar:start");
|
||||
do_check_eq(fs.data, undefined);
|
||||
do_check_eq(ff.state, 2);
|
||||
do_check_eq(ff.subject, "baz");
|
||||
do_check_eq(ff.topic, "foo:bar:finish");
|
||||
do_check_eq(ff.data, undefined);
|
||||
do_check_eq(fe.state, undefined);
|
||||
do_check_eq(fe.subject, undefined);
|
||||
do_check_eq(fe.topic, undefined);
|
||||
do_check_eq(fe.data, undefined);
|
||||
|
||||
_("Make sure a throwy call will call and throw with notifications");
|
||||
ret = null;
|
||||
rightThis = didCall = false;
|
||||
let ts = makeObs("foo:bad:start");
|
||||
let tf = makeObs("foo:bad:finish");
|
||||
let te = makeObs("foo:bad:error");
|
||||
try {
|
||||
ret = obj.throwy();
|
||||
do_throw("throwy should have thrown!");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_eq(ex, 10);
|
||||
}
|
||||
do_check_eq(ret, null);
|
||||
do_check_true(rightThis);
|
||||
do_check_true(didCall);
|
||||
do_check_eq(ts.state, 3);
|
||||
do_check_eq(ts.subject, "one");
|
||||
do_check_eq(ts.topic, "foo:bad:start");
|
||||
do_check_eq(ts.data, undefined);
|
||||
do_check_eq(tf.state, undefined);
|
||||
do_check_eq(tf.subject, undefined);
|
||||
do_check_eq(tf.topic, undefined);
|
||||
do_check_eq(tf.data, undefined);
|
||||
do_check_eq(te.state, 4);
|
||||
do_check_eq(te.subject, "one");
|
||||
do_check_eq(te.topic, "foo:bad:error");
|
||||
do_check_eq(te.data, undefined);
|
||||
}
|
71
services/sync/tests/unit/test_utils_queryAsync.js
Normal file
71
services/sync/tests/unit/test_utils_queryAsync.js
Normal file
@ -0,0 +1,71 @@
|
||||
_("Make sure queryAsync will synchronously fetch rows for a query asyncly");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Using the form service to test queries");
|
||||
function c(query) Svc.Form.DBConnection.createStatement(query);
|
||||
|
||||
_("Make sure the call is async and allows other events to process");
|
||||
let isAsync = false;
|
||||
Utils.delay(function() isAsync = true, 0);
|
||||
do_check_false(isAsync);
|
||||
|
||||
_("Empty out the formhistory table");
|
||||
let r0 = Utils.queryAsync(c("DELETE FROM moz_formhistory"));
|
||||
do_check_eq(r0.length, 0);
|
||||
|
||||
_("Make sure there's nothing there");
|
||||
let r1 = Utils.queryAsync(c("SELECT 1 FROM moz_formhistory"));
|
||||
do_check_eq(r1.length, 0);
|
||||
|
||||
_("Insert a row");
|
||||
let r2 = Utils.queryAsync(c("INSERT INTO moz_formhistory (fieldname, value) VALUES ('foo', 'bar')"));
|
||||
do_check_eq(r2.length, 0);
|
||||
|
||||
_("Request a known value for the one row");
|
||||
let r3 = Utils.queryAsync(c("SELECT 42 num FROM moz_formhistory"), "num");
|
||||
do_check_eq(r3.length, 1);
|
||||
do_check_eq(r3[0].num, 42);
|
||||
|
||||
_("Get multiple columns");
|
||||
let r4 = Utils.queryAsync(c("SELECT fieldname, value FROM moz_formhistory"), ["fieldname", "value"]);
|
||||
do_check_eq(r4.length, 1);
|
||||
do_check_eq(r4[0].fieldname, "foo");
|
||||
do_check_eq(r4[0].value, "bar");
|
||||
|
||||
_("Get multiple columns with a different order");
|
||||
let r5 = Utils.queryAsync(c("SELECT fieldname, value FROM moz_formhistory"), ["value", "fieldname"]);
|
||||
do_check_eq(r5.length, 1);
|
||||
do_check_eq(r5[0].fieldname, "foo");
|
||||
do_check_eq(r5[0].value, "bar");
|
||||
|
||||
_("Add multiple entries (sqlite doesn't support multiple VALUES)");
|
||||
let r6 = Utils.queryAsync(c("INSERT INTO moz_formhistory (fieldname, value) SELECT 'foo', 'baz' UNION SELECT 'more', 'values'"));
|
||||
do_check_eq(r6.length, 0);
|
||||
|
||||
_("Get multiple rows");
|
||||
let r7 = Utils.queryAsync(c("SELECT fieldname, value FROM moz_formhistory WHERE fieldname = 'foo'"), ["fieldname", "value"]);
|
||||
do_check_eq(r7.length, 2);
|
||||
do_check_eq(r7[0].fieldname, "foo");
|
||||
do_check_eq(r7[1].fieldname, "foo");
|
||||
|
||||
_("Make sure updates work");
|
||||
let r8 = Utils.queryAsync(c("UPDATE moz_formhistory SET value = 'updated' WHERE fieldname = 'more'"));
|
||||
do_check_eq(r8.length, 0);
|
||||
|
||||
_("Get the updated");
|
||||
let r9 = Utils.queryAsync(c("SELECT value, fieldname FROM moz_formhistory WHERE fieldname = 'more'"), ["fieldname", "value"]);
|
||||
do_check_eq(r9.length, 1);
|
||||
do_check_eq(r9[0].fieldname, "more");
|
||||
do_check_eq(r9[0].value, "updated");
|
||||
|
||||
_("Grabbing fewer fields than queried is fine");
|
||||
let r10 = Utils.queryAsync(c("SELECT value, fieldname FROM moz_formhistory"), "fieldname");
|
||||
do_check_eq(r10.length, 3);
|
||||
|
||||
_("Cleaning up");
|
||||
Utils.queryAsync(c("DELETE FROM moz_formhistory"));
|
||||
|
||||
_("Make sure the timeout got to run before this function ends");
|
||||
do_check_true(isAsync);
|
||||
}
|
29
services/sync/tests/unit/test_utils_sha1.js
Normal file
29
services/sync/tests/unit/test_utils_sha1.js
Normal file
@ -0,0 +1,29 @@
|
||||
_("Make sure sha1 digests works with various messages");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let mes1 = "hello";
|
||||
let mes2 = "world";
|
||||
|
||||
_("Make sure right sha1 digests are generated");
|
||||
let dig1 = Utils.sha1(mes1);
|
||||
do_check_eq(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
|
||||
let dig2 = Utils.sha1(mes2);
|
||||
do_check_eq(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
|
||||
let dig12 = Utils.sha1(mes1 + mes2);
|
||||
do_check_eq(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
|
||||
let dig21 = Utils.sha1(mes2 + mes1);
|
||||
do_check_eq(dig21, "5715790a892990382d98858c4aa38d0617151575");
|
||||
|
||||
_("Repeated sha1s shouldn't change the digest");
|
||||
do_check_eq(Utils.sha1(mes1), dig1);
|
||||
do_check_eq(Utils.sha1(mes2), dig2);
|
||||
do_check_eq(Utils.sha1(mes1 + mes2), dig12);
|
||||
do_check_eq(Utils.sha1(mes2 + mes1), dig21);
|
||||
|
||||
_("Nested sha1 should work just fine");
|
||||
let nest1 = Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(mes1)))));
|
||||
do_check_eq(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
|
||||
let nest2 = Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(mes2)))));
|
||||
do_check_eq(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
|
||||
}
|
26
services/sync/tests/unit/test_utils_sha256HMAC.js
Normal file
26
services/sync/tests/unit/test_utils_sha256HMAC.js
Normal file
@ -0,0 +1,26 @@
|
||||
_("Make sure sha256 hmac works with various messages and keys");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let key1 = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, "key1");
|
||||
let key2 = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, "key2");
|
||||
|
||||
let mes1 = "message 1";
|
||||
let mes2 = "message 2";
|
||||
|
||||
_("Make sure right sha256 hmacs are generated");
|
||||
let hmac11 = Utils.sha256HMAC(mes1, key1);
|
||||
do_check_eq(hmac11, "54f035bfbd6b44445d771c7c430e0753f1c00823974108fb4723703782552296");
|
||||
let hmac12 = Utils.sha256HMAC(mes1, key2);
|
||||
do_check_eq(hmac12, "1dbeae48de1b12f69517d828fb32969c74c8adc0715babc41b8f50254a980e70");
|
||||
let hmac21 = Utils.sha256HMAC(mes2, key1);
|
||||
do_check_eq(hmac21, "e00e91db4e86973868de8b3e818f4c968894d4135a3209bfea7b9e699484f07a");
|
||||
let hmac22 = Utils.sha256HMAC(mes2, key2);
|
||||
do_check_eq(hmac22, "4624312da31ada485b87beeecef0e5f0311cd5de60ea12291ce34cab158e0cc7");
|
||||
|
||||
_("Repeated hmacs shouldn't change the digest");
|
||||
do_check_eq(Utils.sha256HMAC(mes1, key1), hmac11);
|
||||
do_check_eq(Utils.sha256HMAC(mes1, key2), hmac12);
|
||||
do_check_eq(Utils.sha256HMAC(mes2, key1), hmac21);
|
||||
do_check_eq(Utils.sha256HMAC(mes2, key2), hmac22);
|
||||
}
|
32
services/sync/tests/unit/test_utils_stackTrace.js
Normal file
32
services/sync/tests/unit/test_utils_stackTrace.js
Normal file
@ -0,0 +1,32 @@
|
||||
_("Define some functions in well defined line positions for the test");
|
||||
function foo(v) bar(v + 1); // line 2
|
||||
function bar(v) baz(v + 1); // line 3
|
||||
function baz(v) { throw new Error(v + 1); } // line 4
|
||||
|
||||
_("Make sure lazy constructor calling/assignment works");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
_("Make sure functions, arguments, files are pretty printed in the trace");
|
||||
let trace = "";
|
||||
try {
|
||||
foo(0);
|
||||
}
|
||||
catch(ex) {
|
||||
trace = Utils.stackTrace(ex);
|
||||
}
|
||||
_("Got trace:", trace);
|
||||
do_check_neq(trace, "");
|
||||
|
||||
let errorPos = trace.indexOf('Error("3")@:0');
|
||||
let bazPos = trace.indexOf("baz(2)@test_utils_stackTrace.js:4");
|
||||
let barPos = trace.indexOf("bar(1)@test_utils_stackTrace.js:3");
|
||||
let fooPos = trace.indexOf("foo(0)@test_utils_stackTrace.js:2");
|
||||
_("String positions:", errorPos, bazPos, barPos, fooPos);
|
||||
|
||||
_("Make sure the desired messages show up");
|
||||
do_check_true(errorPos >= 0);
|
||||
do_check_true(bazPos > errorPos);
|
||||
do_check_true(barPos > bazPos);
|
||||
do_check_true(fooPos > barPos);
|
||||
}
|
Loading…
Reference in New Issue
Block a user