diff --git a/browser/components/loop/MozLoopAPI.jsm b/browser/components/loop/MozLoopAPI.jsm index 738b174425e..408d483462e 100644 --- a/browser/components/loop/MozLoopAPI.jsm +++ b/browser/components/loop/MozLoopAPI.jsm @@ -42,19 +42,6 @@ function injectLoopAPI(targetWindow) { } }, - /** - * Returns the url for the Loop server from preferences. - * - * @return {String} The Loop server url - */ - serverUrl: { - enumerable: true, - configurable: true, - get: function() { - return Services.prefs.getCharPref("loop.server"); - } - }, - /** * Returns the current locale of the browser. * @@ -214,7 +201,40 @@ function injectLoopAPI(targetWindow) { ringer = null; } } - } + }, + + /** + * Performs a hawk based request to the loop server. + * + * Callback parameters: + * - {Object|null} null if success. Otherwise an object: + * { + * code: 401, + * errno: 401, + * error: "Request failed", + * message: "invalid token" + * } + * - {String} The body of the response. + * + * @param {String} path The path to make the request to. + * @param {String} method The request method, e.g. 'POST', 'GET'. + * @param {Object} payloadObj An object which is converted to JSON and + * transmitted with the request. + * @param {Function} callback Called when the request completes. + */ + hawkRequest: { + enumerable: true, + configurable: true, + writable: true, + value: function(path, method, payloadObj, callback) { + // XXX Should really return a DOM promise here. + return MozLoopService.hawkRequest(path, method, payloadObj).then((response) => { + callback(null, response.body); + }, (error) => { + callback(Cu.cloneInto(error, targetWindow)); + }); + } + }, }; let contentObj = Cu.createObjectIn(targetWindow); diff --git a/browser/components/loop/MozLoopService.jsm b/browser/components/loop/MozLoopService.jsm index a5b91b39ad9..68ad0850f57 100644 --- a/browser/components/loop/MozLoopService.jsm +++ b/browser/components/loop/MozLoopService.jsm @@ -147,6 +147,11 @@ let MozLoopServiceInternal = { * @param {String} method The request method, e.g. 'POST', 'GET'. * @param {Object} payloadObj An object which is converted to JSON and * transmitted with the request. + * @returns {Promise} + * Returns a promise that resolves to the response of the API call, + * or is rejected with an error. If the server response can be parsed + * as JSON and contains an 'error' property, the promise will be + * rejected with this JSON-parsed response. */ hawkRequest: function(path, method, payloadObj) { if (!this._hawkClient) { @@ -481,5 +486,22 @@ this.MozLoopService = { "; exception: " + ex); return null; } - } + }, + + /** + * Performs a hawk based request to the loop server. + * + * @param {String} path The path to make the request to. + * @param {String} method The request method, e.g. 'POST', 'GET'. + * @param {Object} payloadObj An object which is converted to JSON and + * transmitted with the request. + * @returns {Promise} + * Returns a promise that resolves to the response of the API call, + * or is rejected with an error. If the server response can be parsed + * as JSON and contains an 'error' property, the promise will be + * rejected with this JSON-parsed response. + */ + hawkRequest: function(path, method, payloadObj) { + return MozLoopServiceInternal.hawkRequest(path, method, payloadObj); + }, }; diff --git a/browser/components/loop/content/conversation.html b/browser/components/loop/content/conversation.html index a5142036c1e..470817c6a2b 100644 --- a/browser/components/loop/content/conversation.html +++ b/browser/components/loop/content/conversation.html @@ -21,14 +21,11 @@ - - - - + diff --git a/browser/components/loop/content/js/client.js b/browser/components/loop/content/js/client.js new file mode 100644 index 00000000000..eb0bb0f65a3 --- /dev/null +++ b/browser/components/loop/content/js/client.js @@ -0,0 +1,189 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* global loop:true, hawk, deriveHawkCredentials */ + +var loop = loop || {}; +loop.Client = (function($) { + "use strict"; + + // The expected properties to be returned from the POST /call-url/ request. + const expectedCallUrlProperties = ["call_url", "expiresAt"]; + + // The expected properties to be returned from the GET /calls request. + const expectedCallProperties = ["calls"]; + + /** + * Loop server client. + * + * @param {Object} settings Settings object. + */ + function Client(settings = {}) { + + // allowing an |in| test rather than a more type || allows us to dependency + // inject a non-existent mozLoop + if ("mozLoop" in settings) { + this.mozLoop = settings.mozLoop; + } else { + this.mozLoop = navigator.mozLoop; + } + + this.settings = settings; + } + + Client.prototype = { + /** + * Converts from hours to seconds + */ + _hoursToSeconds: function(value) { + return value * 60 * 60; + }, + + /** + * Validates a data object to confirm it has the specified properties. + * + * @param {Object} The data object to verify + * @param {Array} The list of properties to verify within the object + * @return This returns either the specific property if only one + * property is specified, or it returns all properties + */ + _validate: function(data, properties) { + if (typeof data !== "object") { + throw new Error("Invalid data received from server"); + } + + properties.forEach(function (property) { + if (!data.hasOwnProperty(property)) { + throw new Error("Invalid data received from server - missing " + + property); + } + }); + + if (properties.length == 1) { + return data[properties[0]]; + } + + return data; + }, + + /** + * Generic handler for XHR failures. + * + * @param {Function} cb Callback(err) + * @param {Object} error See MozLoopAPI.hawkRequest + */ + _failureHandler: function(cb, error) { + var message = "HTTP " + error.code + " " + error.error + "; " + error.message; + console.error(message); + cb(new Error(message)); + }, + + /** + * Ensures the client is registered with the push server. + * + * Callback parameters: + * - err null on successful registration, non-null otherwise. + * + * @param {Function} cb Callback(err) + */ + _ensureRegistered: function(cb) { + this.mozLoop.ensureRegistered(cb); + }, + + /** + * Internal handler for requesting a call url from the server. + * + * Callback parameters: + * - err null on successful registration, non-null otherwise. + * - callUrlData an object of the obtained call url data if successful: + * -- call_url: The url of the call + * -- expiresAt: The amount of hours until expiry of the url + * + * @param {String} simplepushUrl a registered Simple Push URL + * @param {string} nickname the nickname of the future caller + * @param {Function} cb Callback(err, callUrlData) + */ + _requestCallUrlInternal: function(nickname, cb) { + this.mozLoop.hawkRequest("/call-url/", "POST", {callerId: nickname}, + (error, responseText) => { + if (error) { + this._failureHandler(cb, error); + return; + } + + try { + var urlData = JSON.parse(responseText); + cb(null, this._validate(urlData, expectedCallUrlProperties)); + + var expiresHours = this._hoursToSeconds(urlData.expiresAt); + this.mozLoop.noteCallUrlExpiry(expiresHours); + } catch (err) { + console.log("Error requesting call info", err); + cb(err); + } + }); + }, + + /** + * Requests a call URL from the Loop server. It will note the + * expiry time for the url with the mozLoop api. + * + * Callback parameters: + * - err null on successful registration, non-null otherwise. + * - callUrlData an object of the obtained call url data if successful: + * -- call_url: The url of the call + * -- expiresAt: The amount of hours until expiry of the url + * + * @param {String} simplepushUrl a registered Simple Push URL + * @param {string} nickname the nickname of the future caller + * @param {Function} cb Callback(err, callUrlData) + */ + requestCallUrl: function(nickname, cb) { + this._ensureRegistered(function(err) { + if (err) { + console.log("Error registering with Loop server, code: " + err); + cb(err); + return; + } + + this._requestCallUrlInternal(nickname, cb); + }.bind(this)); + }, + + /** + * Requests call information from the server for all calls since the + * given version. + * + * @param {String} version the version identifier from the push + * notification + * @param {Function} cb Callback(err, calls) + */ + requestCallsInfo: function(version, cb) { + // XXX It is likely that we'll want to move some of this to whatever + // opens the chat window, but we'll need to decide on this in bug 1002418 + if (!version) { + throw new Error("missing required parameter version"); + } + + this.mozLoop.hawkRequest("/calls?version=" + version, "GET", null, + (error, responseText) => { + if (error) { + this._failureHandler(cb, error); + return; + } + + try { + var callsData = JSON.parse(responseText); + + cb(null, this._validate(callsData, expectedCallProperties)); + } catch (err) { + console.log("Error requesting calls info", err); + cb(err); + } + }); + }, + }; + + return Client; +})(jQuery); diff --git a/browser/components/loop/content/js/conversation.js b/browser/components/loop/content/js/conversation.js index 1341f8d5669..2dedda37d38 100644 --- a/browser/components/loop/content/js/conversation.js +++ b/browser/components/loop/content/js/conversation.js @@ -137,7 +137,7 @@ loop.conversation = (function(OT, mozL10n) { accept: function() { window.navigator.mozLoop.stopAlerting(); this._conversation.initiate({ - baseServerUrl: window.navigator.mozLoop.serverUrl, + client: new loop.Client(), outgoing: false }); }, diff --git a/browser/components/loop/content/js/panel.js b/browser/components/loop/content/js/panel.js index 60456bd8dc5..7a503c60c37 100644 --- a/browser/components/loop/content/js/panel.js +++ b/browser/components/loop/content/js/panel.js @@ -91,9 +91,7 @@ loop.panel = (function(_, mozL10n) { throw new Error("missing required notifier"); } this.notifier = options.notifier; - this.client = new loop.shared.Client({ - baseServerUrl: navigator.mozLoop.serverUrl - }); + this.client = new loop.Client(); }, getNickname: function() { diff --git a/browser/components/loop/content/panel.html b/browser/components/loop/content/panel.html index 448a5d14ce7..c2ed568f20b 100644 --- a/browser/components/loop/content/panel.html +++ b/browser/components/loop/content/panel.html @@ -19,14 +19,11 @@ - - - - + diff --git a/browser/components/loop/content/shared/js/client.js b/browser/components/loop/content/shared/js/client.js deleted file mode 100644 index 9d7db0886e3..00000000000 --- a/browser/components/loop/content/shared/js/client.js +++ /dev/null @@ -1,362 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/* global loop:true, hawk, deriveHawkCredentials */ - -var loop = loop || {}; -loop.shared = loop.shared || {}; -loop.shared.Client = (function($) { - "use strict"; - - /** - * Loop server client. - * - * @param {Object} settings Settings object. - */ - function Client(settings) { - settings = settings || {}; - if (!settings.hasOwnProperty("baseServerUrl") || - !settings.baseServerUrl) { - throw new Error("missing required baseServerUrl"); - } - - // allowing an |in| test rather than a more type || allows us to dependency - // inject a non-existent mozLoop - if ("mozLoop" in settings) { - this.mozLoop = settings.mozLoop; - } else { - this.mozLoop = navigator.mozLoop; - } - - this.settings = settings; - } - - Client.prototype = { - /** - * Converts from hours to seconds - */ - _hoursToSeconds: function(value) { - return value * 60 * 60; - }, - - /** - * Validates a data object to confirm it has the specified properties. - * - * @param {Object} The data object to verify - * @param {Array} The list of properties to verify within the object - * @return This returns either the specific property if only one - * property is specified, or it returns all properties - */ - _validate: function(data, properties) { - if (typeof data !== "object") { - throw new Error("Invalid data received from server"); - } - - properties.forEach(function (property) { - if (!data.hasOwnProperty(property)) { - throw new Error("Invalid data received from server - missing " + - property); - } - }); - - if (properties.length <= 1) { - return data[properties[0]]; - } - - return data; - }, - - /** - * Generic handler for XHR failures. - * - * @param {Function} cb Callback(err) - * @param jqXHR See jQuery docs - * @param textStatus See jQuery docs - * @param errorThrown See jQuery docs - */ - _failureHandler: function(cb, jqXHR, textStatus, errorThrown) { - var error = "Unknown error.", - jsonRes = jqXHR && jqXHR.responseJSON || {}; - // Received error response format: - // { "status": "errors", - // "errors": [{ - // "location": "url", - // "name": "token", - // "description": "invalid token" - // }]} - if (jsonRes.status === "errors" && Array.isArray(jsonRes.errors)) { - error = "Details: " + jsonRes.errors.map(function(err) { - return Object.keys(err).map(function(field) { - return field + ": " + err[field]; - }).join(", "); - }).join("; "); - } - var message = "HTTP " + jqXHR.status + " " + errorThrown + - "; " + error; - console.error(message); - cb(new Error(message)); - }, - - /** - * Ensures the client is registered with the push server. - * - * Callback parameters: - * - err null on successful registration, non-null otherwise. - * - * @param {Function} cb Callback(err) - */ - _ensureRegistered: function(cb) { - navigator.mozLoop.ensureRegistered(function(err) { - cb(err); - }.bind(this)); - }, - - /** - * Ensures that the client picks up the hawk-session-token - * put in preferences by the LoopService registration code, - * derives hawk credentials from them, and saves them in - * this._credentials. - * - * @param {Function} cb Callback(err) - * if err is set to null in the callback, that indicates that the - * credentials have been successfully attached to this object. - * - * @private - * - * @note That as currently written, this is only ever expected to be called - * from browser UI code (ie it relies on mozLoop). - */ - _ensureCredentials: function(cb) { - if (this._credentials) { - cb(null); - return; - } - - var hawkSessionToken = - this.mozLoop.getLoopCharPref("hawk-session-token"); - if (!hawkSessionToken) { - var msg = "loop.hawk-session-token pref not found"; - console.warn(msg); - cb(new Error(msg)); - return; - } - - // XXX do we want to use any of the other hawk params (eg to track clock - // skew, etc)? - var serverDerivedKeyLengthInBytes = 2 * 32; - deriveHawkCredentials(hawkSessionToken, "sessionToken", - serverDerivedKeyLengthInBytes, function (hawkCredentials) { - this._credentials = hawkCredentials; - cb(null); - }.bind(this)); - }, - - /** - * Internal handler for requesting a call url from the server. - * - * Callback parameters: - * - err null on successful registration, non-null otherwise. - * - callUrlData an object of the obtained call url data if successful: - * -- call_url: The url of the call - * -- expiresAt: The amount of hours until expiry of the url - * - * @param {String} simplepushUrl a registered Simple Push URL - * @param {string} nickname the nickname of the future caller - * @param {Function} cb Callback(err, callUrlData) - */ - _requestCallUrlInternal: function(nickname, cb) { - var endpoint = this.settings.baseServerUrl + "/call-url/", - reqData = {callerId: nickname}; - - var req = $.ajax({ - type: "POST", - url: endpoint, - data: reqData, - xhrFields: { - withCredentials: false - }, - crossDomain: true, - beforeSend: function (xhr, settings) { - try { - this._attachAnyServerCreds(xhr, settings); - } catch (ex) { - cb(ex); - return false; - } - return true; - }.bind(this), - success: function(callUrlData) { - // XXX split this out into two functions for better readability - try { - cb(null, this._validate(callUrlData, ["call_url", "expiresAt"])); - - var expiresHours = this._hoursToSeconds(callUrlData.expiresAt); - navigator.mozLoop.noteCallUrlExpiry(expiresHours); - } catch (err) { - console.log("Error requesting call info", err); - cb(err); - } - }.bind(this), - dataType: "json" - }); - - req.fail(this._failureHandler.bind(this, cb)); - }, - - /** - * Requests a call URL from the Loop server. It will note the - * expiry time for the url with the mozLoop api. - * - * Callback parameters: - * - err null on successful registration, non-null otherwise. - * - callUrlData an object of the obtained call url data if successful: - * -- call_url: The url of the call - * -- expiresAt: The amount of hours until expiry of the url - * - * @param {String} simplepushUrl a registered Simple Push URL - * @param {string} nickname the nickname of the future caller - * @param {Function} cb Callback(err, callUrlData) - */ - requestCallUrl: function(nickname, cb) { - this._ensureRegistered(function(err) { - if (err) { - console.log("Error registering with Loop server, code: " + err); - cb(err); - return; - } - this._ensureCredentials(function (err) { - if (err) { - console.log("Error setting up credentials: " + err); - cb(err); - return; - } - this._requestCallUrlInternal(nickname, cb); - }.bind(this)); - }.bind(this)); - }, - - /** - * Requests call information from the server for all calls since the - * given version. - * - * @param {String} version the version identifier from the push - * notification - * @param {Function} cb Callback(err, calls) - */ - requestCallsInfo: function(version, cb) { - this._ensureCredentials(function (err) { - if (err) { - console.log("Error setting up credentials: " + err); - cb(err); - return; - } - this._requestCallsInfoInternal(version, cb); - }.bind(this)); - }, - - _requestCallsInfoInternal: function(version, cb) { - if (!version) { - throw new Error("missing required parameter version"); - } - - var endpoint = this.settings.baseServerUrl + "/calls"; - - // XXX It is likely that we'll want to move some of this to whatever - // opens the chat window, but we'll need to decide that once we make a - // decision on chrome versus content, and know if we're going with - // LoopService or a frameworker. - var req = $.ajax({ - type: "GET", - url: endpoint + "?version=" + version, - xhrFields: { - withCredentials: false - }, - crossDomain: true, - beforeSend: function (xhr, settings) { - try { - this._attachAnyServerCreds(xhr, settings); - } catch (ex) { - cb(ex); - return false; - } - return true; - }.bind(this), - success: function(callsData) { - try { - cb(null, this._validate(callsData, ["calls"])); - } catch (err) { - console.log("Error requesting calls info", err); - cb(err); - } - }.bind(this), - dataType: "json" - }); - - req.fail(this._failureHandler.bind(this, cb)); - }, - - /** - * Posts a call request to the server for a call represented by the - * loopToken. Will return the session data for the call. - * - * @param {String} loopToken The loopToken representing the call - * @param {Function} cb Callback(err, sessionData) - */ - requestCallInfo: function(loopToken, cb) { - if (!loopToken) { - throw new Error("missing required parameter loopToken"); - } - - var req = $.ajax({ - url: this.settings.baseServerUrl + "/calls/" + loopToken, - method: "POST", - contentType: "application/json", - data: JSON.stringify({}), - dataType: "json" - }); - - req.done(function(sessionData) { - try { - cb(null, this._validate(sessionData, [ - "sessionId", "sessionToken", "apiKey" - ])); - } catch (err) { - console.log("Error requesting call info", err); - cb(err); - } - }.bind(this)); - - req.fail(this._failureHandler.bind(this, cb)); - }, - - /** - * If this._credentials is set, adds a hawk Authorization header based - * based on those credentials to the passed-in XHR. - * - * @param xhr request to add any header to - * @param settings settings object passed to jQuery.ajax() - * @private - */ - _attachAnyServerCreds: function(xhr, settings) { - // if the server needs credentials and didn't get them, it will - // return failure for us, so if we don't have any creds, don't try to - // attach them. - if (!this._credentials) { - return; - } - - var header = hawk.client.header(settings.url, settings.type, - { credentials: this._credentials }); - if (header.err) { - throw new Error(header.err); - } - - xhr.setRequestHeader("Authorization", header.field); - - return; - } - }; - - return Client; -})(jQuery); diff --git a/browser/components/loop/content/shared/js/models.js b/browser/components/loop/content/shared/js/models.js index ddc5d0bbd6e..a6ee3e38eaf 100644 --- a/browser/components/loop/content/shared/js/models.js +++ b/browser/components/loop/content/shared/js/models.js @@ -62,9 +62,11 @@ loop.shared.models = (function() { * * Available options: * - * - {String} baseServerUrl The server URL * - {Boolean} outgoing Set to true if this model represents the * outgoing call. + * - {loop.shared.Client} client A client object to request call information + * from. Expects requestCallInfo for outgoing + * calls, requestCallsInfo for incoming calls. * * Triggered events: * @@ -75,10 +77,6 @@ loop.shared.models = (function() { * @param {Object} options Options object */ initiate: function(options) { - var client = new loop.shared.Client({ - baseServerUrl: options.baseServerUrl - }); - function handleResult(err, sessionData) { /*jshint validthis:true */ if (err) { @@ -99,10 +97,10 @@ loop.shared.models = (function() { } if (options.outgoing) { - client.requestCallInfo(this.get("loopToken"), handleResult.bind(this)); + options.client.requestCallInfo(this.get("loopToken"), handleResult.bind(this)); } else { - client.requestCallsInfo(this.get("loopVersion"), + options.client.requestCallsInfo(this.get("loopVersion"), handleResult.bind(this)); } }, diff --git a/browser/components/loop/content/shared/libs/hawk-browser-2.2.1.js b/browser/components/loop/content/shared/libs/hawk-browser-2.2.1.js deleted file mode 100644 index c223f791d8f..00000000000 --- a/browser/components/loop/content/shared/libs/hawk-browser-2.2.1.js +++ /dev/null @@ -1,556 +0,0 @@ -/* - HTTP Hawk Authentication Scheme - Copyright (c) 2012-2014, Eran Hammer - BSD Licensed -*/ - - -// Declare namespace - -var hawk = { - internals: {} -}; - - -hawk.client = { - - // Generate an Authorization header for a given request - - /* - uri: 'http://example.com/resource?a=b' or object generated by hawk.utils.parseUri() - method: HTTP verb (e.g. 'GET', 'POST') - options: { - - // Required - - credentials: { - id: 'dh37fgj492je', - key: 'aoijedoaijsdlaksjdl', - algorithm: 'sha256' // 'sha1', 'sha256' - }, - - // Optional - - ext: 'application-specific', // Application specific data sent via the ext attribute - timestamp: Date.now() / 1000, // A pre-calculated timestamp in seconds - nonce: '2334f34f', // A pre-generated nonce - localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided) - payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided) - contentType: 'application/json', // Payload content-type (ignored if hash provided) - hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash - app: '24s23423f34dx', // Oz application id - dlg: '234sz34tww3sd' // Oz delegated-by application id - } - */ - - header: function (uri, method, options) { - - var result = { - field: '', - artifacts: {} - }; - - // Validate inputs - - if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') || - !method || typeof method !== 'string' || - !options || typeof options !== 'object') { - - result.err = 'Invalid argument type'; - return result; - } - - // Application time - - var timestamp = options.timestamp || hawk.utils.now(options.localtimeOffsetMsec); - - // Validate credentials - - var credentials = options.credentials; - if (!credentials || - !credentials.id || - !credentials.key || - !credentials.algorithm) { - - result.err = 'Invalid credentials object'; - return result; - } - - if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) { - result.err = 'Unknown algorithm'; - return result; - } - - // Parse URI - - if (typeof uri === 'string') { - uri = hawk.utils.parseUri(uri); - } - - // Calculate signature - - var artifacts = { - ts: timestamp, - nonce: options.nonce || hawk.utils.randomString(6), - method: method, - resource: uri.relative, - host: uri.hostname, - port: uri.port, - hash: options.hash, - ext: options.ext, - app: options.app, - dlg: options.dlg - }; - - result.artifacts = artifacts; - - // Calculate payload hash - - if (!artifacts.hash && - (options.payload || options.payload === '')) { - - artifacts.hash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType); - } - - var mac = hawk.crypto.calculateMac('header', credentials, artifacts); - - // Construct header - - var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed - var header = 'Hawk id="' + credentials.id + - '", ts="' + artifacts.ts + - '", nonce="' + artifacts.nonce + - (artifacts.hash ? '", hash="' + artifacts.hash : '') + - (hasExt ? '", ext="' + hawk.utils.escapeHeaderAttribute(artifacts.ext) : '') + - '", mac="' + mac + '"'; - - if (artifacts.app) { - header += ', app="' + artifacts.app + - (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"'; - } - - result.field = header; - - return result; - }, - - - // Validate server response - - /* - request: object created via 'new XMLHttpRequest()' after response received - artifacts: object received from header().artifacts - options: { - payload: optional payload received - required: specifies if a Server-Authorization header is required. Defaults to 'false' - } - */ - - authenticate: function (request, credentials, artifacts, options) { - - options = options || {}; - - var getHeader = function (name) { - - return request.getResponseHeader ? request.getResponseHeader(name) : request.getHeader(name); - }; - - var wwwAuthenticate = getHeader('www-authenticate'); - if (wwwAuthenticate) { - - // Parse HTTP WWW-Authenticate header - - var attributes = hawk.utils.parseAuthorizationHeader(wwwAuthenticate, ['ts', 'tsm', 'error']); - if (!attributes) { - return false; - } - - if (attributes.ts) { - var tsm = hawk.crypto.calculateTsMac(attributes.ts, credentials); - if (tsm !== attributes.tsm) { - return false; - } - - hawk.utils.setNtpOffset(attributes.ts - Math.floor((new Date()).getTime() / 1000)); // Keep offset at 1 second precision - } - } - - // Parse HTTP Server-Authorization header - - var serverAuthorization = getHeader('server-authorization'); - if (!serverAuthorization && - !options.required) { - - return true; - } - - var attributes = hawk.utils.parseAuthorizationHeader(serverAuthorization, ['mac', 'ext', 'hash']); - if (!attributes) { - return false; - } - - var modArtifacts = { - ts: artifacts.ts, - nonce: artifacts.nonce, - method: artifacts.method, - resource: artifacts.resource, - host: artifacts.host, - port: artifacts.port, - hash: attributes.hash, - ext: attributes.ext, - app: artifacts.app, - dlg: artifacts.dlg - }; - - var mac = hawk.crypto.calculateMac('response', credentials, modArtifacts); - if (mac !== attributes.mac) { - return false; - } - - if (!options.payload && - options.payload !== '') { - - return true; - } - - if (!attributes.hash) { - return false; - } - - var calculatedHash = hawk.crypto.calculatePayloadHash(options.payload, credentials.algorithm, getHeader('content-type')); - return (calculatedHash === attributes.hash); - }, - - message: function (host, port, message, options) { - - // Validate inputs - - if (!host || typeof host !== 'string' || - !port || typeof port !== 'number' || - message === null || message === undefined || typeof message !== 'string' || - !options || typeof options !== 'object') { - - return null; - } - - // Application time - - var timestamp = options.timestamp || hawk.utils.now(options.localtimeOffsetMsec); - - // Validate credentials - - var credentials = options.credentials; - if (!credentials || - !credentials.id || - !credentials.key || - !credentials.algorithm) { - - // Invalid credential object - return null; - } - - if (hawk.crypto.algorithms.indexOf(credentials.algorithm) === -1) { - return null; - } - - // Calculate signature - - var artifacts = { - ts: timestamp, - nonce: options.nonce || hawk.utils.randomString(6), - host: host, - port: port, - hash: hawk.crypto.calculatePayloadHash(message, credentials.algorithm) - }; - - // Construct authorization - - var result = { - id: credentials.id, - ts: artifacts.ts, - nonce: artifacts.nonce, - hash: artifacts.hash, - mac: hawk.crypto.calculateMac('message', credentials, artifacts) - }; - - return result; - }, - - authenticateTimestamp: function (message, credentials, updateClock) { // updateClock defaults to true - - var tsm = hawk.crypto.calculateTsMac(message.ts, credentials); - if (tsm !== message.tsm) { - return false; - } - - if (updateClock !== false) { - hawk.utils.setNtpOffset(message.ts - Math.floor((new Date()).getTime() / 1000)); // Keep offset at 1 second precision - } - - return true; - } -}; - - -hawk.crypto = { - - headerVersion: '1', - - algorithms: ['sha1', 'sha256'], - - calculateMac: function (type, credentials, options) { - - var normalized = hawk.crypto.generateNormalizedString(type, options); - - var hmac = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()](normalized, credentials.key); - return hmac.toString(CryptoJS.enc.Base64); - }, - - generateNormalizedString: function (type, options) { - - var normalized = 'hawk.' + hawk.crypto.headerVersion + '.' + type + '\n' + - options.ts + '\n' + - options.nonce + '\n' + - (options.method || '').toUpperCase() + '\n' + - (options.resource || '') + '\n' + - options.host.toLowerCase() + '\n' + - options.port + '\n' + - (options.hash || '') + '\n'; - - if (options.ext) { - normalized += options.ext.replace('\\', '\\\\').replace('\n', '\\n'); - } - - normalized += '\n'; - - if (options.app) { - normalized += options.app + '\n' + - (options.dlg || '') + '\n'; - } - - return normalized; - }, - - calculatePayloadHash: function (payload, algorithm, contentType) { - - var hash = CryptoJS.algo[algorithm.toUpperCase()].create(); - hash.update('hawk.' + hawk.crypto.headerVersion + '.payload\n'); - hash.update(hawk.utils.parseContentType(contentType) + '\n'); - hash.update(payload); - hash.update('\n'); - return hash.finalize().toString(CryptoJS.enc.Base64); - }, - - calculateTsMac: function (ts, credentials) { - - var hash = CryptoJS['Hmac' + credentials.algorithm.toUpperCase()]('hawk.' + hawk.crypto.headerVersion + '.ts\n' + ts + '\n', credentials.key); - return hash.toString(CryptoJS.enc.Base64); - } -}; - - -// localStorage compatible interface - -hawk.internals.LocalStorage = function () { - - this._cache = {}; - this.length = 0; - - this.getItem = function (key) { - - return this._cache.hasOwnProperty(key) ? String(this._cache[key]) : null; - }; - - this.setItem = function (key, value) { - - this._cache[key] = String(value); - this.length = Object.keys(this._cache).length; - }; - - this.removeItem = function (key) { - - delete this._cache[key]; - this.length = Object.keys(this._cache).length; - }; - - this.clear = function () { - - this._cache = {}; - this.length = 0; - }; - - this.key = function (i) { - - return Object.keys(this._cache)[i || 0]; - }; -}; - - -hawk.utils = { - - storage: new hawk.internals.LocalStorage(), - - setStorage: function (storage) { - - var ntpOffset = hawk.utils.storage.getItem('hawk_ntp_offset'); - hawk.utils.storage = storage; - if (ntpOffset) { - hawk.utils.setNtpOffset(ntpOffset); - } - }, - - setNtpOffset: function (offset) { - - try { - hawk.utils.storage.setItem('hawk_ntp_offset', offset); - } - catch (err) { - console.error('[hawk] could not write to storage.'); - console.error(err); - } - }, - - getNtpOffset: function () { - - var offset = hawk.utils.storage.getItem('hawk_ntp_offset'); - if (!offset) { - return 0; - } - - return parseInt(offset, 10); - }, - - now: function (localtimeOffsetMsec) { - - return Math.floor(((new Date()).getTime() + (localtimeOffsetMsec || 0)) / 1000) + hawk.utils.getNtpOffset(); - }, - - escapeHeaderAttribute: function (attribute) { - - return attribute.replace(/\\/g, '\\\\').replace(/\"/g, '\\"'); - }, - - parseContentType: function (header) { - - if (!header) { - return ''; - } - - return header.split(';')[0].replace(/^\s+|\s+$/g, '').toLowerCase(); - }, - - parseAuthorizationHeader: function (header, keys) { - - if (!header) { - return null; - } - - var headerParts = header.match(/^(\w+)(?:\s+(.*))?$/); // Header: scheme[ something] - if (!headerParts) { - return null; - } - - var scheme = headerParts[1]; - if (scheme.toLowerCase() !== 'hawk') { - return null; - } - - var attributesString = headerParts[2]; - if (!attributesString) { - return null; - } - - var attributes = {}; - var verify = attributesString.replace(/(\w+)="([^"\\]*)"\s*(?:,\s*|$)/g, function ($0, $1, $2) { - - // Check valid attribute names - - if (keys.indexOf($1) === -1) { - return; - } - - // Allowed attribute value characters: !#$%&'()*+,-./:;<=>?@[]^_`{|}~ and space, a-z, A-Z, 0-9 - - if ($2.match(/^[ \w\!#\$%&'\(\)\*\+,\-\.\/\:;<\=>\?@\[\]\^`\{\|\}~]+$/) === null) { - return; - } - - // Check for duplicates - - if (attributes.hasOwnProperty($1)) { - return; - } - - attributes[$1] = $2; - return ''; - }); - - if (verify !== '') { - return null; - } - - return attributes; - }, - - randomString: function (size) { - - var randomSource = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; - var len = randomSource.length; - - var result = []; - for (var i = 0; i < size; ++i) { - result[i] = randomSource[Math.floor(Math.random() * len)]; - } - - return result.join(''); - }, - - parseUri: function (input) { - - // Based on: parseURI 1.2.2 - // http://blog.stevenlevithan.com/archives/parseuri - // (c) Steven Levithan - // MIT License - - var keys = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'hostname', 'port', 'resource', 'relative', 'pathname', 'directory', 'file', 'query', 'fragment']; - - var uriRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?(((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?)(?:#(.*))?)/; - var uriByNumber = input.match(uriRegex); - var uri = {}; - - for (var i = 0, il = keys.length; i < il; ++i) { - uri[keys[i]] = uriByNumber[i] || ''; - } - - if (uri.port === '') { - uri.port = (uri.protocol.toLowerCase() === 'http' ? '80' : (uri.protocol.toLowerCase() === 'https' ? '443' : '')); - } - - return uri; - } -}; - - -// $lab:coverage:off$ - -// Based on: Crypto-JS v3.1.2 -// Copyright (c) 2009-2013, Jeff Mott. All rights reserved. -// http://code.google.com/p/crypto-js/ -// http://code.google.com/p/crypto-js/wiki/License - -var CryptoJS = CryptoJS || function (h, r) { var k = {}, l = k.lib = {}, n = function () { }, f = l.Base = { extend: function (a) { n.prototype = this; var b = new n; a && b.mixIn(a); b.hasOwnProperty("init") || (b.init = function () { b.$super.init.apply(this, arguments) }); b.init.prototype = b; b.$super = this; return b }, create: function () { var a = this.extend(); a.init.apply(a, arguments); return a }, init: function () { }, mixIn: function (a) { for (var b in a) a.hasOwnProperty(b) && (this[b] = a[b]); a.hasOwnProperty("toString") && (this.toString = a.toString) }, clone: function () { return this.init.prototype.extend(this) } }, j = l.WordArray = f.extend({ init: function (a, b) { a = this.words = a || []; this.sigBytes = b != r ? b : 4 * a.length }, toString: function (a) { return (a || s).stringify(this) }, concat: function (a) { var b = this.words, d = a.words, c = this.sigBytes; a = a.sigBytes; this.clamp(); if (c % 4) for (var e = 0; e < a; e++) b[c + e >>> 2] |= (d[e >>> 2] >>> 24 - 8 * (e % 4) & 255) << 24 - 8 * ((c + e) % 4); else if (65535 < d.length) for (e = 0; e < a; e += 4) b[c + e >>> 2] = d[e >>> 2]; else b.push.apply(b, d); this.sigBytes += a; return this }, clamp: function () { var a = this.words, b = this.sigBytes; a[b >>> 2] &= 4294967295 << 32 - 8 * (b % 4); a.length = h.ceil(b / 4) }, clone: function () { var a = f.clone.call(this); a.words = this.words.slice(0); return a }, random: function (a) { for (var b = [], d = 0; d < a; d += 4) b.push(4294967296 * h.random() | 0); return new j.init(b, a) } }), m = k.enc = {}, s = m.Hex = { stringify: function (a) { var b = a.words; a = a.sigBytes; for (var d = [], c = 0; c < a; c++) { var e = b[c >>> 2] >>> 24 - 8 * (c % 4) & 255; d.push((e >>> 4).toString(16)); d.push((e & 15).toString(16)) } return d.join("") }, parse: function (a) { for (var b = a.length, d = [], c = 0; c < b; c += 2) d[c >>> 3] |= parseInt(a.substr(c, 2), 16) << 24 - 4 * (c % 8); return new j.init(d, b / 2) } }, p = m.Latin1 = { stringify: function (a) { var b = a.words; a = a.sigBytes; for (var d = [], c = 0; c < a; c++) d.push(String.fromCharCode(b[c >>> 2] >>> 24 - 8 * (c % 4) & 255)); return d.join("") }, parse: function (a) { for (var b = a.length, d = [], c = 0; c < b; c++) d[c >>> 2] |= (a.charCodeAt(c) & 255) << 24 - 8 * (c % 4); return new j.init(d, b) } }, t = m.Utf8 = { stringify: function (a) { try { return decodeURIComponent(escape(p.stringify(a))) } catch (b) { throw Error("Malformed UTF-8 data"); } }, parse: function (a) { return p.parse(unescape(encodeURIComponent(a))) } }, q = l.BufferedBlockAlgorithm = f.extend({ reset: function () { this._data = new j.init; this._nDataBytes = 0 }, _append: function (a) { "string" == typeof a && (a = t.parse(a)); this._data.concat(a); this._nDataBytes += a.sigBytes }, _process: function (a) { var b = this._data, d = b.words, c = b.sigBytes, e = this.blockSize, f = c / (4 * e), f = a ? h.ceil(f) : h.max((f | 0) - this._minBufferSize, 0); a = f * e; c = h.min(4 * a, c); if (a) { for (var g = 0; g < a; g += e) this._doProcessBlock(d, g); g = d.splice(0, a); b.sigBytes -= c } return new j.init(g, c) }, clone: function () { var a = f.clone.call(this); a._data = this._data.clone(); return a }, _minBufferSize: 0 }); l.Hasher = q.extend({ cfg: f.extend(), init: function (a) { this.cfg = this.cfg.extend(a); this.reset() }, reset: function () { q.reset.call(this); this._doReset() }, update: function (a) { this._append(a); this._process(); return this }, finalize: function (a) { a && this._append(a); return this._doFinalize() }, blockSize: 16, _createHelper: function (a) { return function (b, d) { return (new a.init(d)).finalize(b) } }, _createHmacHelper: function (a) { return function (b, d) { return (new u.HMAC.init(a, d)).finalize(b) } } }); var u = k.algo = {}; return k }(Math); -(function () { var k = CryptoJS, b = k.lib, m = b.WordArray, l = b.Hasher, d = [], b = k.algo.SHA1 = l.extend({ _doReset: function () { this._hash = new m.init([1732584193, 4023233417, 2562383102, 271733878, 3285377520]) }, _doProcessBlock: function (n, p) { for (var a = this._hash.words, e = a[0], f = a[1], h = a[2], j = a[3], b = a[4], c = 0; 80 > c; c++) { if (16 > c) d[c] = n[p + c] | 0; else { var g = d[c - 3] ^ d[c - 8] ^ d[c - 14] ^ d[c - 16]; d[c] = g << 1 | g >>> 31 } g = (e << 5 | e >>> 27) + b + d[c]; g = 20 > c ? g + ((f & h | ~f & j) + 1518500249) : 40 > c ? g + ((f ^ h ^ j) + 1859775393) : 60 > c ? g + ((f & h | f & j | h & j) - 1894007588) : g + ((f ^ h ^ j) - 899497514); b = j; j = h; h = f << 30 | f >>> 2; f = e; e = g } a[0] = a[0] + e | 0; a[1] = a[1] + f | 0; a[2] = a[2] + h | 0; a[3] = a[3] + j | 0; a[4] = a[4] + b | 0 }, _doFinalize: function () { var b = this._data, d = b.words, a = 8 * this._nDataBytes, e = 8 * b.sigBytes; d[e >>> 5] |= 128 << 24 - e % 32; d[(e + 64 >>> 9 << 4) + 14] = Math.floor(a / 4294967296); d[(e + 64 >>> 9 << 4) + 15] = a; b.sigBytes = 4 * d.length; this._process(); return this._hash }, clone: function () { var b = l.clone.call(this); b._hash = this._hash.clone(); return b } }); k.SHA1 = l._createHelper(b); k.HmacSHA1 = l._createHmacHelper(b) })(); -(function (k) { for (var g = CryptoJS, h = g.lib, v = h.WordArray, j = h.Hasher, h = g.algo, s = [], t = [], u = function (q) { return 4294967296 * (q - (q | 0)) | 0 }, l = 2, b = 0; 64 > b;) { var d; a: { d = l; for (var w = k.sqrt(d), r = 2; r <= w; r++) if (!(d % r)) { d = !1; break a } d = !0 } d && (8 > b && (s[b] = u(k.pow(l, 0.5))), t[b] = u(k.pow(l, 1 / 3)), b++); l++ } var n = [], h = h.SHA256 = j.extend({ _doReset: function () { this._hash = new v.init(s.slice(0)) }, _doProcessBlock: function (q, h) { for (var a = this._hash.words, c = a[0], d = a[1], b = a[2], k = a[3], f = a[4], g = a[5], j = a[6], l = a[7], e = 0; 64 > e; e++) { if (16 > e) n[e] = q[h + e] | 0; else { var m = n[e - 15], p = n[e - 2]; n[e] = ((m << 25 | m >>> 7) ^ (m << 14 | m >>> 18) ^ m >>> 3) + n[e - 7] + ((p << 15 | p >>> 17) ^ (p << 13 | p >>> 19) ^ p >>> 10) + n[e - 16] } m = l + ((f << 26 | f >>> 6) ^ (f << 21 | f >>> 11) ^ (f << 7 | f >>> 25)) + (f & g ^ ~f & j) + t[e] + n[e]; p = ((c << 30 | c >>> 2) ^ (c << 19 | c >>> 13) ^ (c << 10 | c >>> 22)) + (c & d ^ c & b ^ d & b); l = j; j = g; g = f; f = k + m | 0; k = b; b = d; d = c; c = m + p | 0 } a[0] = a[0] + c | 0; a[1] = a[1] + d | 0; a[2] = a[2] + b | 0; a[3] = a[3] + k | 0; a[4] = a[4] + f | 0; a[5] = a[5] + g | 0; a[6] = a[6] + j | 0; a[7] = a[7] + l | 0 }, _doFinalize: function () { var d = this._data, b = d.words, a = 8 * this._nDataBytes, c = 8 * d.sigBytes; b[c >>> 5] |= 128 << 24 - c % 32; b[(c + 64 >>> 9 << 4) + 14] = k.floor(a / 4294967296); b[(c + 64 >>> 9 << 4) + 15] = a; d.sigBytes = 4 * b.length; this._process(); return this._hash }, clone: function () { var b = j.clone.call(this); b._hash = this._hash.clone(); return b } }); g.SHA256 = j._createHelper(h); g.HmacSHA256 = j._createHmacHelper(h) })(Math); -(function () { var c = CryptoJS, k = c.enc.Utf8; c.algo.HMAC = c.lib.Base.extend({ init: function (a, b) { a = this._hasher = new a.init; "string" == typeof b && (b = k.parse(b)); var c = a.blockSize, e = 4 * c; b.sigBytes > e && (b = a.finalize(b)); b.clamp(); for (var f = this._oKey = b.clone(), g = this._iKey = b.clone(), h = f.words, j = g.words, d = 0; d < c; d++) h[d] ^= 1549556828, j[d] ^= 909522486; f.sigBytes = g.sigBytes = e; this.reset() }, reset: function () { var a = this._hasher; a.reset(); a.update(this._iKey) }, update: function (a) { this._hasher.update(a); return this }, finalize: function (a) { var b = this._hasher; a = b.finalize(a); b.reset(); return b.finalize(this._oKey.clone().concat(a)) } }) })(); -(function () { var h = CryptoJS, j = h.lib.WordArray; h.enc.Base64 = { stringify: function (b) { var e = b.words, f = b.sigBytes, c = this._map; b.clamp(); b = []; for (var a = 0; a < f; a += 3) for (var d = (e[a >>> 2] >>> 24 - 8 * (a % 4) & 255) << 16 | (e[a + 1 >>> 2] >>> 24 - 8 * ((a + 1) % 4) & 255) << 8 | e[a + 2 >>> 2] >>> 24 - 8 * ((a + 2) % 4) & 255, g = 0; 4 > g && a + 0.75 * g < f; g++) b.push(c.charAt(d >>> 6 * (3 - g) & 63)); if (e = c.charAt(64)) for (; b.length % 4;) b.push(e); return b.join("") }, parse: function (b) { var e = b.length, f = this._map, c = f.charAt(64); c && (c = b.indexOf(c), -1 != c && (e = c)); for (var c = [], a = 0, d = 0; d < e; d++) if (d % 4) { var g = f.indexOf(b.charAt(d - 1)) << 2 * (d % 4), h = f.indexOf(b.charAt(d)) >>> 6 - 2 * (d % 4); c[a >>> 2] |= (g | h) << 24 - 8 * (a % 4); a++ } return j.create(c, a) }, _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=" } })(); - -hawk.crypto.internals = CryptoJS; - - -// Export if used as a module - -if (typeof module !== 'undefined' && module.exports) { - module.exports = hawk; -} - -// $lab:coverage:on$ diff --git a/browser/components/loop/content/shared/libs/sjcl-dev20140604.js b/browser/components/loop/content/shared/libs/sjcl-dev20140604.js deleted file mode 100644 index 62847e6057d..00000000000 --- a/browser/components/loop/content/shared/libs/sjcl-dev20140604.js +++ /dev/null @@ -1,606 +0,0 @@ -/** @fileOverview Javascript cryptography implementation. - * - * Crush to remove comments, shorten variable names and - * generally reduce transmission size. - * - * @author Emily Stark - * @author Mike Hamburg - * @author Dan Boneh - */ - -"use strict"; -/*jslint indent: 2, bitwise: false, nomen: false, plusplus: false, white: false, regexp: false */ -/*global document, window, escape, unescape, module, require, Uint32Array */ - -/** @namespace The Stanford Javascript Crypto Library, top-level namespace. */ -var sjcl = { - /** @namespace Symmetric ciphers. */ - cipher: {}, - - /** @namespace Hash functions. Right now only SHA256 is implemented. */ - hash: {}, - - /** @namespace Key exchange functions. Right now only SRP is implemented. */ - keyexchange: {}, - - /** @namespace Block cipher modes of operation. */ - mode: {}, - - /** @namespace Miscellaneous. HMAC and PBKDF2. */ - misc: {}, - - /** - * @namespace Bit array encoders and decoders. - * - * @description - * The members of this namespace are functions which translate between - * SJCL's bitArrays and other objects (usually strings). Because it - * isn't always clear which direction is encoding and which is decoding, - * the method names are "fromBits" and "toBits". - */ - codec: {}, - - /** @namespace Exceptions. */ - exception: { - /** @constructor Ciphertext is corrupt. */ - corrupt: function(message) { - this.toString = function() { return "CORRUPT: "+this.message; }; - this.message = message; - }, - - /** @constructor Invalid parameter. */ - invalid: function(message) { - this.toString = function() { return "INVALID: "+this.message; }; - this.message = message; - }, - - /** @constructor Bug or missing feature in SJCL. @constructor */ - bug: function(message) { - this.toString = function() { return "BUG: "+this.message; }; - this.message = message; - }, - - /** @constructor Something isn't ready. */ - notReady: function(message) { - this.toString = function() { return "NOT READY: "+this.message; }; - this.message = message; - } - } -}; - -if(typeof module !== 'undefined' && module.exports){ - module.exports = sjcl; -} -/** @fileOverview Arrays of bits, encoded as arrays of Numbers. - * - * @author Emily Stark - * @author Mike Hamburg - * @author Dan Boneh - */ - -/** @namespace Arrays of bits, encoded as arrays of Numbers. - * - * @description - *

- * These objects are the currency accepted by SJCL's crypto functions. - *

- * - *

- * Most of our crypto primitives operate on arrays of 4-byte words internally, - * but many of them can take arguments that are not a multiple of 4 bytes. - * This library encodes arrays of bits (whose size need not be a multiple of 8 - * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an - * array of words, 32 bits at a time. Since the words are double-precision - * floating point numbers, they fit some extra data. We use this (in a private, - * possibly-changing manner) to encode the number of bits actually present - * in the last word of the array. - *

- * - *

- * Because bitwise ops clear this out-of-band data, these arrays can be passed - * to ciphers like AES which want arrays of words. - *

- */ -sjcl.bitArray = { - /** - * Array slices in units of bits. - * @param {bitArray} a The array to slice. - * @param {Number} bstart The offset to the start of the slice, in bits. - * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined, - * slice until the end of the array. - * @return {bitArray} The requested slice. - */ - bitSlice: function (a, bstart, bend) { - a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1); - return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart); - }, - - /** - * Extract a number packed into a bit array. - * @param {bitArray} a The array to slice. - * @param {Number} bstart The offset to the start of the slice, in bits. - * @param {Number} length The length of the number to extract. - * @return {Number} The requested slice. - */ - extract: function(a, bstart, blength) { - // FIXME: this Math.floor is not necessary at all, but for some reason - // seems to suppress a bug in the Chromium JIT. - var x, sh = Math.floor((-bstart-blength) & 31); - if ((bstart + blength - 1 ^ bstart) & -32) { - // it crosses a boundary - x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh); - } else { - // within a single word - x = a[bstart/32|0] >>> sh; - } - return x & ((1< 0 && len) { - a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1); - } - return a; - }, - - /** - * Make a partial word for a bit array. - * @param {Number} len The number of bits in the word. - * @param {Number} x The bits. - * @param {Number} [0] _end Pass 1 if x has already been shifted to the high side. - * @return {Number} The partial word. - */ - partial: function (len, x, _end) { - if (len === 32) { return x; } - return (_end ? x|0 : x << (32-len)) + len * 0x10000000000; - }, - - /** - * Get the number of bits used by a partial word. - * @param {Number} x The partial word. - * @return {Number} The number of bits used by the partial word. - */ - getPartial: function (x) { - return Math.round(x/0x10000000000) || 32; - }, - - /** - * Compare two arrays for equality in a predictable amount of time. - * @param {bitArray} a The first array. - * @param {bitArray} b The second array. - * @return {boolean} true if a == b; false otherwise. - */ - equal: function (a, b) { - if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) { - return false; - } - var x = 0, i; - for (i=0; i= 32; shift -= 32) { - out.push(carry); - carry = 0; - } - if (shift === 0) { - return out.concat(a); - } - - for (i=0; i>>shift); - carry = a[i] << (32-shift); - } - last2 = a.length ? a[a.length-1] : 0; - shift2 = sjcl.bitArray.getPartial(last2); - out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1)); - return out; - }, - - /** xor a block of 4 words together. - * @private - */ - _xor4: function(x,y) { - return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; - } -}; -/** @fileOverview Bit array codec implementations. - * - * @author Emily Stark - * @author Mike Hamburg - * @author Dan Boneh - */ - -/** @namespace UTF-8 strings */ -sjcl.codec.utf8String = { - /** Convert from a bitArray to a UTF-8 string. */ - fromBits: function (arr) { - var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp; - for (i=0; i>> 24); - tmp <<= 8; - } - return decodeURIComponent(escape(out)); - }, - - /** Convert from a UTF-8 string to a bitArray. */ - toBits: function (str) { - str = unescape(encodeURIComponent(str)); - var out = [], i, tmp=0; - for (i=0; i>>7 ^ a>>>18 ^ a>>>3 ^ a<<25 ^ a<<14) + - (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) + - w[i&15] + w[(i+9) & 15]) | 0; - } - - tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) + (h6 ^ h4&(h5^h6)) + k[i]); // | 0; - - // shift register - h7 = h6; h6 = h5; h5 = h4; - h4 = h3 + tmp | 0; - h3 = h2; h2 = h1; h1 = h0; - - h0 = (tmp + ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0; - } - - h[0] = h[0]+h0 | 0; - h[1] = h[1]+h1 | 0; - h[2] = h[2]+h2 | 0; - h[3] = h[3]+h3 | 0; - h[4] = h[4]+h4 | 0; - h[5] = h[5]+h5 | 0; - h[6] = h[6]+h6 | 0; - h[7] = h[7]+h7 | 0; - } -}; - - -/** @fileOverview HMAC implementation. - * - * @author Emily Stark - * @author Mike Hamburg - * @author Dan Boneh - */ - -/** HMAC with the specified hash function. - * @constructor - * @param {bitArray} key the key for HMAC. - * @param {Object} [hash=sjcl.hash.sha256] The hash function to use. - */ -sjcl.misc.hmac = function (key, Hash) { - this._hash = Hash = Hash || sjcl.hash.sha256; - var exKey = [[],[]], i, - bs = Hash.prototype.blockSize / 32; - this._baseHash = [new Hash(), new Hash()]; - - if (key.length > bs) { - key = Hash.hash(key); - } - - for (i=0; i - + - - + + + - - - - + @@ -35,13 +32,11 @@ - - diff --git a/browser/components/loop/test/shared/models_test.js b/browser/components/loop/test/shared/models_test.js index 0877214d025..eb67eb5d226 100644 --- a/browser/components/loop/test/shared/models_test.js +++ b/browser/components/loop/test/shared/models_test.js @@ -52,68 +52,68 @@ describe("loop.shared.models", function() { }); describe("constructed", function() { - var conversation, reqCallInfoStub, reqCallsInfoStub, fakeBaseServerUrl; + var conversation, fakeClient, fakeBaseServerUrl; beforeEach(function() { conversation = new sharedModels.ConversationModel({}, {sdk: fakeSDK}); conversation.set("loopToken", "fakeToken"); fakeBaseServerUrl = "http://fakeBaseServerUrl"; - reqCallInfoStub = sandbox.stub(loop.shared.Client.prototype, - "requestCallInfo"); - reqCallsInfoStub = sandbox.stub(loop.shared.Client.prototype, - "requestCallsInfo"); + fakeClient = { + requestCallInfo: sandbox.stub(), + requestCallsInfo: sandbox.stub() + }; }); describe("#initiate", function() { it("call requestCallInfo on the client for outgoing calls", function() { conversation.initiate({ - baseServerUrl: fakeBaseServerUrl, + client: fakeClient, outgoing: true }); - sinon.assert.calledOnce(reqCallInfoStub); - sinon.assert.calledWith(reqCallInfoStub, "fakeToken"); + sinon.assert.calledOnce(fakeClient.requestCallInfo); + sinon.assert.calledWith(fakeClient.requestCallInfo, "fakeToken"); }); it("should not call requestCallsInfo on the client for outgoing calls", function() { conversation.initiate({ - baseServerUrl: fakeBaseServerUrl, + client: fakeClient, outgoing: true }); - sinon.assert.notCalled(reqCallsInfoStub); + sinon.assert.notCalled(fakeClient.requestCallsInfo); }); it("call requestCallsInfo on the client for incoming calls", function() { conversation.initiate({ - baseServerUrl: fakeBaseServerUrl, + client: fakeClient, outgoing: false }); - sinon.assert.calledOnce(reqCallsInfoStub); - sinon.assert.calledWith(reqCallsInfoStub); + sinon.assert.calledOnce(fakeClient.requestCallsInfo); + sinon.assert.calledWith(fakeClient.requestCallsInfo); }); it("should not call requestCallInfo on the client for incoming calls", function() { conversation.initiate({ - baseServerUrl: fakeBaseServerUrl, + client: fakeClient, outgoing: false }); - sinon.assert.notCalled(reqCallInfoStub); + sinon.assert.notCalled(fakeClient.requestCallInfo); }); it("should update conversation session information from server data", function() { sandbox.stub(conversation, "setReady"); - reqCallInfoStub.callsArgWith(1, null, fakeSessionData); + fakeClient.requestCallInfo.callsArgWith(1, null, fakeSessionData); conversation.initiate({ - baseServerUrl: fakeBaseServerUrl, + client: fakeClient, outgoing: true }); @@ -122,14 +122,14 @@ describe("loop.shared.models", function() { }); it("should trigger a `session:error` on failure", function(done) { - reqCallInfoStub.callsArgWith(1, + fakeClient.requestCallInfo.callsArgWith(1, new Error("failed: HTTP 400 Bad Request; fake")); conversation.on("session:error", function(err) { expect(err.message).to.match(/failed: HTTP 400 Bad Request; fake/); done(); }).initiate({ - baseServerUrl: fakeBaseServerUrl, + client: fakeClient, outgoing: true }); }); diff --git a/browser/components/loop/test/standalone/index.html b/browser/components/loop/test/standalone/index.html index 77cef25fedc..4952f3b4e22 100644 --- a/browser/components/loop/test/standalone/index.html +++ b/browser/components/loop/test/standalone/index.html @@ -33,8 +33,10 @@ + - + +