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