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