Merge fx-sync to mozilla-central.

This commit is contained in:
Edward Lee 2010-06-23 15:20:11 -07:00
commit 21e0aa0006
97 changed files with 17445 additions and 0 deletions

View 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);
};

File diff suppressed because it is too large Load Diff

View 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
View 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
]);
}

View 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

View 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

View 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;
}
};

View 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);
}
};
}
};

View 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
};

View 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"
};

View 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];
}
};

View 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
}))];

View 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 = [];
}
};

File diff suppressed because it is too large Load Diff

View 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 = {};
},
};

View 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);
}
}
};

View 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;
}
};

View 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;
}
}
};

View 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");
}
}
};

View 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++;
},
}

View 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() {}
};

View 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");
}

View 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();
}
}

View 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);

View 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)
};

View 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
}
};

View 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;
}

View 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;
}
};

File diff suppressed because it is too large Load Diff

View 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();

View 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";
}
};

View 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();
}
};

View 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");

View 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"]);

View 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"]);

View 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"]);

View 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"]);

View 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"]);

View 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"]);

View 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)));

View 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);

View 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);
}
};
}

View 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();

View 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";

View 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);
};
}
};

View 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() {});
}

View 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!");
}
}

View 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"]]]);
}

View 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);
}

View 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ê");
}

View 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");
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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();
}

View 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);
}

View 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!");
}
}

View 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, {});
}
}

View 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);
}

View 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);
}

View 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() {}); }
}

View 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);
}

View 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() {}); }
}

View 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() {}); }
}

View 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() {});
}

View 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();
}

View 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() {});
}
}
}

View 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() {});
}
}

View 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();
}

View 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() {});
}
}

View 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() {});
}
}

View 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.");
}

View 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();
}
}

View 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("");
}
}

View 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() {});
}
}

View 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);
}

View 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();
}

File diff suppressed because it is too large Load Diff

View 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);
}

View 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);
}

View 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);
}

View 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));
}

View 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!!!");
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}
}

View 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);
}

View 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);
}

View 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);
}

View 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");
}

View 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);
}

View 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);
}