1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/modules/resource.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,665 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +this.EXPORTED_SYMBOLS = [ 1.9 + "AsyncResource", 1.10 + "Resource" 1.11 +]; 1.12 + 1.13 +const Cc = Components.classes; 1.14 +const Ci = Components.interfaces; 1.15 +const Cr = Components.results; 1.16 +const Cu = Components.utils; 1.17 + 1.18 +Cu.import("resource://gre/modules/Preferences.jsm"); 1.19 +Cu.import("resource://services-common/async.js"); 1.20 +Cu.import("resource://gre/modules/Log.jsm"); 1.21 +Cu.import("resource://services-common/observers.js"); 1.22 +Cu.import("resource://services-common/utils.js"); 1.23 +Cu.import("resource://services-sync/constants.js"); 1.24 +Cu.import("resource://services-sync/util.js"); 1.25 + 1.26 +const DEFAULT_LOAD_FLAGS = 1.27 + // Always validate the cache: 1.28 + Ci.nsIRequest.LOAD_BYPASS_CACHE | 1.29 + Ci.nsIRequest.INHIBIT_CACHING | 1.30 + // Don't send user cookies over the wire (Bug 644734). 1.31 + Ci.nsIRequest.LOAD_ANONYMOUS; 1.32 + 1.33 +/* 1.34 + * AsyncResource represents a remote network resource, identified by a URI. 1.35 + * Create an instance like so: 1.36 + * 1.37 + * let resource = new AsyncResource("http://foobar.com/path/to/resource"); 1.38 + * 1.39 + * The 'resource' object has the following methods to issue HTTP requests 1.40 + * of the corresponding HTTP methods: 1.41 + * 1.42 + * get(callback) 1.43 + * put(data, callback) 1.44 + * post(data, callback) 1.45 + * delete(callback) 1.46 + * 1.47 + * 'callback' is a function with the following signature: 1.48 + * 1.49 + * function callback(error, result) {...} 1.50 + * 1.51 + * 'error' will be null on successful requests. Likewise, result will not be 1.52 + * passed (=undefined) when an error occurs. Note that this is independent of 1.53 + * the status of the HTTP response. 1.54 + */ 1.55 +this.AsyncResource = function AsyncResource(uri) { 1.56 + this._log = Log.repository.getLogger(this._logName); 1.57 + this._log.level = 1.58 + Log.Level[Svc.Prefs.get("log.logger.network.resources")]; 1.59 + this.uri = uri; 1.60 + this._headers = {}; 1.61 + this._onComplete = Utils.bind2(this, this._onComplete); 1.62 +} 1.63 +AsyncResource.prototype = { 1.64 + _logName: "Sync.AsyncResource", 1.65 + 1.66 + // ** {{{ AsyncResource.serverTime }}} ** 1.67 + // 1.68 + // Caches the latest server timestamp (X-Weave-Timestamp header). 1.69 + serverTime: null, 1.70 + 1.71 + /** 1.72 + * Callback to be invoked at request time to add authentication details. 1.73 + * 1.74 + * By default, a global authenticator is provided. If this is set, it will 1.75 + * be used instead of the global one. 1.76 + */ 1.77 + authenticator: null, 1.78 + 1.79 + // The string to use as the base User-Agent in Sync requests. 1.80 + // These strings will look something like 1.81 + // 1.82 + // Firefox/4.0 FxSync/1.8.0.20100101.mobile 1.83 + // 1.84 + // or 1.85 + // 1.86 + // Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop 1.87 + // 1.88 + _userAgent: 1.89 + Services.appinfo.name + "/" + Services.appinfo.version + // Product. 1.90 + " FxSync/" + WEAVE_VERSION + "." + // Sync. 1.91 + Services.appinfo.appBuildID + ".", // Build. 1.92 + 1.93 + // Wait 5 minutes before killing a request. 1.94 + ABORT_TIMEOUT: 300000, 1.95 + 1.96 + // ** {{{ AsyncResource.headers }}} ** 1.97 + // 1.98 + // Headers to be included when making a request for the resource. 1.99 + // Note: Header names should be all lower case, there's no explicit 1.100 + // check for duplicates due to case! 1.101 + get headers() { 1.102 + return this._headers; 1.103 + }, 1.104 + set headers(value) { 1.105 + this._headers = value; 1.106 + }, 1.107 + setHeader: function Res_setHeader(header, value) { 1.108 + this._headers[header.toLowerCase()] = value; 1.109 + }, 1.110 + get headerNames() { 1.111 + return Object.keys(this.headers); 1.112 + }, 1.113 + 1.114 + // ** {{{ AsyncResource.uri }}} ** 1.115 + // 1.116 + // URI representing this resource. 1.117 + get uri() { 1.118 + return this._uri; 1.119 + }, 1.120 + set uri(value) { 1.121 + if (typeof value == 'string') 1.122 + this._uri = CommonUtils.makeURI(value); 1.123 + else 1.124 + this._uri = value; 1.125 + }, 1.126 + 1.127 + // ** {{{ AsyncResource.spec }}} ** 1.128 + // 1.129 + // Get the string representation of the URI. 1.130 + get spec() { 1.131 + if (this._uri) 1.132 + return this._uri.spec; 1.133 + return null; 1.134 + }, 1.135 + 1.136 + // ** {{{ AsyncResource.data }}} ** 1.137 + // 1.138 + // Get and set the data encapulated in the resource. 1.139 + _data: null, 1.140 + get data() this._data, 1.141 + set data(value) { 1.142 + this._data = value; 1.143 + }, 1.144 + 1.145 + // ** {{{ AsyncResource._createRequest }}} ** 1.146 + // 1.147 + // This method returns a new IO Channel for requests to be made 1.148 + // through. It is never called directly, only {{{_doRequest}}} uses it 1.149 + // to obtain a request channel. 1.150 + // 1.151 + _createRequest: function Res__createRequest(method) { 1.152 + let channel = Services.io.newChannel(this.spec, null, null) 1.153 + .QueryInterface(Ci.nsIRequest) 1.154 + .QueryInterface(Ci.nsIHttpChannel); 1.155 + 1.156 + channel.loadFlags |= DEFAULT_LOAD_FLAGS; 1.157 + 1.158 + // Setup a callback to handle channel notifications. 1.159 + let listener = new ChannelNotificationListener(this.headerNames); 1.160 + channel.notificationCallbacks = listener; 1.161 + 1.162 + // Compose a UA string fragment from the various available identifiers. 1.163 + if (Svc.Prefs.get("sendVersionInfo", true)) { 1.164 + let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop"); 1.165 + channel.setRequestHeader("user-agent", ua, false); 1.166 + } 1.167 + 1.168 + let headers = this.headers; 1.169 + 1.170 + if (this.authenticator) { 1.171 + let result = this.authenticator(this, method); 1.172 + if (result && result.headers) { 1.173 + for (let [k, v] in Iterator(result.headers)) { 1.174 + headers[k.toLowerCase()] = v; 1.175 + } 1.176 + } 1.177 + } else { 1.178 + this._log.debug("No authenticator found."); 1.179 + } 1.180 + 1.181 + for (let [key, value] in Iterator(headers)) { 1.182 + if (key == 'authorization') 1.183 + this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); 1.184 + else 1.185 + this._log.trace("HTTP Header " + key + ": " + headers[key]); 1.186 + channel.setRequestHeader(key, headers[key], false); 1.187 + } 1.188 + return channel; 1.189 + }, 1.190 + 1.191 + _onProgress: function Res__onProgress(channel) {}, 1.192 + 1.193 + _doRequest: function _doRequest(action, data, callback) { 1.194 + this._log.trace("In _doRequest."); 1.195 + this._callback = callback; 1.196 + let channel = this._createRequest(action); 1.197 + 1.198 + if ("undefined" != typeof(data)) 1.199 + this._data = data; 1.200 + 1.201 + // PUT and POST are treated differently because they have payload data. 1.202 + if ("PUT" == action || "POST" == action) { 1.203 + // Convert non-string bodies into JSON 1.204 + if (this._data.constructor.toString() != String) 1.205 + this._data = JSON.stringify(this._data); 1.206 + 1.207 + this._log.debug(action + " Length: " + this._data.length); 1.208 + this._log.trace(action + " Body: " + this._data); 1.209 + 1.210 + let type = ('content-type' in this._headers) ? 1.211 + this._headers['content-type'] : 'text/plain'; 1.212 + 1.213 + let stream = Cc["@mozilla.org/io/string-input-stream;1"]. 1.214 + createInstance(Ci.nsIStringInputStream); 1.215 + stream.setData(this._data, this._data.length); 1.216 + 1.217 + channel.QueryInterface(Ci.nsIUploadChannel); 1.218 + channel.setUploadStream(stream, type, this._data.length); 1.219 + } 1.220 + 1.221 + // Setup a channel listener so that the actual network operation 1.222 + // is performed asynchronously. 1.223 + let listener = new ChannelListener(this._onComplete, this._onProgress, 1.224 + this._log, this.ABORT_TIMEOUT); 1.225 + channel.requestMethod = action; 1.226 + try { 1.227 + channel.asyncOpen(listener, null); 1.228 + } catch (ex) { 1.229 + // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port. 1.230 + this._log.warn("Caught an error in asyncOpen: " + CommonUtils.exceptionStr(ex)); 1.231 + CommonUtils.nextTick(callback.bind(this, ex)); 1.232 + } 1.233 + }, 1.234 + 1.235 + _onComplete: function _onComplete(error, data, channel) { 1.236 + this._log.trace("In _onComplete. Error is " + error + "."); 1.237 + 1.238 + if (error) { 1.239 + this._callback(error); 1.240 + return; 1.241 + } 1.242 + 1.243 + this._data = data; 1.244 + let action = channel.requestMethod; 1.245 + 1.246 + this._log.trace("Channel: " + channel); 1.247 + this._log.trace("Action: " + action); 1.248 + 1.249 + // Process status and success first. This way a problem with headers 1.250 + // doesn't fail to include accurate status information. 1.251 + let status = 0; 1.252 + let success = false; 1.253 + 1.254 + try { 1.255 + status = channel.responseStatus; 1.256 + success = channel.requestSucceeded; // HTTP status. 1.257 + 1.258 + this._log.trace("Status: " + status); 1.259 + this._log.trace("Success: " + success); 1.260 + 1.261 + // Log the status of the request. 1.262 + let mesg = [action, success ? "success" : "fail", status, 1.263 + channel.URI.spec].join(" "); 1.264 + this._log.debug("mesg: " + mesg); 1.265 + 1.266 + if (mesg.length > 200) 1.267 + mesg = mesg.substr(0, 200) + "…"; 1.268 + this._log.debug(mesg); 1.269 + 1.270 + // Additionally give the full response body when Trace logging. 1.271 + if (this._log.level <= Log.Level.Trace) 1.272 + this._log.trace(action + " body: " + data); 1.273 + 1.274 + } catch(ex) { 1.275 + // Got a response, but an exception occurred during processing. 1.276 + // This shouldn't occur. 1.277 + this._log.warn("Caught unexpected exception " + CommonUtils.exceptionStr(ex) + 1.278 + " in _onComplete."); 1.279 + this._log.debug(CommonUtils.stackTrace(ex)); 1.280 + } 1.281 + 1.282 + // Process headers. They can be empty, or the call can otherwise fail, so 1.283 + // put this in its own try block. 1.284 + let headers = {}; 1.285 + try { 1.286 + this._log.trace("Processing response headers."); 1.287 + 1.288 + // Read out the response headers if available. 1.289 + channel.visitResponseHeaders({ 1.290 + visitHeader: function visitHeader(header, value) { 1.291 + headers[header.toLowerCase()] = value; 1.292 + } 1.293 + }); 1.294 + 1.295 + // This is a server-side safety valve to allow slowing down 1.296 + // clients without hurting performance. 1.297 + if (headers["x-weave-backoff"]) { 1.298 + let backoff = headers["x-weave-backoff"]; 1.299 + this._log.debug("Got X-Weave-Backoff: " + backoff); 1.300 + Observers.notify("weave:service:backoff:interval", 1.301 + parseInt(backoff, 10)); 1.302 + } 1.303 + 1.304 + if (success && headers["x-weave-quota-remaining"]) { 1.305 + Observers.notify("weave:service:quota:remaining", 1.306 + parseInt(headers["x-weave-quota-remaining"], 10)); 1.307 + } 1.308 + } catch (ex) { 1.309 + this._log.debug("Caught exception " + CommonUtils.exceptionStr(ex) + 1.310 + " visiting headers in _onComplete."); 1.311 + this._log.debug(CommonUtils.stackTrace(ex)); 1.312 + } 1.313 + 1.314 + let ret = new String(data); 1.315 + ret.status = status; 1.316 + ret.success = success; 1.317 + ret.headers = headers; 1.318 + 1.319 + // Make a lazy getter to convert the json response into an object. 1.320 + // Note that this can cause a parse error to be thrown far away from the 1.321 + // actual fetch, so be warned! 1.322 + XPCOMUtils.defineLazyGetter(ret, "obj", function() { 1.323 + try { 1.324 + return JSON.parse(ret); 1.325 + } catch (ex) { 1.326 + this._log.warn("Got exception parsing response body: \"" + CommonUtils.exceptionStr(ex)); 1.327 + // Stringify to avoid possibly printing non-printable characters. 1.328 + this._log.debug("Parse fail: Response body starts: \"" + 1.329 + JSON.stringify((ret + "").slice(0, 100)) + 1.330 + "\"."); 1.331 + throw ex; 1.332 + } 1.333 + }.bind(this)); 1.334 + 1.335 + this._callback(null, ret); 1.336 + }, 1.337 + 1.338 + get: function get(callback) { 1.339 + this._doRequest("GET", undefined, callback); 1.340 + }, 1.341 + 1.342 + put: function put(data, callback) { 1.343 + if (typeof data == "function") 1.344 + [data, callback] = [undefined, data]; 1.345 + this._doRequest("PUT", data, callback); 1.346 + }, 1.347 + 1.348 + post: function post(data, callback) { 1.349 + if (typeof data == "function") 1.350 + [data, callback] = [undefined, data]; 1.351 + this._doRequest("POST", data, callback); 1.352 + }, 1.353 + 1.354 + delete: function delete_(callback) { 1.355 + this._doRequest("DELETE", undefined, callback); 1.356 + } 1.357 +}; 1.358 + 1.359 + 1.360 +/* 1.361 + * Represent a remote network resource, identified by a URI, with a 1.362 + * synchronous API. 1.363 + * 1.364 + * 'Resource' is not recommended for new code. Use the asynchronous API of 1.365 + * 'AsyncResource' instead. 1.366 + */ 1.367 +this.Resource = function Resource(uri) { 1.368 + AsyncResource.call(this, uri); 1.369 +} 1.370 +Resource.prototype = { 1.371 + 1.372 + __proto__: AsyncResource.prototype, 1.373 + 1.374 + _logName: "Sync.Resource", 1.375 + 1.376 + // ** {{{ Resource._request }}} ** 1.377 + // 1.378 + // Perform a particular HTTP request on the resource. This method 1.379 + // is never called directly, but is used by the high-level 1.380 + // {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods. 1.381 + _request: function Res__request(action, data) { 1.382 + let cb = Async.makeSyncCallback(); 1.383 + function callback(error, ret) { 1.384 + if (error) 1.385 + cb.throw(error); 1.386 + cb(ret); 1.387 + } 1.388 + 1.389 + // The channel listener might get a failure code 1.390 + try { 1.391 + this._doRequest(action, data, callback); 1.392 + return Async.waitForSyncCallback(cb); 1.393 + } catch(ex) { 1.394 + // Combine the channel stack with this request stack. Need to create 1.395 + // a new error object for that. 1.396 + let error = Error(ex.message); 1.397 + error.result = ex.result; 1.398 + let chanStack = []; 1.399 + if (ex.stack) 1.400 + chanStack = ex.stack.trim().split(/\n/).slice(1); 1.401 + let requestStack = error.stack.split(/\n/).slice(1); 1.402 + 1.403 + // Strip out the args for the last 2 frames because they're usually HUGE! 1.404 + for (let i = 0; i <= 1; i++) 1.405 + requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@"); 1.406 + 1.407 + error.stack = chanStack.concat(requestStack).join("\n"); 1.408 + throw error; 1.409 + } 1.410 + }, 1.411 + 1.412 + // ** {{{ Resource.get }}} ** 1.413 + // 1.414 + // Perform an asynchronous HTTP GET for this resource. 1.415 + get: function Res_get() { 1.416 + return this._request("GET"); 1.417 + }, 1.418 + 1.419 + // ** {{{ Resource.put }}} ** 1.420 + // 1.421 + // Perform a HTTP PUT for this resource. 1.422 + put: function Res_put(data) { 1.423 + return this._request("PUT", data); 1.424 + }, 1.425 + 1.426 + // ** {{{ Resource.post }}} ** 1.427 + // 1.428 + // Perform a HTTP POST for this resource. 1.429 + post: function Res_post(data) { 1.430 + return this._request("POST", data); 1.431 + }, 1.432 + 1.433 + // ** {{{ Resource.delete }}} ** 1.434 + // 1.435 + // Perform a HTTP DELETE for this resource. 1.436 + delete: function Res_delete() { 1.437 + return this._request("DELETE"); 1.438 + } 1.439 +}; 1.440 + 1.441 +// = ChannelListener = 1.442 +// 1.443 +// This object implements the {{{nsIStreamListener}}} interface 1.444 +// and is called as the network operation proceeds. 1.445 +function ChannelListener(onComplete, onProgress, logger, timeout) { 1.446 + this._onComplete = onComplete; 1.447 + this._onProgress = onProgress; 1.448 + this._log = logger; 1.449 + this._timeout = timeout; 1.450 + this.delayAbort(); 1.451 +} 1.452 +ChannelListener.prototype = { 1.453 + 1.454 + onStartRequest: function Channel_onStartRequest(channel) { 1.455 + this._log.trace("onStartRequest called for channel " + channel + "."); 1.456 + 1.457 + try { 1.458 + channel.QueryInterface(Ci.nsIHttpChannel); 1.459 + } catch (ex) { 1.460 + this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); 1.461 + channel.cancel(Cr.NS_BINDING_ABORTED); 1.462 + return; 1.463 + } 1.464 + 1.465 + // Save the latest server timestamp when possible. 1.466 + try { 1.467 + AsyncResource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0; 1.468 + } 1.469 + catch(ex) {} 1.470 + 1.471 + this._log.trace("onStartRequest: " + channel.requestMethod + " " + 1.472 + channel.URI.spec); 1.473 + this._data = ''; 1.474 + this.delayAbort(); 1.475 + }, 1.476 + 1.477 + onStopRequest: function Channel_onStopRequest(channel, context, status) { 1.478 + // Clear the abort timer now that the channel is done. 1.479 + this.abortTimer.clear(); 1.480 + 1.481 + if (!this._onComplete) { 1.482 + this._log.error("Unexpected error: _onComplete not defined in onStopRequest."); 1.483 + this._onProgress = null; 1.484 + return; 1.485 + } 1.486 + 1.487 + try { 1.488 + channel.QueryInterface(Ci.nsIHttpChannel); 1.489 + } catch (ex) { 1.490 + this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); 1.491 + 1.492 + this._onComplete(ex, this._data, channel); 1.493 + this._onComplete = this._onProgress = null; 1.494 + return; 1.495 + } 1.496 + 1.497 + let statusSuccess = Components.isSuccessCode(status); 1.498 + let uri = channel && channel.URI && channel.URI.spec || "<unknown>"; 1.499 + this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " + 1.500 + "isSuccessCode(" + status + ")? " + statusSuccess); 1.501 + 1.502 + if (this._data == '') { 1.503 + this._data = null; 1.504 + } 1.505 + 1.506 + // Pass back the failure code and stop execution. Use Components.Exception() 1.507 + // instead of Error() so the exception is QI-able and can be passed across 1.508 + // XPCOM borders while preserving the status code. 1.509 + if (!statusSuccess) { 1.510 + let message = Components.Exception("", status).name; 1.511 + let error = Components.Exception(message, status); 1.512 + 1.513 + this._onComplete(error, undefined, channel); 1.514 + this._onComplete = this._onProgress = null; 1.515 + return; 1.516 + } 1.517 + 1.518 + this._log.trace("Channel: flags = " + channel.loadFlags + 1.519 + ", URI = " + uri + 1.520 + ", HTTP success? " + channel.requestSucceeded); 1.521 + this._onComplete(null, this._data, channel); 1.522 + this._onComplete = this._onProgress = null; 1.523 + }, 1.524 + 1.525 + onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) { 1.526 + let siStream; 1.527 + try { 1.528 + siStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); 1.529 + siStream.init(stream); 1.530 + } catch (ex) { 1.531 + this._log.warn("Exception creating nsIScriptableInputStream." + CommonUtils.exceptionStr(ex)); 1.532 + this._log.debug("Parameters: " + req.URI.spec + ", " + stream + ", " + off + ", " + count); 1.533 + // Cannot proceed, so rethrow and allow the channel to cancel itself. 1.534 + throw ex; 1.535 + } 1.536 + 1.537 + try { 1.538 + this._data += siStream.read(count); 1.539 + } catch (ex) { 1.540 + this._log.warn("Exception thrown reading " + count + " bytes from " + siStream + "."); 1.541 + throw ex; 1.542 + } 1.543 + 1.544 + try { 1.545 + this._onProgress(); 1.546 + } catch (ex) { 1.547 + this._log.warn("Got exception calling onProgress handler during fetch of " 1.548 + + req.URI.spec); 1.549 + this._log.debug(CommonUtils.exceptionStr(ex)); 1.550 + this._log.trace("Rethrowing; expect a failure code from the HTTP channel."); 1.551 + throw ex; 1.552 + } 1.553 + 1.554 + this.delayAbort(); 1.555 + }, 1.556 + 1.557 + /** 1.558 + * Create or push back the abort timer that kills this request. 1.559 + */ 1.560 + delayAbort: function delayAbort() { 1.561 + try { 1.562 + CommonUtils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer"); 1.563 + } catch (ex) { 1.564 + this._log.warn("Got exception extending abort timer: " + CommonUtils.exceptionStr(ex)); 1.565 + } 1.566 + }, 1.567 + 1.568 + abortRequest: function abortRequest() { 1.569 + // Ignore any callbacks if we happen to get any now 1.570 + this.onStopRequest = function() {}; 1.571 + let error = Components.Exception("Aborting due to channel inactivity.", 1.572 + Cr.NS_ERROR_NET_TIMEOUT); 1.573 + if (!this._onComplete) { 1.574 + this._log.error("Unexpected error: _onComplete not defined in " + 1.575 + "abortRequest."); 1.576 + return; 1.577 + } 1.578 + this._onComplete(error); 1.579 + } 1.580 +}; 1.581 + 1.582 +/** 1.583 + * This class handles channel notification events. 1.584 + * 1.585 + * An instance of this class is bound to each created channel. 1.586 + * 1.587 + * Optionally pass an array of header names. Each header named 1.588 + * in this array will be copied between the channels in the 1.589 + * event of a redirect. 1.590 + */ 1.591 +function ChannelNotificationListener(headersToCopy) { 1.592 + this._headersToCopy = headersToCopy; 1.593 + 1.594 + this._log = Log.repository.getLogger(this._logName); 1.595 + this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")]; 1.596 +} 1.597 +ChannelNotificationListener.prototype = { 1.598 + _logName: "Sync.Resource", 1.599 + 1.600 + getInterface: function(aIID) { 1.601 + return this.QueryInterface(aIID); 1.602 + }, 1.603 + 1.604 + QueryInterface: function(aIID) { 1.605 + if (aIID.equals(Ci.nsIBadCertListener2) || 1.606 + aIID.equals(Ci.nsIInterfaceRequestor) || 1.607 + aIID.equals(Ci.nsISupports) || 1.608 + aIID.equals(Ci.nsIChannelEventSink)) 1.609 + return this; 1.610 + 1.611 + throw Cr.NS_ERROR_NO_INTERFACE; 1.612 + }, 1.613 + 1.614 + notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) { 1.615 + let log = Log.repository.getLogger("Sync.CertListener"); 1.616 + log.warn("Invalid HTTPS certificate encountered!"); 1.617 + 1.618 + // This suppresses the UI warning only. The request is still cancelled. 1.619 + return true; 1.620 + }, 1.621 + 1.622 + asyncOnChannelRedirect: 1.623 + function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) { 1.624 + 1.625 + let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>"; 1.626 + let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>"; 1.627 + this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags); 1.628 + 1.629 + this._log.debug("Ensuring load flags are set."); 1.630 + newChannel.loadFlags |= DEFAULT_LOAD_FLAGS; 1.631 + 1.632 + // For internal redirects, copy the headers that our caller set. 1.633 + try { 1.634 + if ((flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL) && 1.635 + newChannel.URI.equals(oldChannel.URI)) { 1.636 + this._log.debug("Copying headers for safe internal redirect."); 1.637 + 1.638 + // QI the channel so we can set headers on it. 1.639 + try { 1.640 + newChannel.QueryInterface(Ci.nsIHttpChannel); 1.641 + } catch (ex) { 1.642 + this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); 1.643 + throw ex; 1.644 + } 1.645 + 1.646 + for (let header of this._headersToCopy) { 1.647 + let value = oldChannel.getRequestHeader(header); 1.648 + if (value) { 1.649 + let printed = (header == "authorization") ? "****" : value; 1.650 + this._log.debug("Header: " + header + " = " + printed); 1.651 + newChannel.setRequestHeader(header, value, false); 1.652 + } else { 1.653 + this._log.warn("No value for header " + header); 1.654 + } 1.655 + } 1.656 + } 1.657 + } catch (ex) { 1.658 + this._log.error("Error copying headers: " + CommonUtils.exceptionStr(ex)); 1.659 + } 1.660 + 1.661 + // We let all redirects proceed. 1.662 + try { 1.663 + callback.onRedirectVerifyCallback(Cr.NS_OK); 1.664 + } catch (ex) { 1.665 + this._log.error("onRedirectVerifyCallback threw!" + CommonUtils.exceptionStr(ex)); 1.666 + } 1.667 + } 1.668 +};