services/sync/modules/util.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 this.EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"];
michael@0 6
michael@0 7 const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
michael@0 8
michael@0 9 Cu.import("resource://gre/modules/Log.jsm");
michael@0 10 Cu.import("resource://services-common/observers.js");
michael@0 11 Cu.import("resource://services-common/stringbundle.js");
michael@0 12 Cu.import("resource://services-common/utils.js");
michael@0 13 Cu.import("resource://services-common/async.js", this);
michael@0 14 Cu.import("resource://services-crypto/utils.js");
michael@0 15 Cu.import("resource://services-sync/constants.js");
michael@0 16 Cu.import("resource://gre/modules/Preferences.jsm");
michael@0 17 Cu.import("resource://gre/modules/Services.jsm", this);
michael@0 18 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
michael@0 19 Cu.import("resource://gre/modules/osfile.jsm", this);
michael@0 20 Cu.import("resource://gre/modules/Task.jsm", this);
michael@0 21
michael@0 22 /*
michael@0 23 * Utility functions
michael@0 24 */
michael@0 25
michael@0 26 this.Utils = {
michael@0 27 // Alias in functions from CommonUtils. These previously were defined here.
michael@0 28 // In the ideal world, references to these would be removed.
michael@0 29 nextTick: CommonUtils.nextTick,
michael@0 30 namedTimer: CommonUtils.namedTimer,
michael@0 31 exceptionStr: CommonUtils.exceptionStr,
michael@0 32 stackTrace: CommonUtils.stackTrace,
michael@0 33 makeURI: CommonUtils.makeURI,
michael@0 34 encodeUTF8: CommonUtils.encodeUTF8,
michael@0 35 decodeUTF8: CommonUtils.decodeUTF8,
michael@0 36 safeAtoB: CommonUtils.safeAtoB,
michael@0 37 byteArrayToString: CommonUtils.byteArrayToString,
michael@0 38 bytesAsHex: CommonUtils.bytesAsHex,
michael@0 39 hexToBytes: CommonUtils.hexToBytes,
michael@0 40 encodeBase32: CommonUtils.encodeBase32,
michael@0 41 decodeBase32: CommonUtils.decodeBase32,
michael@0 42
michael@0 43 // Aliases from CryptoUtils.
michael@0 44 generateRandomBytes: CryptoUtils.generateRandomBytes,
michael@0 45 computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
michael@0 46 digestUTF8: CryptoUtils.digestUTF8,
michael@0 47 digestBytes: CryptoUtils.digestBytes,
michael@0 48 sha1: CryptoUtils.sha1,
michael@0 49 sha1Base32: CryptoUtils.sha1Base32,
michael@0 50 makeHMACKey: CryptoUtils.makeHMACKey,
michael@0 51 makeHMACHasher: CryptoUtils.makeHMACHasher,
michael@0 52 hkdfExpand: CryptoUtils.hkdfExpand,
michael@0 53 pbkdf2Generate: CryptoUtils.pbkdf2Generate,
michael@0 54 deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
michael@0 55 getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
michael@0 56
michael@0 57 /**
michael@0 58 * Wrap a function to catch all exceptions and log them
michael@0 59 *
michael@0 60 * @usage MyObj._catch = Utils.catch;
michael@0 61 * MyObj.foo = function() { this._catch(func)(); }
michael@0 62 *
michael@0 63 * Optionally pass a function which will be called if an
michael@0 64 * exception occurs.
michael@0 65 */
michael@0 66 catch: function Utils_catch(func, exceptionCallback) {
michael@0 67 let thisArg = this;
michael@0 68 return function WrappedCatch() {
michael@0 69 try {
michael@0 70 return func.call(thisArg);
michael@0 71 }
michael@0 72 catch(ex) {
michael@0 73 thisArg._log.debug("Exception: " + Utils.exceptionStr(ex));
michael@0 74 if (exceptionCallback) {
michael@0 75 return exceptionCallback.call(thisArg, ex);
michael@0 76 }
michael@0 77 return null;
michael@0 78 }
michael@0 79 };
michael@0 80 },
michael@0 81
michael@0 82 /**
michael@0 83 * Wrap a function to call lock before calling the function then unlock.
michael@0 84 *
michael@0 85 * @usage MyObj._lock = Utils.lock;
michael@0 86 * MyObj.foo = function() { this._lock(func)(); }
michael@0 87 */
michael@0 88 lock: function lock(label, func) {
michael@0 89 let thisArg = this;
michael@0 90 return function WrappedLock() {
michael@0 91 if (!thisArg.lock()) {
michael@0 92 throw "Could not acquire lock. Label: \"" + label + "\".";
michael@0 93 }
michael@0 94
michael@0 95 try {
michael@0 96 return func.call(thisArg);
michael@0 97 }
michael@0 98 finally {
michael@0 99 thisArg.unlock();
michael@0 100 }
michael@0 101 };
michael@0 102 },
michael@0 103
michael@0 104 isLockException: function isLockException(ex) {
michael@0 105 return ex && ex.indexOf && ex.indexOf("Could not acquire lock.") == 0;
michael@0 106 },
michael@0 107
michael@0 108 /**
michael@0 109 * Wrap functions to notify when it starts and finishes executing or if it
michael@0 110 * threw an error.
michael@0 111 *
michael@0 112 * The message is a combination of a provided prefix, the local name, and
michael@0 113 * the event. Possible events are: "start", "finish", "error". The subject
michael@0 114 * is the function's return value on "finish" or the caught exception on
michael@0 115 * "error". The data argument is the predefined data value.
michael@0 116 *
michael@0 117 * Example:
michael@0 118 *
michael@0 119 * @usage function MyObj(name) {
michael@0 120 * this.name = name;
michael@0 121 * this._notify = Utils.notify("obj:");
michael@0 122 * }
michael@0 123 * MyObj.prototype = {
michael@0 124 * foo: function() this._notify("func", "data-arg", function () {
michael@0 125 * //...
michael@0 126 * }(),
michael@0 127 * };
michael@0 128 */
michael@0 129 notify: function Utils_notify(prefix) {
michael@0 130 return function NotifyMaker(name, data, func) {
michael@0 131 let thisArg = this;
michael@0 132 let notify = function(state, subject) {
michael@0 133 let mesg = prefix + name + ":" + state;
michael@0 134 thisArg._log.trace("Event: " + mesg);
michael@0 135 Observers.notify(mesg, subject, data);
michael@0 136 };
michael@0 137
michael@0 138 return function WrappedNotify() {
michael@0 139 try {
michael@0 140 notify("start", null);
michael@0 141 let ret = func.call(thisArg);
michael@0 142 notify("finish", ret);
michael@0 143 return ret;
michael@0 144 }
michael@0 145 catch(ex) {
michael@0 146 notify("error", ex);
michael@0 147 throw ex;
michael@0 148 }
michael@0 149 };
michael@0 150 };
michael@0 151 },
michael@0 152
michael@0 153 runInTransaction: function(db, callback, thisObj) {
michael@0 154 let hasTransaction = false;
michael@0 155 try {
michael@0 156 db.beginTransaction();
michael@0 157 hasTransaction = true;
michael@0 158 } catch(e) { /* om nom nom exceptions */ }
michael@0 159
michael@0 160 try {
michael@0 161 return callback.call(thisObj);
michael@0 162 } finally {
michael@0 163 if (hasTransaction) {
michael@0 164 db.commitTransaction();
michael@0 165 }
michael@0 166 }
michael@0 167 },
michael@0 168
michael@0 169 /**
michael@0 170 * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
michael@0 171 * That makes them 12 characters long with 72 bits of entropy.
michael@0 172 */
michael@0 173 makeGUID: function makeGUID() {
michael@0 174 return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
michael@0 175 },
michael@0 176
michael@0 177 _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
michael@0 178 checkGUID: function checkGUID(guid) {
michael@0 179 return !!guid && this._base64url_regex.test(guid);
michael@0 180 },
michael@0 181
michael@0 182 /**
michael@0 183 * Add a simple getter/setter to an object that defers access of a property
michael@0 184 * to an inner property.
michael@0 185 *
michael@0 186 * @param obj
michael@0 187 * Object to add properties to defer in its prototype
michael@0 188 * @param defer
michael@0 189 * Property of obj to defer to
michael@0 190 * @param prop
michael@0 191 * Property name to defer (or an array of property names)
michael@0 192 */
michael@0 193 deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
michael@0 194 if (Array.isArray(prop))
michael@0 195 return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop));
michael@0 196
michael@0 197 let prot = obj.prototype;
michael@0 198
michael@0 199 // Create a getter if it doesn't exist yet
michael@0 200 if (!prot.__lookupGetter__(prop)) {
michael@0 201 prot.__defineGetter__(prop, function () {
michael@0 202 return this[defer][prop];
michael@0 203 });
michael@0 204 }
michael@0 205
michael@0 206 // Create a setter if it doesn't exist yet
michael@0 207 if (!prot.__lookupSetter__(prop)) {
michael@0 208 prot.__defineSetter__(prop, function (val) {
michael@0 209 this[defer][prop] = val;
michael@0 210 });
michael@0 211 }
michael@0 212 },
michael@0 213
michael@0 214 lazyStrings: function Weave_lazyStrings(name) {
michael@0 215 let bundle = "chrome://weave/locale/services/" + name + ".properties";
michael@0 216 return function() new StringBundle(bundle);
michael@0 217 },
michael@0 218
michael@0 219 deepEquals: function eq(a, b) {
michael@0 220 // If they're triple equals, then it must be equals!
michael@0 221 if (a === b)
michael@0 222 return true;
michael@0 223
michael@0 224 // If they weren't equal, they must be objects to be different
michael@0 225 if (typeof a != "object" || typeof b != "object")
michael@0 226 return false;
michael@0 227
michael@0 228 // But null objects won't have properties to compare
michael@0 229 if (a === null || b === null)
michael@0 230 return false;
michael@0 231
michael@0 232 // Make sure all of a's keys have a matching value in b
michael@0 233 for (let k in a)
michael@0 234 if (!eq(a[k], b[k]))
michael@0 235 return false;
michael@0 236
michael@0 237 // Do the same for b's keys but skip those that we already checked
michael@0 238 for (let k in b)
michael@0 239 if (!(k in a) && !eq(a[k], b[k]))
michael@0 240 return false;
michael@0 241
michael@0 242 return true;
michael@0 243 },
michael@0 244
michael@0 245 // Generator and discriminator for HMAC exceptions.
michael@0 246 // Split these out in case we want to make them richer in future, and to
michael@0 247 // avoid inevitable confusion if the message changes.
michael@0 248 throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
michael@0 249 throw "Record SHA256 HMAC mismatch: should be " + shouldBe + ", is " + is;
michael@0 250 },
michael@0 251
michael@0 252 isHMACMismatch: function isHMACMismatch(ex) {
michael@0 253 const hmacFail = "Record SHA256 HMAC mismatch: ";
michael@0 254 return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
michael@0 255 },
michael@0 256
michael@0 257 /**
michael@0 258 * Turn RFC 4648 base32 into our own user-friendly version.
michael@0 259 * ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
michael@0 260 * becomes
michael@0 261 * abcdefghijk8mn9pqrstuvwxyz234567
michael@0 262 */
michael@0 263 base32ToFriendly: function base32ToFriendly(input) {
michael@0 264 return input.toLowerCase()
michael@0 265 .replace("l", '8', "g")
michael@0 266 .replace("o", '9', "g");
michael@0 267 },
michael@0 268
michael@0 269 base32FromFriendly: function base32FromFriendly(input) {
michael@0 270 return input.toUpperCase()
michael@0 271 .replace("8", 'L', "g")
michael@0 272 .replace("9", 'O', "g");
michael@0 273 },
michael@0 274
michael@0 275 /**
michael@0 276 * Key manipulation.
michael@0 277 */
michael@0 278
michael@0 279 // Return an octet string in friendly base32 *with no trailing =*.
michael@0 280 encodeKeyBase32: function encodeKeyBase32(keyData) {
michael@0 281 return Utils.base32ToFriendly(
michael@0 282 Utils.encodeBase32(keyData))
michael@0 283 .slice(0, SYNC_KEY_ENCODED_LENGTH);
michael@0 284 },
michael@0 285
michael@0 286 decodeKeyBase32: function decodeKeyBase32(encoded) {
michael@0 287 return Utils.decodeBase32(
michael@0 288 Utils.base32FromFriendly(
michael@0 289 Utils.normalizePassphrase(encoded)))
michael@0 290 .slice(0, SYNC_KEY_DECODED_LENGTH);
michael@0 291 },
michael@0 292
michael@0 293 base64Key: function base64Key(keyData) {
michael@0 294 return btoa(keyData);
michael@0 295 },
michael@0 296
michael@0 297 /**
michael@0 298 * N.B., salt should be base64 encoded, even though we have to decode
michael@0 299 * it later!
michael@0 300 */
michael@0 301 derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
michael@0 302 let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
michael@0 303 forceJS);
michael@0 304 return Utils.encodeKeyBase32(k);
michael@0 305 },
michael@0 306
michael@0 307 /**
michael@0 308 * N.B., salt should be base64 encoded, even though we have to decode
michael@0 309 * it later!
michael@0 310 */
michael@0 311 deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
michael@0 312 let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
michael@0 313 forceJS);
michael@0 314 return Utils.base64Key(k);
michael@0 315 },
michael@0 316
michael@0 317 /**
michael@0 318 * Take a base64-encoded 128-bit AES key, returning it as five groups of five
michael@0 319 * uppercase alphanumeric characters, separated by hyphens.
michael@0 320 * A.K.A. base64-to-base32 encoding.
michael@0 321 */
michael@0 322 presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
michael@0 323 return Utils.encodeKeyBase32(atob(encodedKey));
michael@0 324 },
michael@0 325
michael@0 326 /**
michael@0 327 * Load a JSON file from disk in the profile directory.
michael@0 328 *
michael@0 329 * @param filePath
michael@0 330 * JSON file path load from profile. Loaded file will be
michael@0 331 * <profile>/<filePath>.json. i.e. Do not specify the ".json"
michael@0 332 * extension.
michael@0 333 * @param that
michael@0 334 * Object to use for logging and "this" for callback.
michael@0 335 * @param callback
michael@0 336 * Function to process json object as its first argument. If the file
michael@0 337 * could not be loaded, the first argument will be undefined.
michael@0 338 */
michael@0 339 jsonLoad: Task.async(function*(filePath, that, callback) {
michael@0 340 let path = OS.Path.join(OS.Constants.Path.profileDir, "weave", filePath + ".json");
michael@0 341
michael@0 342 if (that._log) {
michael@0 343 that._log.trace("Loading json from disk: " + filePath);
michael@0 344 }
michael@0 345
michael@0 346 let json;
michael@0 347
michael@0 348 try {
michael@0 349 json = yield CommonUtils.readJSON(path);
michael@0 350 } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
michael@0 351 // Ignore non-existent files.
michael@0 352 } catch (e) {
michael@0 353 if (that._log) {
michael@0 354 that._log.debug("Failed to load json: " +
michael@0 355 CommonUtils.exceptionStr(e));
michael@0 356 }
michael@0 357 }
michael@0 358
michael@0 359 callback.call(that, json);
michael@0 360 }),
michael@0 361
michael@0 362 /**
michael@0 363 * Save a json-able object to disk in the profile directory.
michael@0 364 *
michael@0 365 * @param filePath
michael@0 366 * JSON file path save to <filePath>.json
michael@0 367 * @param that
michael@0 368 * Object to use for logging and "this" for callback
michael@0 369 * @param obj
michael@0 370 * Function to provide json-able object to save. If this isn't a
michael@0 371 * function, it'll be used as the object to make a json string.
michael@0 372 * @param callback
michael@0 373 * Function called when the write has been performed. Optional.
michael@0 374 * The first argument will be a Components.results error
michael@0 375 * constant on error or null if no error was encountered (and
michael@0 376 * the file saved successfully).
michael@0 377 */
michael@0 378 jsonSave: Task.async(function*(filePath, that, obj, callback) {
michael@0 379 let path = OS.Path.join(OS.Constants.Path.profileDir, "weave",
michael@0 380 ...(filePath + ".json").split("/"));
michael@0 381 let dir = OS.Path.dirname(path);
michael@0 382 let error = null;
michael@0 383
michael@0 384 try {
michael@0 385 yield OS.File.makeDir(dir, { from: OS.Constants.Path.profileDir });
michael@0 386
michael@0 387 if (that._log) {
michael@0 388 that._log.trace("Saving json to disk: " + path);
michael@0 389 }
michael@0 390
michael@0 391 let json = typeof obj == "function" ? obj.call(that) : obj;
michael@0 392
michael@0 393 yield CommonUtils.writeJSON(json, path);
michael@0 394 } catch (e) {
michael@0 395 error = e
michael@0 396 }
michael@0 397
michael@0 398 if (typeof callback == "function") {
michael@0 399 callback.call(that, error);
michael@0 400 }
michael@0 401 }),
michael@0 402
michael@0 403 getErrorString: function Utils_getErrorString(error, args) {
michael@0 404 try {
michael@0 405 return Str.errors.get(error, args || null);
michael@0 406 } catch (e) {}
michael@0 407
michael@0 408 // basically returns "Unknown Error"
michael@0 409 return Str.errors.get("error.reason.unknown");
michael@0 410 },
michael@0 411
michael@0 412 /**
michael@0 413 * Generate 26 characters.
michael@0 414 */
michael@0 415 generatePassphrase: function generatePassphrase() {
michael@0 416 // Note that this is a different base32 alphabet to the one we use for
michael@0 417 // other tasks. It's lowercase, uses different letters, and needs to be
michael@0 418 // decoded with decodeKeyBase32, not just decodeBase32.
michael@0 419 return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16));
michael@0 420 },
michael@0 421
michael@0 422 /**
michael@0 423 * The following are the methods supported for UI use:
michael@0 424 *
michael@0 425 * * isPassphrase:
michael@0 426 * determines whether a string is either a normalized or presentable
michael@0 427 * passphrase.
michael@0 428 * * hyphenatePassphrase:
michael@0 429 * present a normalized passphrase for display. This might actually
michael@0 430 * perform work beyond just hyphenation; sorry.
michael@0 431 * * hyphenatePartialPassphrase:
michael@0 432 * present a fragment of a normalized passphrase for display.
michael@0 433 * * normalizePassphrase:
michael@0 434 * take a presentable passphrase and reduce it to a normalized
michael@0 435 * representation for storage. normalizePassphrase can safely be called
michael@0 436 * on normalized input.
michael@0 437 * * normalizeAccount:
michael@0 438 * take user input for account/username, cleaning up appropriately.
michael@0 439 */
michael@0 440
michael@0 441 isPassphrase: function(s) {
michael@0 442 if (s) {
michael@0 443 return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
michael@0 444 }
michael@0 445 return false;
michael@0 446 },
michael@0 447
michael@0 448 /**
michael@0 449 * Hyphenate a passphrase (26 characters) into groups.
michael@0 450 * abbbbccccddddeeeeffffggggh
michael@0 451 * =>
michael@0 452 * a-bbbbc-cccdd-ddeee-effff-ggggh
michael@0 453 */
michael@0 454 hyphenatePassphrase: function hyphenatePassphrase(passphrase) {
michael@0 455 // For now, these are the same.
michael@0 456 return Utils.hyphenatePartialPassphrase(passphrase, true);
michael@0 457 },
michael@0 458
michael@0 459 hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) {
michael@0 460 if (!passphrase)
michael@0 461 return null;
michael@0 462
michael@0 463 // Get the raw data input. Just base32.
michael@0 464 let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, "");
michael@0 465
michael@0 466 // This is the neatest way to do this.
michael@0 467 if ((data.length == 1) && !omitTrailingDash)
michael@0 468 return data + "-";
michael@0 469
michael@0 470 // Hyphenate it.
michael@0 471 let y = data.substr(0,1);
michael@0 472 let z = data.substr(1).replace(/(.{1,5})/g, "-$1");
michael@0 473
michael@0 474 // Correct length? We're done.
michael@0 475 if ((z.length == 30) || omitTrailingDash)
michael@0 476 return y + z;
michael@0 477
michael@0 478 // Add a trailing dash if appropriate.
michael@0 479 return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH);
michael@0 480 },
michael@0 481
michael@0 482 normalizePassphrase: function normalizePassphrase(pp) {
michael@0 483 // Short var name... have you seen the lines below?!
michael@0 484 // Allow leading and trailing whitespace.
michael@0 485 pp = pp.trim().toLowerCase();
michael@0 486
michael@0 487 // 20-char sync key.
michael@0 488 if (pp.length == 23 &&
michael@0 489 [5, 11, 17].every(function(i) pp[i] == '-')) {
michael@0 490
michael@0 491 return pp.slice(0, 5) + pp.slice(6, 11)
michael@0 492 + pp.slice(12, 17) + pp.slice(18, 23);
michael@0 493 }
michael@0 494
michael@0 495 // "Modern" 26-char key.
michael@0 496 if (pp.length == 31 &&
michael@0 497 [1, 7, 13, 19, 25].every(function(i) pp[i] == '-')) {
michael@0 498
michael@0 499 return pp.slice(0, 1) + pp.slice(2, 7)
michael@0 500 + pp.slice(8, 13) + pp.slice(14, 19)
michael@0 501 + pp.slice(20, 25) + pp.slice(26, 31);
michael@0 502 }
michael@0 503
michael@0 504 // Something else -- just return.
michael@0 505 return pp;
michael@0 506 },
michael@0 507
michael@0 508 normalizeAccount: function normalizeAccount(acc) {
michael@0 509 return acc.trim();
michael@0 510 },
michael@0 511
michael@0 512 /**
michael@0 513 * Create an array like the first but without elements of the second. Reuse
michael@0 514 * arrays if possible.
michael@0 515 */
michael@0 516 arraySub: function arraySub(minuend, subtrahend) {
michael@0 517 if (!minuend.length || !subtrahend.length)
michael@0 518 return minuend;
michael@0 519 return minuend.filter(function(i) subtrahend.indexOf(i) == -1);
michael@0 520 },
michael@0 521
michael@0 522 /**
michael@0 523 * Build the union of two arrays. Reuse arrays if possible.
michael@0 524 */
michael@0 525 arrayUnion: function arrayUnion(foo, bar) {
michael@0 526 if (!foo.length)
michael@0 527 return bar;
michael@0 528 if (!bar.length)
michael@0 529 return foo;
michael@0 530 return foo.concat(Utils.arraySub(bar, foo));
michael@0 531 },
michael@0 532
michael@0 533 bind2: function Async_bind2(object, method) {
michael@0 534 return function innerBind() { return method.apply(object, arguments); };
michael@0 535 },
michael@0 536
michael@0 537 /**
michael@0 538 * Is there a master password configured, regardless of current lock state?
michael@0 539 */
michael@0 540 mpEnabled: function mpEnabled() {
michael@0 541 let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
michael@0 542 .getService(Ci.nsIPKCS11ModuleDB);
michael@0 543 let sdrSlot = modules.findSlotByName("");
michael@0 544 let status = sdrSlot.status;
michael@0 545 let slots = Ci.nsIPKCS11Slot;
michael@0 546
michael@0 547 return status != slots.SLOT_UNINITIALIZED && status != slots.SLOT_READY;
michael@0 548 },
michael@0 549
michael@0 550 /**
michael@0 551 * Is there a master password configured and currently locked?
michael@0 552 */
michael@0 553 mpLocked: function mpLocked() {
michael@0 554 let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
michael@0 555 .getService(Ci.nsIPKCS11ModuleDB);
michael@0 556 let sdrSlot = modules.findSlotByName("");
michael@0 557 let status = sdrSlot.status;
michael@0 558 let slots = Ci.nsIPKCS11Slot;
michael@0 559
michael@0 560 if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN
michael@0 561 || status == slots.SLOT_UNINITIALIZED)
michael@0 562 return false;
michael@0 563
michael@0 564 if (status == slots.SLOT_NOT_LOGGED_IN)
michael@0 565 return true;
michael@0 566
michael@0 567 // something wacky happened, pretend MP is locked
michael@0 568 return true;
michael@0 569 },
michael@0 570
michael@0 571 // If Master Password is enabled and locked, present a dialog to unlock it.
michael@0 572 // Return whether the system is unlocked.
michael@0 573 ensureMPUnlocked: function ensureMPUnlocked() {
michael@0 574 if (!Utils.mpLocked()) {
michael@0 575 return true;
michael@0 576 }
michael@0 577 let sdr = Cc["@mozilla.org/security/sdr;1"]
michael@0 578 .getService(Ci.nsISecretDecoderRing);
michael@0 579 try {
michael@0 580 sdr.encryptString("bacon");
michael@0 581 return true;
michael@0 582 } catch(e) {}
michael@0 583 return false;
michael@0 584 },
michael@0 585
michael@0 586 /**
michael@0 587 * Return a value for a backoff interval. Maximum is eight hours, unless
michael@0 588 * Status.backoffInterval is higher.
michael@0 589 *
michael@0 590 */
michael@0 591 calculateBackoff: function calculateBackoff(attempts, baseInterval,
michael@0 592 statusInterval) {
michael@0 593 let backoffInterval = attempts *
michael@0 594 (Math.floor(Math.random() * baseInterval) +
michael@0 595 baseInterval);
michael@0 596 return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
michael@0 597 statusInterval);
michael@0 598 },
michael@0 599
michael@0 600 /**
michael@0 601 * Return a set of hostnames (including the protocol) which may have
michael@0 602 * credentials for sync itself stored in the login manager.
michael@0 603 *
michael@0 604 * In general, these hosts will not have their passwords synced, will be
michael@0 605 * reset when we drop sync credentials, etc.
michael@0 606 */
michael@0 607 getSyncCredentialsHosts: function() {
michael@0 608 // This is somewhat expensive and the result static, so we cache the result.
michael@0 609 if (this._syncCredentialsHosts) {
michael@0 610 return this._syncCredentialsHosts;
michael@0 611 }
michael@0 612 let result = new Set();
michael@0 613 // the legacy sync host.
michael@0 614 result.add(PWDMGR_HOST);
michael@0 615 // The FxA hosts - these almost certainly all have the same hostname, but
michael@0 616 // better safe than sorry...
michael@0 617 for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
michael@0 618 "identity.fxaccounts.remote.signup.uri",
michael@0 619 "identity.fxaccounts.remote.signin.uri",
michael@0 620 "identity.fxaccounts.settings.uri"]) {
michael@0 621 let prefVal;
michael@0 622 try {
michael@0 623 prefVal = Services.prefs.getCharPref(prefName);
michael@0 624 } catch (_) {
michael@0 625 continue;
michael@0 626 }
michael@0 627 let uri = Services.io.newURI(prefVal, null, null);
michael@0 628 result.add(uri.prePath);
michael@0 629 }
michael@0 630 return this._syncCredentialsHosts = result;
michael@0 631 },
michael@0 632 };
michael@0 633
michael@0 634 XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
michael@0 635 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 636 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 637 converter.charset = "UTF-8";
michael@0 638 return converter;
michael@0 639 });
michael@0 640
michael@0 641 /*
michael@0 642 * Commonly-used services
michael@0 643 */
michael@0 644 this.Svc = {};
michael@0 645 Svc.Prefs = new Preferences(PREFS_BRANCH);
michael@0 646 Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true});
michael@0 647 Svc.Obs = Observers;
michael@0 648
michael@0 649 let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
michael@0 650 "@mozilla.org/suite/sessionstore;1" :
michael@0 651 "@mozilla.org/browser/sessionstore;1";
michael@0 652
michael@0 653 [
michael@0 654 ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
michael@0 655 ["Session", _sessionCID, "nsISessionStore"]
michael@0 656 ].forEach(function([name, contract, iface]) {
michael@0 657 XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
michael@0 658 });
michael@0 659
michael@0 660 XPCOMUtils.defineLazyModuleGetter(Svc, "FormHistory", "resource://gre/modules/FormHistory.jsm");
michael@0 661
michael@0 662 Svc.__defineGetter__("Crypto", function() {
michael@0 663 let cryptoSvc;
michael@0 664 let ns = {};
michael@0 665 Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
michael@0 666 cryptoSvc = new ns.WeaveCrypto();
michael@0 667 delete Svc.Crypto;
michael@0 668 return Svc.Crypto = cryptoSvc;
michael@0 669 });
michael@0 670
michael@0 671 this.Str = {};
michael@0 672 ["errors", "sync"].forEach(function(lazy) {
michael@0 673 XPCOMUtils.defineLazyGetter(Str, lazy, Utils.lazyStrings(lazy));
michael@0 674 });
michael@0 675
michael@0 676 Svc.Obs.add("xpcom-shutdown", function () {
michael@0 677 for (let name in Svc)
michael@0 678 delete Svc[name];
michael@0 679 });

mercurial