services/sync/modules/util.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/sync/modules/util.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,679 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +this.EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"];
     1.9 +
    1.10 +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
    1.11 +
    1.12 +Cu.import("resource://gre/modules/Log.jsm");
    1.13 +Cu.import("resource://services-common/observers.js");
    1.14 +Cu.import("resource://services-common/stringbundle.js");
    1.15 +Cu.import("resource://services-common/utils.js");
    1.16 +Cu.import("resource://services-common/async.js", this);
    1.17 +Cu.import("resource://services-crypto/utils.js");
    1.18 +Cu.import("resource://services-sync/constants.js");
    1.19 +Cu.import("resource://gre/modules/Preferences.jsm");
    1.20 +Cu.import("resource://gre/modules/Services.jsm", this);
    1.21 +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
    1.22 +Cu.import("resource://gre/modules/osfile.jsm", this);
    1.23 +Cu.import("resource://gre/modules/Task.jsm", this);
    1.24 +
    1.25 +/*
    1.26 + * Utility functions
    1.27 + */
    1.28 +
    1.29 +this.Utils = {
    1.30 +  // Alias in functions from CommonUtils. These previously were defined here.
    1.31 +  // In the ideal world, references to these would be removed.
    1.32 +  nextTick: CommonUtils.nextTick,
    1.33 +  namedTimer: CommonUtils.namedTimer,
    1.34 +  exceptionStr: CommonUtils.exceptionStr,
    1.35 +  stackTrace: CommonUtils.stackTrace,
    1.36 +  makeURI: CommonUtils.makeURI,
    1.37 +  encodeUTF8: CommonUtils.encodeUTF8,
    1.38 +  decodeUTF8: CommonUtils.decodeUTF8,
    1.39 +  safeAtoB: CommonUtils.safeAtoB,
    1.40 +  byteArrayToString: CommonUtils.byteArrayToString,
    1.41 +  bytesAsHex: CommonUtils.bytesAsHex,
    1.42 +  hexToBytes: CommonUtils.hexToBytes,
    1.43 +  encodeBase32: CommonUtils.encodeBase32,
    1.44 +  decodeBase32: CommonUtils.decodeBase32,
    1.45 +
    1.46 +  // Aliases from CryptoUtils.
    1.47 +  generateRandomBytes: CryptoUtils.generateRandomBytes,
    1.48 +  computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
    1.49 +  digestUTF8: CryptoUtils.digestUTF8,
    1.50 +  digestBytes: CryptoUtils.digestBytes,
    1.51 +  sha1: CryptoUtils.sha1,
    1.52 +  sha1Base32: CryptoUtils.sha1Base32,
    1.53 +  makeHMACKey: CryptoUtils.makeHMACKey,
    1.54 +  makeHMACHasher: CryptoUtils.makeHMACHasher,
    1.55 +  hkdfExpand: CryptoUtils.hkdfExpand,
    1.56 +  pbkdf2Generate: CryptoUtils.pbkdf2Generate,
    1.57 +  deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
    1.58 +  getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
    1.59 +
    1.60 +  /**
    1.61 +   * Wrap a function to catch all exceptions and log them
    1.62 +   *
    1.63 +   * @usage MyObj._catch = Utils.catch;
    1.64 +   *        MyObj.foo = function() { this._catch(func)(); }
    1.65 +   *
    1.66 +   * Optionally pass a function which will be called if an
    1.67 +   * exception occurs.
    1.68 +   */
    1.69 +  catch: function Utils_catch(func, exceptionCallback) {
    1.70 +    let thisArg = this;
    1.71 +    return function WrappedCatch() {
    1.72 +      try {
    1.73 +        return func.call(thisArg);
    1.74 +      }
    1.75 +      catch(ex) {
    1.76 +        thisArg._log.debug("Exception: " + Utils.exceptionStr(ex));
    1.77 +        if (exceptionCallback) {
    1.78 +          return exceptionCallback.call(thisArg, ex);
    1.79 +        }
    1.80 +        return null;
    1.81 +      }
    1.82 +    };
    1.83 +  },
    1.84 +
    1.85 +  /**
    1.86 +   * Wrap a function to call lock before calling the function then unlock.
    1.87 +   *
    1.88 +   * @usage MyObj._lock = Utils.lock;
    1.89 +   *        MyObj.foo = function() { this._lock(func)(); }
    1.90 +   */
    1.91 +  lock: function lock(label, func) {
    1.92 +    let thisArg = this;
    1.93 +    return function WrappedLock() {
    1.94 +      if (!thisArg.lock()) {
    1.95 +        throw "Could not acquire lock. Label: \"" + label + "\".";
    1.96 +      }
    1.97 +
    1.98 +      try {
    1.99 +        return func.call(thisArg);
   1.100 +      }
   1.101 +      finally {
   1.102 +        thisArg.unlock();
   1.103 +      }
   1.104 +    };
   1.105 +  },
   1.106 +
   1.107 +  isLockException: function isLockException(ex) {
   1.108 +    return ex && ex.indexOf && ex.indexOf("Could not acquire lock.") == 0;
   1.109 +  },
   1.110 +
   1.111 +  /**
   1.112 +   * Wrap functions to notify when it starts and finishes executing or if it
   1.113 +   * threw an error.
   1.114 +   *
   1.115 +   * The message is a combination of a provided prefix, the local name, and
   1.116 +   * the event. Possible events are: "start", "finish", "error". The subject
   1.117 +   * is the function's return value on "finish" or the caught exception on
   1.118 +   * "error". The data argument is the predefined data value.
   1.119 +   *
   1.120 +   * Example:
   1.121 +   *
   1.122 +   * @usage function MyObj(name) {
   1.123 +   *          this.name = name;
   1.124 +   *          this._notify = Utils.notify("obj:");
   1.125 +   *        }
   1.126 +   *        MyObj.prototype = {
   1.127 +   *          foo: function() this._notify("func", "data-arg", function () {
   1.128 +   *            //...
   1.129 +   *          }(),
   1.130 +   *        };
   1.131 +   */
   1.132 +  notify: function Utils_notify(prefix) {
   1.133 +    return function NotifyMaker(name, data, func) {
   1.134 +      let thisArg = this;
   1.135 +      let notify = function(state, subject) {
   1.136 +        let mesg = prefix + name + ":" + state;
   1.137 +        thisArg._log.trace("Event: " + mesg);
   1.138 +        Observers.notify(mesg, subject, data);
   1.139 +      };
   1.140 +
   1.141 +      return function WrappedNotify() {
   1.142 +        try {
   1.143 +          notify("start", null);
   1.144 +          let ret = func.call(thisArg);
   1.145 +          notify("finish", ret);
   1.146 +          return ret;
   1.147 +        }
   1.148 +        catch(ex) {
   1.149 +          notify("error", ex);
   1.150 +          throw ex;
   1.151 +        }
   1.152 +      };
   1.153 +    };
   1.154 +  },
   1.155 +
   1.156 +  runInTransaction: function(db, callback, thisObj) {
   1.157 +    let hasTransaction = false;
   1.158 +    try {
   1.159 +      db.beginTransaction();
   1.160 +      hasTransaction = true;
   1.161 +    } catch(e) { /* om nom nom exceptions */ }
   1.162 +
   1.163 +    try {
   1.164 +      return callback.call(thisObj);
   1.165 +    } finally {
   1.166 +      if (hasTransaction) {
   1.167 +        db.commitTransaction();
   1.168 +      }
   1.169 +    }
   1.170 +  },
   1.171 +
   1.172 +  /**
   1.173 +   * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
   1.174 +   * That makes them 12 characters long with 72 bits of entropy.
   1.175 +   */
   1.176 +  makeGUID: function makeGUID() {
   1.177 +    return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
   1.178 +  },
   1.179 +
   1.180 +  _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
   1.181 +  checkGUID: function checkGUID(guid) {
   1.182 +    return !!guid && this._base64url_regex.test(guid);
   1.183 +  },
   1.184 +
   1.185 +  /**
   1.186 +   * Add a simple getter/setter to an object that defers access of a property
   1.187 +   * to an inner property.
   1.188 +   *
   1.189 +   * @param obj
   1.190 +   *        Object to add properties to defer in its prototype
   1.191 +   * @param defer
   1.192 +   *        Property of obj to defer to
   1.193 +   * @param prop
   1.194 +   *        Property name to defer (or an array of property names)
   1.195 +   */
   1.196 +  deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
   1.197 +    if (Array.isArray(prop))
   1.198 +      return prop.map(function(prop) Utils.deferGetSet(obj, defer, prop));
   1.199 +
   1.200 +    let prot = obj.prototype;
   1.201 +
   1.202 +    // Create a getter if it doesn't exist yet
   1.203 +    if (!prot.__lookupGetter__(prop)) {
   1.204 +      prot.__defineGetter__(prop, function () {
   1.205 +        return this[defer][prop];
   1.206 +      });
   1.207 +    }
   1.208 +
   1.209 +    // Create a setter if it doesn't exist yet
   1.210 +    if (!prot.__lookupSetter__(prop)) {
   1.211 +      prot.__defineSetter__(prop, function (val) {
   1.212 +        this[defer][prop] = val;
   1.213 +      });
   1.214 +    }
   1.215 +  },
   1.216 +
   1.217 +  lazyStrings: function Weave_lazyStrings(name) {
   1.218 +    let bundle = "chrome://weave/locale/services/" + name + ".properties";
   1.219 +    return function() new StringBundle(bundle);
   1.220 +  },
   1.221 +
   1.222 +  deepEquals: function eq(a, b) {
   1.223 +    // If they're triple equals, then it must be equals!
   1.224 +    if (a === b)
   1.225 +      return true;
   1.226 +
   1.227 +    // If they weren't equal, they must be objects to be different
   1.228 +    if (typeof a != "object" || typeof b != "object")
   1.229 +      return false;
   1.230 +
   1.231 +    // But null objects won't have properties to compare
   1.232 +    if (a === null || b === null)
   1.233 +      return false;
   1.234 +
   1.235 +    // Make sure all of a's keys have a matching value in b
   1.236 +    for (let k in a)
   1.237 +      if (!eq(a[k], b[k]))
   1.238 +        return false;
   1.239 +
   1.240 +    // Do the same for b's keys but skip those that we already checked
   1.241 +    for (let k in b)
   1.242 +      if (!(k in a) && !eq(a[k], b[k]))
   1.243 +        return false;
   1.244 +
   1.245 +    return true;
   1.246 +  },
   1.247 +
   1.248 +  // Generator and discriminator for HMAC exceptions.
   1.249 +  // Split these out in case we want to make them richer in future, and to
   1.250 +  // avoid inevitable confusion if the message changes.
   1.251 +  throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
   1.252 +    throw "Record SHA256 HMAC mismatch: should be " + shouldBe + ", is " + is;
   1.253 +  },
   1.254 +
   1.255 +  isHMACMismatch: function isHMACMismatch(ex) {
   1.256 +    const hmacFail = "Record SHA256 HMAC mismatch: ";
   1.257 +    return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
   1.258 +  },
   1.259 +
   1.260 +  /**
   1.261 +   * Turn RFC 4648 base32 into our own user-friendly version.
   1.262 +   *   ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
   1.263 +   * becomes
   1.264 +   *   abcdefghijk8mn9pqrstuvwxyz234567
   1.265 +   */
   1.266 +  base32ToFriendly: function base32ToFriendly(input) {
   1.267 +    return input.toLowerCase()
   1.268 +                .replace("l", '8', "g")
   1.269 +                .replace("o", '9', "g");
   1.270 +  },
   1.271 +
   1.272 +  base32FromFriendly: function base32FromFriendly(input) {
   1.273 +    return input.toUpperCase()
   1.274 +                .replace("8", 'L', "g")
   1.275 +                .replace("9", 'O', "g");
   1.276 +  },
   1.277 +
   1.278 +  /**
   1.279 +   * Key manipulation.
   1.280 +   */
   1.281 +
   1.282 +  // Return an octet string in friendly base32 *with no trailing =*.
   1.283 +  encodeKeyBase32: function encodeKeyBase32(keyData) {
   1.284 +    return Utils.base32ToFriendly(
   1.285 +             Utils.encodeBase32(keyData))
   1.286 +           .slice(0, SYNC_KEY_ENCODED_LENGTH);
   1.287 +  },
   1.288 +
   1.289 +  decodeKeyBase32: function decodeKeyBase32(encoded) {
   1.290 +    return Utils.decodeBase32(
   1.291 +             Utils.base32FromFriendly(
   1.292 +               Utils.normalizePassphrase(encoded)))
   1.293 +           .slice(0, SYNC_KEY_DECODED_LENGTH);
   1.294 +  },
   1.295 +
   1.296 +  base64Key: function base64Key(keyData) {
   1.297 +    return btoa(keyData);
   1.298 +  },
   1.299 +
   1.300 +  /**
   1.301 +   * N.B., salt should be base64 encoded, even though we have to decode
   1.302 +   * it later!
   1.303 +   */
   1.304 +  derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
   1.305 +    let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
   1.306 +                                                forceJS);
   1.307 +    return Utils.encodeKeyBase32(k);
   1.308 +  },
   1.309 +
   1.310 +  /**
   1.311 +   * N.B., salt should be base64 encoded, even though we have to decode
   1.312 +   * it later!
   1.313 +   */
   1.314 +  deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
   1.315 +    let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
   1.316 +                                                forceJS);
   1.317 +    return Utils.base64Key(k);
   1.318 +  },
   1.319 +
   1.320 +  /**
   1.321 +   * Take a base64-encoded 128-bit AES key, returning it as five groups of five
   1.322 +   * uppercase alphanumeric characters, separated by hyphens.
   1.323 +   * A.K.A. base64-to-base32 encoding.
   1.324 +   */
   1.325 +  presentEncodedKeyAsSyncKey : function presentEncodedKeyAsSyncKey(encodedKey) {
   1.326 +    return Utils.encodeKeyBase32(atob(encodedKey));
   1.327 +  },
   1.328 +
   1.329 +  /**
   1.330 +   * Load a JSON file from disk in the profile directory.
   1.331 +   *
   1.332 +   * @param filePath
   1.333 +   *        JSON file path load from profile. Loaded file will be
   1.334 +   *        <profile>/<filePath>.json. i.e. Do not specify the ".json"
   1.335 +   *        extension.
   1.336 +   * @param that
   1.337 +   *        Object to use for logging and "this" for callback.
   1.338 +   * @param callback
   1.339 +   *        Function to process json object as its first argument. If the file
   1.340 +   *        could not be loaded, the first argument will be undefined.
   1.341 +   */
   1.342 +  jsonLoad: Task.async(function*(filePath, that, callback) {
   1.343 +    let path = OS.Path.join(OS.Constants.Path.profileDir, "weave", filePath + ".json");
   1.344 +
   1.345 +    if (that._log) {
   1.346 +      that._log.trace("Loading json from disk: " + filePath);
   1.347 +    }
   1.348 +
   1.349 +    let json;
   1.350 +
   1.351 +    try {
   1.352 +      json = yield CommonUtils.readJSON(path);
   1.353 +    } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) {
   1.354 +      // Ignore non-existent files.
   1.355 +    } catch (e) {
   1.356 +      if (that._log) {
   1.357 +        that._log.debug("Failed to load json: " +
   1.358 +                        CommonUtils.exceptionStr(e));
   1.359 +      }
   1.360 +    }
   1.361 +
   1.362 +    callback.call(that, json);
   1.363 +  }),
   1.364 +
   1.365 +  /**
   1.366 +   * Save a json-able object to disk in the profile directory.
   1.367 +   *
   1.368 +   * @param filePath
   1.369 +   *        JSON file path save to <filePath>.json
   1.370 +   * @param that
   1.371 +   *        Object to use for logging and "this" for callback
   1.372 +   * @param obj
   1.373 +   *        Function to provide json-able object to save. If this isn't a
   1.374 +   *        function, it'll be used as the object to make a json string.
   1.375 +   * @param callback
   1.376 +   *        Function called when the write has been performed. Optional.
   1.377 +   *        The first argument will be a Components.results error
   1.378 +   *        constant on error or null if no error was encountered (and
   1.379 +   *        the file saved successfully).
   1.380 +   */
   1.381 +  jsonSave: Task.async(function*(filePath, that, obj, callback) {
   1.382 +    let path = OS.Path.join(OS.Constants.Path.profileDir, "weave",
   1.383 +                            ...(filePath + ".json").split("/"));
   1.384 +    let dir = OS.Path.dirname(path);
   1.385 +    let error = null;
   1.386 +
   1.387 +    try {
   1.388 +      yield OS.File.makeDir(dir, { from: OS.Constants.Path.profileDir });
   1.389 +
   1.390 +      if (that._log) {
   1.391 +        that._log.trace("Saving json to disk: " + path);
   1.392 +      }
   1.393 +
   1.394 +      let json = typeof obj == "function" ? obj.call(that) : obj;
   1.395 +
   1.396 +      yield CommonUtils.writeJSON(json, path);
   1.397 +    } catch (e) {
   1.398 +      error = e
   1.399 +    }
   1.400 +
   1.401 +    if (typeof callback == "function") {
   1.402 +      callback.call(that, error);
   1.403 +    }
   1.404 +  }),
   1.405 +
   1.406 +  getErrorString: function Utils_getErrorString(error, args) {
   1.407 +    try {
   1.408 +      return Str.errors.get(error, args || null);
   1.409 +    } catch (e) {}
   1.410 +
   1.411 +    // basically returns "Unknown Error"
   1.412 +    return Str.errors.get("error.reason.unknown");
   1.413 +  },
   1.414 +
   1.415 +  /**
   1.416 +   * Generate 26 characters.
   1.417 +   */
   1.418 +  generatePassphrase: function generatePassphrase() {
   1.419 +    // Note that this is a different base32 alphabet to the one we use for
   1.420 +    // other tasks. It's lowercase, uses different letters, and needs to be
   1.421 +    // decoded with decodeKeyBase32, not just decodeBase32.
   1.422 +    return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16));
   1.423 +  },
   1.424 +
   1.425 +  /**
   1.426 +   * The following are the methods supported for UI use:
   1.427 +   *
   1.428 +   * * isPassphrase:
   1.429 +   *     determines whether a string is either a normalized or presentable
   1.430 +   *     passphrase.
   1.431 +   * * hyphenatePassphrase:
   1.432 +   *     present a normalized passphrase for display. This might actually
   1.433 +   *     perform work beyond just hyphenation; sorry.
   1.434 +   * * hyphenatePartialPassphrase:
   1.435 +   *     present a fragment of a normalized passphrase for display.
   1.436 +   * * normalizePassphrase:
   1.437 +   *     take a presentable passphrase and reduce it to a normalized
   1.438 +   *     representation for storage. normalizePassphrase can safely be called
   1.439 +   *     on normalized input.
   1.440 +   * * normalizeAccount:
   1.441 +   *     take user input for account/username, cleaning up appropriately.
   1.442 +   */
   1.443 +
   1.444 +  isPassphrase: function(s) {
   1.445 +    if (s) {
   1.446 +      return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(Utils.normalizePassphrase(s));
   1.447 +    }
   1.448 +    return false;
   1.449 +  },
   1.450 +
   1.451 +  /**
   1.452 +   * Hyphenate a passphrase (26 characters) into groups.
   1.453 +   * abbbbccccddddeeeeffffggggh
   1.454 +   * =>
   1.455 +   * a-bbbbc-cccdd-ddeee-effff-ggggh
   1.456 +   */
   1.457 +  hyphenatePassphrase: function hyphenatePassphrase(passphrase) {
   1.458 +    // For now, these are the same.
   1.459 +    return Utils.hyphenatePartialPassphrase(passphrase, true);
   1.460 +  },
   1.461 +
   1.462 +  hyphenatePartialPassphrase: function hyphenatePartialPassphrase(passphrase, omitTrailingDash) {
   1.463 +    if (!passphrase)
   1.464 +      return null;
   1.465 +
   1.466 +    // Get the raw data input. Just base32.
   1.467 +    let data = passphrase.toLowerCase().replace(/[^abcdefghijkmnpqrstuvwxyz23456789]/g, "");
   1.468 +
   1.469 +    // This is the neatest way to do this.
   1.470 +    if ((data.length == 1) && !omitTrailingDash)
   1.471 +      return data + "-";
   1.472 +
   1.473 +    // Hyphenate it.
   1.474 +    let y = data.substr(0,1);
   1.475 +    let z = data.substr(1).replace(/(.{1,5})/g, "-$1");
   1.476 +
   1.477 +    // Correct length? We're done.
   1.478 +    if ((z.length == 30) || omitTrailingDash)
   1.479 +      return y + z;
   1.480 +
   1.481 +    // Add a trailing dash if appropriate.
   1.482 +    return (y + z.replace(/([^-]{5})$/, "$1-")).substr(0, SYNC_KEY_HYPHENATED_LENGTH);
   1.483 +  },
   1.484 +
   1.485 +  normalizePassphrase: function normalizePassphrase(pp) {
   1.486 +    // Short var name... have you seen the lines below?!
   1.487 +    // Allow leading and trailing whitespace.
   1.488 +    pp = pp.trim().toLowerCase();
   1.489 +
   1.490 +    // 20-char sync key.
   1.491 +    if (pp.length == 23 &&
   1.492 +        [5, 11, 17].every(function(i) pp[i] == '-')) {
   1.493 +
   1.494 +      return pp.slice(0, 5) + pp.slice(6, 11)
   1.495 +             + pp.slice(12, 17) + pp.slice(18, 23);
   1.496 +    }
   1.497 +
   1.498 +    // "Modern" 26-char key.
   1.499 +    if (pp.length == 31 &&
   1.500 +        [1, 7, 13, 19, 25].every(function(i) pp[i] == '-')) {
   1.501 +
   1.502 +      return pp.slice(0, 1) + pp.slice(2, 7)
   1.503 +             + pp.slice(8, 13) + pp.slice(14, 19)
   1.504 +             + pp.slice(20, 25) + pp.slice(26, 31);
   1.505 +    }
   1.506 +
   1.507 +    // Something else -- just return.
   1.508 +    return pp;
   1.509 +  },
   1.510 +
   1.511 +  normalizeAccount: function normalizeAccount(acc) {
   1.512 +    return acc.trim();
   1.513 +  },
   1.514 +
   1.515 +  /**
   1.516 +   * Create an array like the first but without elements of the second. Reuse
   1.517 +   * arrays if possible.
   1.518 +   */
   1.519 +  arraySub: function arraySub(minuend, subtrahend) {
   1.520 +    if (!minuend.length || !subtrahend.length)
   1.521 +      return minuend;
   1.522 +    return minuend.filter(function(i) subtrahend.indexOf(i) == -1);
   1.523 +  },
   1.524 +
   1.525 +  /**
   1.526 +   * Build the union of two arrays. Reuse arrays if possible.
   1.527 +   */
   1.528 +  arrayUnion: function arrayUnion(foo, bar) {
   1.529 +    if (!foo.length)
   1.530 +      return bar;
   1.531 +    if (!bar.length)
   1.532 +      return foo;
   1.533 +    return foo.concat(Utils.arraySub(bar, foo));
   1.534 +  },
   1.535 +
   1.536 +  bind2: function Async_bind2(object, method) {
   1.537 +    return function innerBind() { return method.apply(object, arguments); };
   1.538 +  },
   1.539 +
   1.540 +  /**
   1.541 +   * Is there a master password configured, regardless of current lock state?
   1.542 +   */
   1.543 +  mpEnabled: function mpEnabled() {
   1.544 +    let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
   1.545 +                    .getService(Ci.nsIPKCS11ModuleDB);
   1.546 +    let sdrSlot = modules.findSlotByName("");
   1.547 +    let status  = sdrSlot.status;
   1.548 +    let slots = Ci.nsIPKCS11Slot;
   1.549 +
   1.550 +    return status != slots.SLOT_UNINITIALIZED && status != slots.SLOT_READY;
   1.551 +  },
   1.552 +
   1.553 +  /**
   1.554 +   * Is there a master password configured and currently locked?
   1.555 +   */
   1.556 +  mpLocked: function mpLocked() {
   1.557 +    let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"]
   1.558 +                    .getService(Ci.nsIPKCS11ModuleDB);
   1.559 +    let sdrSlot = modules.findSlotByName("");
   1.560 +    let status  = sdrSlot.status;
   1.561 +    let slots = Ci.nsIPKCS11Slot;
   1.562 +
   1.563 +    if (status == slots.SLOT_READY || status == slots.SLOT_LOGGED_IN
   1.564 +                                   || status == slots.SLOT_UNINITIALIZED)
   1.565 +      return false;
   1.566 +
   1.567 +    if (status == slots.SLOT_NOT_LOGGED_IN)
   1.568 +      return true;
   1.569 +
   1.570 +    // something wacky happened, pretend MP is locked
   1.571 +    return true;
   1.572 +  },
   1.573 +
   1.574 +  // If Master Password is enabled and locked, present a dialog to unlock it.
   1.575 +  // Return whether the system is unlocked.
   1.576 +  ensureMPUnlocked: function ensureMPUnlocked() {
   1.577 +    if (!Utils.mpLocked()) {
   1.578 +      return true;
   1.579 +    }
   1.580 +    let sdr = Cc["@mozilla.org/security/sdr;1"]
   1.581 +                .getService(Ci.nsISecretDecoderRing);
   1.582 +    try {
   1.583 +      sdr.encryptString("bacon");
   1.584 +      return true;
   1.585 +    } catch(e) {}
   1.586 +    return false;
   1.587 +  },
   1.588 +
   1.589 +  /**
   1.590 +   * Return a value for a backoff interval.  Maximum is eight hours, unless
   1.591 +   * Status.backoffInterval is higher.
   1.592 +   *
   1.593 +   */
   1.594 +  calculateBackoff: function calculateBackoff(attempts, baseInterval,
   1.595 +                                              statusInterval) {
   1.596 +    let backoffInterval = attempts *
   1.597 +                          (Math.floor(Math.random() * baseInterval) +
   1.598 +                           baseInterval);
   1.599 +    return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
   1.600 +                    statusInterval);
   1.601 +  },
   1.602 +
   1.603 +  /**
   1.604 +   * Return a set of hostnames (including the protocol) which may have
   1.605 +   * credentials for sync itself stored in the login manager.
   1.606 +   *
   1.607 +   * In general, these hosts will not have their passwords synced, will be
   1.608 +   * reset when we drop sync credentials, etc.
   1.609 +   */
   1.610 +  getSyncCredentialsHosts: function() {
   1.611 +    // This is somewhat expensive and the result static, so we cache the result.
   1.612 +    if (this._syncCredentialsHosts) {
   1.613 +      return this._syncCredentialsHosts;
   1.614 +    }
   1.615 +    let result = new Set();
   1.616 +    // the legacy sync host.
   1.617 +    result.add(PWDMGR_HOST);
   1.618 +    // The FxA hosts - these almost certainly all have the same hostname, but
   1.619 +    // better safe than sorry...
   1.620 +    for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
   1.621 +                          "identity.fxaccounts.remote.signup.uri",
   1.622 +                          "identity.fxaccounts.remote.signin.uri",
   1.623 +                          "identity.fxaccounts.settings.uri"]) {
   1.624 +      let prefVal;
   1.625 +      try {
   1.626 +        prefVal = Services.prefs.getCharPref(prefName);
   1.627 +      } catch (_) {
   1.628 +        continue;
   1.629 +      }
   1.630 +      let uri = Services.io.newURI(prefVal, null, null);
   1.631 +      result.add(uri.prePath);
   1.632 +    }
   1.633 +    return this._syncCredentialsHosts = result;
   1.634 +  },
   1.635 +};
   1.636 +
   1.637 +XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() {
   1.638 +  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   1.639 +                    .createInstance(Ci.nsIScriptableUnicodeConverter);
   1.640 +  converter.charset = "UTF-8";
   1.641 +  return converter;
   1.642 +});
   1.643 +
   1.644 +/*
   1.645 + * Commonly-used services
   1.646 + */
   1.647 +this.Svc = {};
   1.648 +Svc.Prefs = new Preferences(PREFS_BRANCH);
   1.649 +Svc.DefaultPrefs = new Preferences({branch: PREFS_BRANCH, defaultBranch: true});
   1.650 +Svc.Obs = Observers;
   1.651 +
   1.652 +let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
   1.653 +  "@mozilla.org/suite/sessionstore;1" :
   1.654 +  "@mozilla.org/browser/sessionstore;1";
   1.655 +
   1.656 +[
   1.657 + ["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
   1.658 + ["Session", _sessionCID, "nsISessionStore"]
   1.659 +].forEach(function([name, contract, iface]) {
   1.660 +  XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
   1.661 +});
   1.662 +
   1.663 +XPCOMUtils.defineLazyModuleGetter(Svc, "FormHistory", "resource://gre/modules/FormHistory.jsm");
   1.664 +
   1.665 +Svc.__defineGetter__("Crypto", function() {
   1.666 +  let cryptoSvc;
   1.667 +  let ns = {};
   1.668 +  Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
   1.669 +  cryptoSvc = new ns.WeaveCrypto();
   1.670 +  delete Svc.Crypto;
   1.671 +  return Svc.Crypto = cryptoSvc;
   1.672 +});
   1.673 +
   1.674 +this.Str = {};
   1.675 +["errors", "sync"].forEach(function(lazy) {
   1.676 +  XPCOMUtils.defineLazyGetter(Str, lazy, Utils.lazyStrings(lazy));
   1.677 +});
   1.678 +
   1.679 +Svc.Obs.add("xpcom-shutdown", function () {
   1.680 +  for (let name in Svc)
   1.681 +    delete Svc[name];
   1.682 +});

mercurial