Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | const Cc = Components.classes; |
michael@0 | 6 | const Ci = Components.interfaces; |
michael@0 | 7 | const Cr = Components.results; |
michael@0 | 8 | const Cu = Components.utils; |
michael@0 | 9 | |
michael@0 | 10 | // COMPLETE_LENGTH and PARTIAL_LENGTH copied from nsUrlClassifierDBService.h, |
michael@0 | 11 | // they correspond to the length, in bytes, of a hash prefix and the total |
michael@0 | 12 | // hash. |
michael@0 | 13 | const COMPLETE_LENGTH = 32; |
michael@0 | 14 | const PARTIAL_LENGTH = 4; |
michael@0 | 15 | |
michael@0 | 16 | // These backoff related constants are taken from v2 of the Google Safe Browsing |
michael@0 | 17 | // API. |
michael@0 | 18 | // BACKOFF_ERRORS: the number of errors incurred until we start to back off. |
michael@0 | 19 | // BACKOFF_INTERVAL: the initial time, in seconds, to wait once we start backing |
michael@0 | 20 | // off. |
michael@0 | 21 | // BACKOFF_MAX: as the backoff time doubles after each failure, this is a |
michael@0 | 22 | // ceiling on the time to wait, in seconds. |
michael@0 | 23 | // BACKOFF_TIME: length of the interval of time, in seconds, during which errors |
michael@0 | 24 | // are taken into account. |
michael@0 | 25 | |
michael@0 | 26 | const BACKOFF_ERRORS = 2; |
michael@0 | 27 | const BACKOFF_INTERVAL = 30 * 60; |
michael@0 | 28 | const BACKOFF_MAX = 8 * 60 * 60; |
michael@0 | 29 | const BACKOFF_TIME = 5 * 60; |
michael@0 | 30 | |
michael@0 | 31 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 32 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 33 | |
michael@0 | 34 | function HashCompleter() { |
michael@0 | 35 | // This is a HashCompleterRequest and is used by multiple calls to |complete| |
michael@0 | 36 | // in succession to avoid unnecessarily creating requests. Once it has been |
michael@0 | 37 | // started, this is set to null again. |
michael@0 | 38 | this._currentRequest = null; |
michael@0 | 39 | |
michael@0 | 40 | // Whether we have been informed of a shutdown by the xpcom-shutdown event. |
michael@0 | 41 | this._shuttingDown = false; |
michael@0 | 42 | |
michael@0 | 43 | // All of these backoff properties are different per completer as the DB |
michael@0 | 44 | // service keeps one completer per table. |
michael@0 | 45 | // |
michael@0 | 46 | // _backoff tells us whether we are "backing off" from making requests. |
michael@0 | 47 | // It is set in |noteServerResponse| and set after a number of failures. |
michael@0 | 48 | this._backoff = false; |
michael@0 | 49 | // _backoffTime tells us how long we should wait (in seconds) before making |
michael@0 | 50 | // another request. |
michael@0 | 51 | this._backoffTime = 0; |
michael@0 | 52 | // _nextRequestTime is the earliest time at which we are allowed to make |
michael@0 | 53 | // another request by the backoff policy. It is measured in milliseconds. |
michael@0 | 54 | this._nextRequestTime = 0; |
michael@0 | 55 | // A list of the times at which a failed request was made, recorded in |
michael@0 | 56 | // |noteServerResponse|. Sorted by oldest to newest and its length is clamped |
michael@0 | 57 | // by BACKOFF_ERRORS. |
michael@0 | 58 | this._errorTimes = []; |
michael@0 | 59 | |
michael@0 | 60 | Services.obs.addObserver(this, "xpcom-shutdown", true); |
michael@0 | 61 | } |
michael@0 | 62 | HashCompleter.prototype = { |
michael@0 | 63 | classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"), |
michael@0 | 64 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIUrlClassifierHashCompleter, |
michael@0 | 65 | Ci.nsIRunnable, |
michael@0 | 66 | Ci.nsIObserver, |
michael@0 | 67 | Ci.nsISupportsWeakReference, |
michael@0 | 68 | Ci.nsISupports]), |
michael@0 | 69 | |
michael@0 | 70 | // This is mainly how the HashCompleter interacts with other components. |
michael@0 | 71 | // Even though it only takes one partial hash and callback, subsequent |
michael@0 | 72 | // calls are made into the same HTTP request by using a thread dispatch. |
michael@0 | 73 | complete: function HC_complete(aPartialHash, aCallback) { |
michael@0 | 74 | if (!this._currentRequest) { |
michael@0 | 75 | this._currentRequest = new HashCompleterRequest(this); |
michael@0 | 76 | |
michael@0 | 77 | // It's possible for getHashUrl to not be set, usually at start-up. |
michael@0 | 78 | if (this._getHashUrl) { |
michael@0 | 79 | Services.tm.currentThread.dispatch(this, Ci.nsIThread.DISPATCH_NORMAL); |
michael@0 | 80 | } |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | this._currentRequest.add(aPartialHash, aCallback); |
michael@0 | 84 | }, |
michael@0 | 85 | |
michael@0 | 86 | // This is called when either the getHashUrl has been set or after a few calls |
michael@0 | 87 | // to |complete|. It starts off the HTTP request by making a |begin| call |
michael@0 | 88 | // to the HashCompleterRequest. |
michael@0 | 89 | run: function HC_run() { |
michael@0 | 90 | if (this._shuttingDown) { |
michael@0 | 91 | this._currentRequest = null; |
michael@0 | 92 | throw Cr.NS_ERROR_NOT_INITIALIZED; |
michael@0 | 93 | } |
michael@0 | 94 | |
michael@0 | 95 | if (!this._currentRequest) { |
michael@0 | 96 | return; |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | if (!this._getHashUrl) { |
michael@0 | 100 | throw Cr.NS_ERROR_NOT_INITIALIZED; |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | let url = this._getHashUrl; |
michael@0 | 104 | |
michael@0 | 105 | let uri = Services.io.newURI(url, null, null); |
michael@0 | 106 | this._currentRequest.setURI(uri); |
michael@0 | 107 | |
michael@0 | 108 | // If |begin| fails, we should get rid of our request. |
michael@0 | 109 | try { |
michael@0 | 110 | this._currentRequest.begin(); |
michael@0 | 111 | } |
michael@0 | 112 | finally { |
michael@0 | 113 | this._currentRequest = null; |
michael@0 | 114 | } |
michael@0 | 115 | }, |
michael@0 | 116 | |
michael@0 | 117 | get gethashUrl() { |
michael@0 | 118 | return this._getHashUrl; |
michael@0 | 119 | }, |
michael@0 | 120 | // Because we hold off on making a request until we have a valid getHashUrl, |
michael@0 | 121 | // we kick off the process here. |
michael@0 | 122 | set gethashUrl(aNewUrl) { |
michael@0 | 123 | this._getHashUrl = aNewUrl; |
michael@0 | 124 | |
michael@0 | 125 | if (this._currentRequest) { |
michael@0 | 126 | Services.tm.currentThread.dispatch(this, Ci.nsIThread.DISPATCH_NORMAL); |
michael@0 | 127 | } |
michael@0 | 128 | }, |
michael@0 | 129 | |
michael@0 | 130 | // This handles all the logic about setting a back off time based on |
michael@0 | 131 | // server responses. It should only be called once in the life time of a |
michael@0 | 132 | // request. |
michael@0 | 133 | // The logic behind backoffs is documented in the Google Safe Browsing API, |
michael@0 | 134 | // the general idea is that a completer should go into backoff mode after |
michael@0 | 135 | // BACKOFF_ERRORS errors in the last BACKOFF_TIME seconds. From there, |
michael@0 | 136 | // we do not make a request for BACKOFF_INTERVAL seconds and for every failed |
michael@0 | 137 | // request after that we double how long to wait, to a maximum of BACKOFF_MAX. |
michael@0 | 138 | // Note that even in the case of a successful response we still keep a history |
michael@0 | 139 | // of past errors. |
michael@0 | 140 | noteServerResponse: function HC_noteServerResponse(aSuccess) { |
michael@0 | 141 | if (aSuccess) { |
michael@0 | 142 | this._backoff = false; |
michael@0 | 143 | this._nextRequestTime = 0; |
michael@0 | 144 | this._backoffTime = 0; |
michael@0 | 145 | return; |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | let now = Date.now(); |
michael@0 | 149 | |
michael@0 | 150 | // We only alter the size of |_errorTimes| here, so we can guarantee that |
michael@0 | 151 | // its length is at most BACKOFF_ERRORS. |
michael@0 | 152 | this._errorTimes.push(now); |
michael@0 | 153 | if (this._errorTimes.length > BACKOFF_ERRORS) { |
michael@0 | 154 | this._errorTimes.shift(); |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | if (this._backoff) { |
michael@0 | 158 | this._backoffTime *= 2; |
michael@0 | 159 | } else if (this._errorTimes.length == BACKOFF_ERRORS && |
michael@0 | 160 | ((now - this._errorTimes[0]) / 1000) <= BACKOFF_TIME) { |
michael@0 | 161 | this._backoff = true; |
michael@0 | 162 | this._backoffTime = BACKOFF_INTERVAL; |
michael@0 | 163 | } |
michael@0 | 164 | |
michael@0 | 165 | if (this._backoff) { |
michael@0 | 166 | this._backoffTime = Math.min(this._backoffTime, BACKOFF_MAX); |
michael@0 | 167 | this._nextRequestTime = now + (this._backoffTime * 1000); |
michael@0 | 168 | } |
michael@0 | 169 | }, |
michael@0 | 170 | |
michael@0 | 171 | // This is not included on the interface but is used to communicate to the |
michael@0 | 172 | // HashCompleterRequest. It returns a time in milliseconds. |
michael@0 | 173 | get nextRequestTime() { |
michael@0 | 174 | return this._nextRequestTime; |
michael@0 | 175 | }, |
michael@0 | 176 | |
michael@0 | 177 | observe: function HC_observe(aSubject, aTopic, aData) { |
michael@0 | 178 | if (aTopic == "xpcom-shutdown") { |
michael@0 | 179 | this._shuttingDown = true; |
michael@0 | 180 | } |
michael@0 | 181 | }, |
michael@0 | 182 | }; |
michael@0 | 183 | |
michael@0 | 184 | function HashCompleterRequest(aCompleter) { |
michael@0 | 185 | // HashCompleter object that created this HashCompleterRequest. |
michael@0 | 186 | this._completer = aCompleter; |
michael@0 | 187 | // The internal set of hashes and callbacks that this request corresponds to. |
michael@0 | 188 | this._requests = []; |
michael@0 | 189 | // URI to query for hash completions. Largely comes from the |
michael@0 | 190 | // browser.safebrowsing.gethashURL pref. |
michael@0 | 191 | this._uri = null; |
michael@0 | 192 | // nsIChannel that the hash completion query is transmitted over. |
michael@0 | 193 | this._channel = null; |
michael@0 | 194 | // Response body of hash completion. Created in onDataAvailable. |
michael@0 | 195 | this._response = ""; |
michael@0 | 196 | // Whether we have been informed of a shutdown by the xpcom-shutdown event. |
michael@0 | 197 | this._shuttingDown = false; |
michael@0 | 198 | } |
michael@0 | 199 | HashCompleterRequest.prototype = { |
michael@0 | 200 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequestObserver, |
michael@0 | 201 | Ci.nsIStreamListener, |
michael@0 | 202 | Ci.nsIObserver, |
michael@0 | 203 | Ci.nsISupports]), |
michael@0 | 204 | |
michael@0 | 205 | // This is called by the HashCompleter to add a hash and callback to the |
michael@0 | 206 | // HashCompleterRequest. It must be called before calling |begin|. |
michael@0 | 207 | add: function HCR_add(aPartialHash, aCallback) { |
michael@0 | 208 | this._requests.push({ |
michael@0 | 209 | partialHash: aPartialHash, |
michael@0 | 210 | callback: aCallback, |
michael@0 | 211 | responses: [], |
michael@0 | 212 | }); |
michael@0 | 213 | }, |
michael@0 | 214 | |
michael@0 | 215 | // This initiates the HTTP request. It can fail due to backoff timings and |
michael@0 | 216 | // will notify all callbacks as necessary. |
michael@0 | 217 | begin: function HCR_begin() { |
michael@0 | 218 | if (Date.now() < this._completer.nextRequestTime) { |
michael@0 | 219 | this.notifyFailure(Cr.NS_ERROR_ABORT); |
michael@0 | 220 | return; |
michael@0 | 221 | } |
michael@0 | 222 | |
michael@0 | 223 | Services.obs.addObserver(this, "xpcom-shutdown", false); |
michael@0 | 224 | |
michael@0 | 225 | try { |
michael@0 | 226 | this.openChannel(); |
michael@0 | 227 | } |
michael@0 | 228 | catch (err) { |
michael@0 | 229 | this.notifyFailure(err); |
michael@0 | 230 | throw err; |
michael@0 | 231 | } |
michael@0 | 232 | }, |
michael@0 | 233 | |
michael@0 | 234 | setURI: function HCR_setURI(aURI) { |
michael@0 | 235 | this._uri = aURI; |
michael@0 | 236 | }, |
michael@0 | 237 | |
michael@0 | 238 | // Creates an nsIChannel for the request and fills the body. |
michael@0 | 239 | openChannel: function HCR_openChannel() { |
michael@0 | 240 | let loadFlags = Ci.nsIChannel.INHIBIT_CACHING | |
michael@0 | 241 | Ci.nsIChannel.LOAD_BYPASS_CACHE; |
michael@0 | 242 | |
michael@0 | 243 | let channel = Services.io.newChannelFromURI(this._uri); |
michael@0 | 244 | channel.loadFlags = loadFlags; |
michael@0 | 245 | |
michael@0 | 246 | this._channel = channel; |
michael@0 | 247 | |
michael@0 | 248 | let body = this.buildRequest(); |
michael@0 | 249 | this.addRequestBody(body); |
michael@0 | 250 | |
michael@0 | 251 | channel.asyncOpen(this, null); |
michael@0 | 252 | }, |
michael@0 | 253 | |
michael@0 | 254 | // Returns a string for the request body based on the contents of |
michael@0 | 255 | // this._requests. |
michael@0 | 256 | buildRequest: function HCR_buildRequest() { |
michael@0 | 257 | // Sometimes duplicate entries are sent to HashCompleter but we do not need |
michael@0 | 258 | // to propagate these to the server. (bug 633644) |
michael@0 | 259 | let prefixes = []; |
michael@0 | 260 | |
michael@0 | 261 | for (let i = 0; i < this._requests.length; i++) { |
michael@0 | 262 | let request = this._requests[i]; |
michael@0 | 263 | if (prefixes.indexOf(request.partialHash) == -1) { |
michael@0 | 264 | prefixes.push(request.partialHash); |
michael@0 | 265 | } |
michael@0 | 266 | } |
michael@0 | 267 | |
michael@0 | 268 | // Randomize the order to obscure the original request from noise |
michael@0 | 269 | // unbiased Fisher-Yates shuffle |
michael@0 | 270 | let i = prefixes.length; |
michael@0 | 271 | while (i--) { |
michael@0 | 272 | let j = Math.floor(Math.random() * (i + 1)); |
michael@0 | 273 | let temp = prefixes[i]; |
michael@0 | 274 | prefixes[i] = prefixes[j]; |
michael@0 | 275 | prefixes[j] = temp; |
michael@0 | 276 | } |
michael@0 | 277 | |
michael@0 | 278 | let body; |
michael@0 | 279 | body = PARTIAL_LENGTH + ":" + (PARTIAL_LENGTH * prefixes.length) + |
michael@0 | 280 | "\n" + prefixes.join(""); |
michael@0 | 281 | |
michael@0 | 282 | return body; |
michael@0 | 283 | }, |
michael@0 | 284 | |
michael@0 | 285 | // Sets the request body of this._channel. |
michael@0 | 286 | addRequestBody: function HCR_addRequestBody(aBody) { |
michael@0 | 287 | let inputStream = Cc["@mozilla.org/io/string-input-stream;1"]. |
michael@0 | 288 | createInstance(Ci.nsIStringInputStream); |
michael@0 | 289 | |
michael@0 | 290 | inputStream.setData(aBody, aBody.length); |
michael@0 | 291 | |
michael@0 | 292 | let uploadChannel = this._channel.QueryInterface(Ci.nsIUploadChannel); |
michael@0 | 293 | uploadChannel.setUploadStream(inputStream, "text/plain", -1); |
michael@0 | 294 | |
michael@0 | 295 | let httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel); |
michael@0 | 296 | httpChannel.requestMethod = "POST"; |
michael@0 | 297 | }, |
michael@0 | 298 | |
michael@0 | 299 | // Parses the response body and eventually adds items to the |responses| array |
michael@0 | 300 | // for elements of |this._requests|. |
michael@0 | 301 | handleResponse: function HCR_handleResponse() { |
michael@0 | 302 | if (this._response == "") { |
michael@0 | 303 | return; |
michael@0 | 304 | } |
michael@0 | 305 | |
michael@0 | 306 | let start = 0; |
michael@0 | 307 | |
michael@0 | 308 | let length = this._response.length; |
michael@0 | 309 | while (start != length) |
michael@0 | 310 | start = this.handleTable(start); |
michael@0 | 311 | }, |
michael@0 | 312 | |
michael@0 | 313 | // This parses a table entry in the response body and calls |handleItem| |
michael@0 | 314 | // for complete hash in the table entry. |
michael@0 | 315 | handleTable: function HCR_handleTable(aStart) { |
michael@0 | 316 | let body = this._response.substring(aStart); |
michael@0 | 317 | |
michael@0 | 318 | // deal with new line indexes as there could be |
michael@0 | 319 | // new line characters in the data parts. |
michael@0 | 320 | let newlineIndex = body.indexOf("\n"); |
michael@0 | 321 | if (newlineIndex == -1) { |
michael@0 | 322 | throw errorWithStack(); |
michael@0 | 323 | } |
michael@0 | 324 | let header = body.substring(0, newlineIndex); |
michael@0 | 325 | let entries = header.split(":"); |
michael@0 | 326 | if (entries.length != 3) { |
michael@0 | 327 | throw errorWithStack(); |
michael@0 | 328 | } |
michael@0 | 329 | |
michael@0 | 330 | let list = entries[0]; |
michael@0 | 331 | let addChunk = parseInt(entries[1]); |
michael@0 | 332 | let dataLength = parseInt(entries[2]); |
michael@0 | 333 | |
michael@0 | 334 | if (dataLength % COMPLETE_LENGTH != 0 || |
michael@0 | 335 | dataLength == 0 || |
michael@0 | 336 | dataLength > body.length - (newlineIndex + 1)) { |
michael@0 | 337 | throw errorWithStack(); |
michael@0 | 338 | } |
michael@0 | 339 | |
michael@0 | 340 | let data = body.substr(newlineIndex + 1, dataLength); |
michael@0 | 341 | for (let i = 0; i < (dataLength / COMPLETE_LENGTH); i++) { |
michael@0 | 342 | this.handleItem(data.substr(i * COMPLETE_LENGTH, COMPLETE_LENGTH), list, |
michael@0 | 343 | addChunk); |
michael@0 | 344 | } |
michael@0 | 345 | |
michael@0 | 346 | return aStart + newlineIndex + 1 + dataLength; |
michael@0 | 347 | }, |
michael@0 | 348 | |
michael@0 | 349 | // This adds a complete hash to any entry in |this._requests| that matches |
michael@0 | 350 | // the hash. |
michael@0 | 351 | handleItem: function HCR_handleItem(aData, aTableName, aChunkId) { |
michael@0 | 352 | for (let i = 0; i < this._requests.length; i++) { |
michael@0 | 353 | let request = this._requests[i]; |
michael@0 | 354 | if (aData.substring(0,4) == request.partialHash) { |
michael@0 | 355 | request.responses.push({ |
michael@0 | 356 | completeHash: aData, |
michael@0 | 357 | tableName: aTableName, |
michael@0 | 358 | chunkId: aChunkId, |
michael@0 | 359 | }); |
michael@0 | 360 | } |
michael@0 | 361 | } |
michael@0 | 362 | }, |
michael@0 | 363 | |
michael@0 | 364 | // notifySuccess and notifyFailure are used to alert the callbacks with |
michael@0 | 365 | // results. notifySuccess makes |completion| and |completionFinished| calls |
michael@0 | 366 | // while notifyFailure only makes a |completionFinished| call with the error |
michael@0 | 367 | // code. |
michael@0 | 368 | notifySuccess: function HCR_notifySuccess() { |
michael@0 | 369 | for (let i = 0; i < this._requests.length; i++) { |
michael@0 | 370 | let request = this._requests[i]; |
michael@0 | 371 | for (let j = 0; j < request.responses.length; j++) { |
michael@0 | 372 | let response = request.responses[j]; |
michael@0 | 373 | request.callback.completion(response.completeHash, response.tableName, |
michael@0 | 374 | response.chunkId); |
michael@0 | 375 | } |
michael@0 | 376 | |
michael@0 | 377 | request.callback.completionFinished(Cr.NS_OK); |
michael@0 | 378 | } |
michael@0 | 379 | }, |
michael@0 | 380 | notifyFailure: function HCR_notifyFailure(aStatus) { |
michael@0 | 381 | for (let i = 0; i < this._requests; i++) { |
michael@0 | 382 | let request = this._requests[i]; |
michael@0 | 383 | request.callback.completionFinished(aStatus); |
michael@0 | 384 | } |
michael@0 | 385 | }, |
michael@0 | 386 | |
michael@0 | 387 | onDataAvailable: function HCR_onDataAvailable(aRequest, aContext, |
michael@0 | 388 | aInputStream, aOffset, aCount) { |
michael@0 | 389 | let sis = Cc["@mozilla.org/scriptableinputstream;1"]. |
michael@0 | 390 | createInstance(Ci.nsIScriptableInputStream); |
michael@0 | 391 | sis.init(aInputStream); |
michael@0 | 392 | this._response += sis.readBytes(aCount); |
michael@0 | 393 | }, |
michael@0 | 394 | |
michael@0 | 395 | onStartRequest: function HCR_onStartRequest(aRequest, aContext) { |
michael@0 | 396 | // At this point no data is available for us and we have no reason to |
michael@0 | 397 | // terminate the connection, so we do nothing until |onStopRequest|. |
michael@0 | 398 | }, |
michael@0 | 399 | |
michael@0 | 400 | onStopRequest: function HCR_onStopRequest(aRequest, aContext, aStatusCode) { |
michael@0 | 401 | Services.obs.removeObserver(this, "xpcom-shutdown"); |
michael@0 | 402 | |
michael@0 | 403 | if (this._shuttingDown) { |
michael@0 | 404 | throw Cr.NS_ERROR_ABORT; |
michael@0 | 405 | } |
michael@0 | 406 | |
michael@0 | 407 | if (Components.isSuccessCode(aStatusCode)) { |
michael@0 | 408 | let channel = aRequest.QueryInterface(Ci.nsIHttpChannel); |
michael@0 | 409 | let success = channel.requestSucceeded; |
michael@0 | 410 | if (!success) { |
michael@0 | 411 | aStatusCode = Cr.NS_ERROR_ABORT; |
michael@0 | 412 | } |
michael@0 | 413 | } |
michael@0 | 414 | |
michael@0 | 415 | let success = Components.isSuccessCode(aStatusCode); |
michael@0 | 416 | this._completer.noteServerResponse(success); |
michael@0 | 417 | |
michael@0 | 418 | if (success) { |
michael@0 | 419 | try { |
michael@0 | 420 | this.handleResponse(); |
michael@0 | 421 | } |
michael@0 | 422 | catch (err) { |
michael@0 | 423 | dump(err.stack); |
michael@0 | 424 | aStatusCode = err.value; |
michael@0 | 425 | success = false; |
michael@0 | 426 | } |
michael@0 | 427 | } |
michael@0 | 428 | |
michael@0 | 429 | if (success) { |
michael@0 | 430 | this.notifySuccess(); |
michael@0 | 431 | } else { |
michael@0 | 432 | this.notifyFailure(aStatusCode); |
michael@0 | 433 | } |
michael@0 | 434 | }, |
michael@0 | 435 | |
michael@0 | 436 | observe: function HCR_observe(aSubject, aTopic, aData) { |
michael@0 | 437 | if (aTopic != "xpcom-shutdown") { |
michael@0 | 438 | return; |
michael@0 | 439 | } |
michael@0 | 440 | |
michael@0 | 441 | this._shuttingDown = true; |
michael@0 | 442 | if (this._channel) { |
michael@0 | 443 | this._channel.cancel(Cr.NS_ERROR_ABORT); |
michael@0 | 444 | } |
michael@0 | 445 | }, |
michael@0 | 446 | }; |
michael@0 | 447 | |
michael@0 | 448 | // Converts a URL safe base64 string to a normal base64 string. Will not change |
michael@0 | 449 | // normal base64 strings. This is modelled after the same function in |
michael@0 | 450 | // nsUrlClassifierUtils.h. |
michael@0 | 451 | function unUrlsafeBase64(aStr) { |
michael@0 | 452 | return !aStr ? "" : aStr.replace(/-/g, "+") |
michael@0 | 453 | .replace(/_/g, "/"); |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | function errorWithStack() { |
michael@0 | 457 | let err = new Error(); |
michael@0 | 458 | err.value = Cr.NS_ERROR_FAILURE; |
michael@0 | 459 | return err; |
michael@0 | 460 | } |
michael@0 | 461 | |
michael@0 | 462 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory([HashCompleter]); |