mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
1482122303
--HG-- extra : rebase_source : 98337b6a8c07d05e8c961a452dd05a7d75c3c60b
582 lines
18 KiB
JavaScript
582 lines
18 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
this.EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "NetUtil", "PlacesUtils",
|
|
"FileUtils", "Utils", "Async", "Svc", "Str"];
|
|
|
|
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
|
|
|
Cu.import("resource://services-common/log4moz.js");
|
|
Cu.import("resource://services-common/observers.js");
|
|
Cu.import("resource://services-common/preferences.js");
|
|
Cu.import("resource://services-common/stringbundle.js");
|
|
Cu.import("resource://services-common/utils.js");
|
|
Cu.import("resource://services-common/async.js", this);
|
|
Cu.import("resource://services-crypto/utils.js");
|
|
Cu.import("resource://services-sync/constants.js");
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
|
|
Cu.import("resource://gre/modules/Services.jsm", this);
|
|
Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
|
|
Cu.import("resource://gre/modules/NetUtil.jsm", this);
|
|
Cu.import("resource://gre/modules/FileUtils.jsm", this);
|
|
|
|
/*
|
|
* Utility functions
|
|
*/
|
|
|
|
this.Utils = {
|
|
// Alias in functions from CommonUtils. These previously were defined here.
|
|
// In the ideal world, references to these would be removed.
|
|
nextTick: CommonUtils.nextTick,
|
|
namedTimer: CommonUtils.namedTimer,
|
|
exceptionStr: CommonUtils.exceptionStr,
|
|
stackTrace: CommonUtils.stackTrace,
|
|
makeURI: CommonUtils.makeURI,
|
|
encodeUTF8: CommonUtils.encodeUTF8,
|
|
decodeUTF8: CommonUtils.decodeUTF8,
|
|
safeAtoB: CommonUtils.safeAtoB,
|
|
byteArrayToString: CommonUtils.byteArrayToString,
|
|
bytesAsHex: CommonUtils.bytesAsHex,
|
|
encodeBase32: CommonUtils.encodeBase32,
|
|
decodeBase32: CommonUtils.decodeBase32,
|
|
|
|
// Aliases from CryptoUtils.
|
|
generateRandomBytes: CryptoUtils.generateRandomBytes,
|
|
computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
|
|
digestUTF8: CryptoUtils.digestUTF8,
|
|
digestBytes: CryptoUtils.digestBytes,
|
|
sha1: CryptoUtils.sha1,
|
|
sha1Base32: CryptoUtils.sha1Base32,
|
|
makeHMACKey: CryptoUtils.makeHMACKey,
|
|
makeHMACHasher: CryptoUtils.makeHMACHasher,
|
|
hkdfExpand: CryptoUtils.hkdfExpand,
|
|
pbkdf2Generate: CryptoUtils.pbkdf2Generate,
|
|
deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
|
|
getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
|
|
|
|
/**
|
|
* Wrap a function to catch all exceptions and log them
|
|
*
|
|
* @usage MyObj._catch = Utils.catch;
|
|
* MyObj.foo = function() { this._catch(func)(); }
|
|
*
|
|
* Optionally pass a function which will be called if an
|
|
* exception occurs.
|
|
*/
|
|
catch: function Utils_catch(func, exceptionCallback) {
|
|
let thisArg = this;
|
|
return function WrappedCatch() {
|
|
try {
|
|
return func.call(thisArg);
|
|
}
|
|
catch(ex) {
|
|
thisArg._log.debug("Exception: " + Utils.exceptionStr(ex));
|
|
if (exceptionCallback) {
|
|
return exceptionCallback.call(thisArg, ex);
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 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 lock(label, func) {
|
|
let thisArg = this;
|
|
return function WrappedLock() {
|
|
if (!thisArg.lock()) {
|
|
throw "Could not acquire lock. Label: \"" + label + "\".";
|
|
}
|
|
|
|
try {
|
|
return func.call(thisArg);
|
|
}
|
|
finally {
|
|
thisArg.unlock();
|
|
}
|
|
};
|
|
},
|
|
|
|
isLockException: function isLockException(ex) {
|
|
return ex && ex.indexOf && ex.indexOf("Could not acquire lock.") == 0;
|
|
},
|
|
|
|
/**
|
|
* Wrap functions to notify when it starts and finishes executing or if it
|
|
* threw an error.
|
|
*
|
|
* The message is a combination of a provided prefix, the local name, and
|
|
* the event. Possible events are: "start", "finish", "error". The subject
|
|
* is the function's return value on "finish" or the caught exception on
|
|
* "error". The data argument is the predefined data value.
|
|
*
|
|
* Example:
|
|
*
|
|
* @usage function MyObj(name) {
|
|
* this.name = name;
|
|
* this._notify = Utils.notify("obj:");
|
|
* }
|
|
* MyObj.prototype = {
|
|
* foo: function() this._notify("func", "data-arg", function () {
|
|
* //...
|
|
* }(),
|
|
* };
|
|
*/
|
|
notify: function Utils_notify(prefix) {
|
|
return function NotifyMaker(name, data, func) {
|
|
let thisArg = this;
|
|
let notify = function(state, subject) {
|
|
let mesg = prefix + name + ":" + state;
|
|
thisArg._log.trace("Event: " + mesg);
|
|
Observers.notify(mesg, subject, data);
|
|
};
|
|
|
|
return function WrappedNotify() {
|
|
try {
|
|
notify("start", null);
|
|
let ret = func.call(thisArg);
|
|
notify("finish", ret);
|
|
return ret;
|
|
}
|
|
catch(ex) {
|
|
notify("error", ex);
|
|
throw ex;
|
|
}
|
|
};
|
|
};
|
|
},
|
|
|
|
runInTransaction: function(db, callback, thisObj) {
|
|
let hasTransaction = false;
|
|
try {
|
|
db.beginTransaction();
|
|
hasTransaction = true;
|
|
} catch(e) { /* om nom nom exceptions */ }
|
|
|
|
try {
|
|
return callback.call(thisObj);
|
|
} finally {
|
|
if (hasTransaction) {
|
|
db.commitTransaction();
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* GUIDs are 9 random bytes encoded with base64url (RFC 4648).
|
|
* That makes them 12 characters long with 72 bits of entropy.
|
|
*/
|
|
makeGUID: function makeGUID() {
|
|
return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
|
|
},
|
|
|
|
_base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
|
|
checkGUID: function checkGUID(guid) {
|
|
return !!guid && this._base64url_regex.test(guid);
|
|
},
|
|
|
|
/**
|
|
* 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
|
|
* Property of obj to defer to
|
|
* @param prop
|
|
* Property name to defer (or an array of property names)
|
|
*/
|
|
deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
|
|
if (Array.isArray(prop))
|
|
return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop));
|
|
|
|
let prot = obj.prototype;
|
|
|
|
// Create a getter if it doesn't exist yet
|
|
if (!prot.__lookupGetter__(prop)) {
|
|
prot.__defineGetter__(prop, function () {
|
|
return this[defer][prop];
|
|
});
|
|
}
|
|
|
|
// Create a setter if it doesn't exist yet
|
|
if (!prot.__lookupSetter__(prop)) {
|
|
prot.__defineSetter__(prop, function (val) {
|
|
this[defer][prop] = val;
|
|
});
|
|
}
|
|
},
|
|
|
|
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;
|
|
},
|
|
|
|
// Generator and discriminator for HMAC exceptions.
|
|
// Split these out in case we want to make them richer in future, and to
|
|
// avoid inevitable confusion if the message changes.
|
|
throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
|
|
throw "Record SHA256 HMAC mismatch: should be " + shouldBe + ", is " + is;
|
|
},
|
|
|
|
isHMACMismatch: function isHMACMismatch(ex) {
|
|
const hmacFail = "Record SHA256 HMAC mismatch: ";
|
|
return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
|
|
},
|
|
|
|
/**
|
|
* Turn RFC 4648 base32 into our own user-friendly version.
|
|
* ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
|
|
* becomes
|
|
* abcdefghijk8mn9pqrstuvwxyz234567
|
|
*/
|
|
base32ToFriendly: function base32ToFriendly(input) {
|
|
return input.toLowerCase()
|
|
.replace("l", '8', "g")
|
|
.replace("o", '9', "g");
|
|
},
|
|
|
|
base32FromFriendly: function base32FromFriendly(input) {
|
|
return input.toUpperCase()
|
|
.replace("8", 'L', "g")
|
|
.replace("9", 'O', "g");
|
|
},
|
|
|
|
/**
|
|
* Key manipulation.
|
|
*/
|
|
|
|
// Return an octet string in friendly base32 *with no trailing =*.
|
|
encodeKeyBase32: function encodeKeyBase32(keyData) {
|
|
return Utils.base32ToFriendly(
|
|
Utils.encodeBase32(keyData))
|
|
.slice(0, SYNC_KEY_ENCODED_LENGTH);
|
|
},
|
|
|
|
decodeKeyBase32: function decodeKeyBase32(encoded) {
|
|
return Utils.decodeBase32(
|
|
Utils.base32FromFriendly(
|
|
Utils.normalizePassphrase(encoded)))
|
|
.slice(0, SYNC_KEY_DECODED_LENGTH);
|
|
},
|
|
|
|
base64Key: function base64Key(keyData) {
|
|
return btoa(keyData);
|
|
},
|
|
|
|
/**
|
|
* N.B., salt should be base64 encoded, even though we have to decode
|
|
* it later!
|
|
*/
|
|
derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
|
let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
|
|
forceJS);
|
|
return Utils.encodeKeyBase32(k);
|
|
},
|
|
|
|
/**
|
|
* N.B., salt should be base64 encoded, even though we have to decode
|
|
* it later!
|
|
*/
|
|
deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
|
let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
|
|
forceJS);
|
|
return Utils.base64Key(k);
|
|
},
|
|
|
|
/**
|
|
* Take a base64-encoded 128-bit AES key, returning it as five groups of five
|
|
* uppercase alphanumeric characters, separated by hyphens.
|
|
* A.K.A. base64-to-base32 encoding.
|
|
*/
|
|
presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
|
|
return Utils.encodeKeyBase32(atob(encodedKey));
|
|
},
|
|
|
|
jsonLoad: function jsonLoad(path, that, callback) {
|
|
CommonUtils.jsonLoad("weave/" + path, that, callback);
|
|
},
|
|
|
|
jsonSave: function jsonSave(path, that, obj, callback) {
|
|
CommonUtils.jsonSave("weave/" + path, that, obj, callback);
|
|
},
|
|
|
|
getIcon: function(iconUri, defaultIcon) {
|
|
try {
|
|
let iconURI = Utils.makeURI(iconUri);
|
|
return PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
|
|
}
|
|
catch(ex) {}
|
|
|
|
// Just give the provided default icon or the system's default
|
|
return defaultIcon || PlacesUtils.favicons.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");
|
|
},
|
|
|
|
/**
|
|
* Generate 26 characters.
|
|
*/
|
|
generatePassphrase: function generatePassphrase() {
|
|
// Note that this is a different base32 alphabet to the one we use for
|
|
// other tasks. It's lowercase, uses different letters, and needs to be
|
|
// decoded with decodeKeyBase32, not just decodeBase32.
|
|
return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16));
|
|
},
|
|
|
|
/**
|
|
* The following are the methods supported for UI use:
|
|
*
|
|
* * isPassphrase:
|
|
* determines whether a string is either a normalized or presentable
|
|
* passphrase.
|
|
* * hyphenatePassphrase:
|
|
* present a normalized passphrase for display. This might actually
|
|
* perform work beyond just hyphenation; sorry.
|
|
* * hyphenatePartialPassphrase:
|
|
* present a fragment of a normalized passphrase for display.
|
|
* * normalizePassphrase:
|
|
* take a presentable passphrase and reduce it to a normalized
|
|
* representation for storage. normalizePassphrase can safely be called
|
|
* on normalized input.
|
|
* * normalizeAccount:
|
|
* take user input for account/username, cleaning up appropriately.
|
|
*/
|
|
|
|
isPassphrase: function(s) {
|
|
if (s) {
|
|
return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
|
|
}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Hyphenate a passphrase (26 characters) into groups.
|
|
* abbbbccccddddeeeeffffggggh
|
|
* =>
|
|
* a-bbbbc-cccdd-ddeee-effff-ggggh
|
|
*/
|
|
hyphenatePassphrase: function hyphenatePassphrase(passphrase) {
|
|
// For now, these are the same.
|
|
return Utils.hyphenatePartialPassphrase(passphrase, true);
|
|
},
|
|
|
|
hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) {
|
|
if (!passphrase)
|
|
return null;
|
|
|
|
// Get the raw data input. Just base32.
|
|
let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, "");
|
|
|
|
// This is the neatest way to do this.
|
|
if ((data.length == 1) && !omitTrailingDash)
|
|
return data + "-";
|
|
|
|
// Hyphenate it.
|
|
let y = data.substr(0,1);
|
|
let z = data.substr(1).replace(/(.{1,5})/g, "-$1");
|
|
|
|
// Correct length? We're done.
|
|
if ((z.length == 30) || omitTrailingDash)
|
|
return y + z;
|
|
|
|
// Add a trailing dash if appropriate.
|
|
return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH);
|
|
},
|
|
|
|
normalizePassphrase: function normalizePassphrase(pp) {
|
|
// Short var name... have you seen the lines below?!
|
|
// Allow leading and trailing whitespace.
|
|
pp = pp.trim().toLowerCase();
|
|
|
|
// 20-char sync key.
|
|
if (pp.length == 23 &&
|
|
[5, 11, 17].every(function(i) pp[i] == '-')) {
|
|
|
|
return pp.slice(0, 5) + pp.slice(6, 11)
|
|
+ pp.slice(12, 17) + pp.slice(18, 23);
|
|
}
|
|
|
|
// "Modern" 26-char key.
|
|
if (pp.length == 31 &&
|
|
[1, 7, 13, 19, 25].every(function(i) pp[i] == '-')) {
|
|
|
|
return pp.slice(0, 1) + pp.slice(2, 7)
|
|
+ pp.slice(8, 13) + pp.slice(14, 19)
|
|
+ pp.slice(20, 25) + pp.slice(26, 31);
|
|
}
|
|
|
|
// Something else -- just return.
|
|
return pp;
|
|
},
|
|
|
|
normalizeAccount: function normalizeAccount(acc) {
|
|
return acc.trim();
|
|
},
|
|
|
|
/**
|
|
* Create an array like the first but without elements of the second. Reuse
|
|
* arrays if possible.
|
|
*/
|
|
arraySub: function arraySub(minuend, subtrahend) {
|
|
if (!minuend.length || !subtrahend.length)
|
|
return minuend;
|
|
return minuend.filter(function(i) subtrahend.indexOf(i) == -1);
|
|
},
|
|
|
|
/**
|
|
* Build the union of two arrays. Reuse arrays if possible.
|
|
*/
|
|
arrayUnion: function arrayUnion(foo, bar) {
|
|
if (!foo.length)
|
|
return bar;
|
|
if (!bar.length)
|
|
return foo;
|
|
return foo.concat(Utils.arraySub(bar, foo));
|
|
},
|
|
|
|
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
|
|
|| status == slots.SLOT_UNINITIALIZED)
|
|
return false;
|
|
|
|
if (status == slots.SLOT_NOT_LOGGED_IN)
|
|
return true;
|
|
|
|
// something wacky happened, pretend MP is locked
|
|
return true;
|
|
},
|
|
|
|
// If Master Password is enabled and locked, present a dialog to unlock it.
|
|
// Return whether the system is unlocked.
|
|
ensureMPUnlocked: function ensureMPUnlocked() {
|
|
if (!Utils.mpLocked()) {
|
|
return true;
|
|
}
|
|
let sdr = Cc["@mozilla.org/security/sdr;1"]
|
|
.getService(Ci.nsISecretDecoderRing);
|
|
try {
|
|
sdr.encryptString("bacon");
|
|
return true;
|
|
} catch(e) {}
|
|
return false;
|
|
},
|
|
|
|
/**
|
|
* Return a value for a backoff interval. Maximum is eight hours, unless
|
|
* Status.backoffInterval is higher.
|
|
*
|
|
*/
|
|
calculateBackoff: function calculateBackoff(attempts, baseInterval,
|
|
statusInterval) {
|
|
let backoffInterval = attempts *
|
|
(Math.floor(Math.random() * baseInterval) +
|
|
baseInterval);
|
|
return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
|
|
statusInterval);
|
|
},
|
|
};
|
|
|
|
XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
|
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
|
converter.charset = "UTF-8";
|
|
return converter;
|
|
});
|
|
|
|
/*
|
|
* Commonly-used services
|
|
*/
|
|
this.Svc = {};
|
|
Svc.Prefs = new Preferences(PREFS_BRANCH);
|
|
Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true});
|
|
Svc.Obs = Observers;
|
|
|
|
let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
|
|
"@mozilla.org/suite/sessionstore;1" :
|
|
"@mozilla.org/browser/sessionstore;1";
|
|
|
|
[["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"],
|
|
["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
|
|
["Session", _sessionCID, "nsISessionStore"]
|
|
].forEach(function([name, contract, iface]) {
|
|
XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
|
|
});
|
|
|
|
// nsIPrivateBrowsingService is not implemented in mobile Firefox.
|
|
// Svc.Private should just return undefined in this case instead of throwing.
|
|
XPCOMUtils.defineLazyGetter(Svc, "Private", function() {
|
|
try {
|
|
return Cc["@mozilla.org/privatebrowsing;1"].getService(Ci["nsIPrivateBrowsingService"]);
|
|
} catch (e) {
|
|
return undefined;
|
|
}
|
|
});
|
|
|
|
Svc.__defineGetter__("Crypto", function() {
|
|
let cryptoSvc;
|
|
let ns = {};
|
|
Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
|
|
cryptoSvc = new ns.WeaveCrypto();
|
|
delete Svc.Crypto;
|
|
return Svc.Crypto = cryptoSvc;
|
|
});
|
|
|
|
this.Str = {};
|
|
["errors", "sync"].forEach(function(lazy) {
|
|
XPCOMUtils.defineLazyGetter(Str, lazy, Utils.lazyStrings(lazy));
|
|
});
|
|
|
|
Svc.Obs.add("xpcom-shutdown", function () {
|
|
for (let name in Svc)
|
|
delete Svc[name];
|
|
});
|