michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "AsyncResource", michael@0: "Resource" michael@0: ]; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Preferences.jsm"); michael@0: Cu.import("resource://services-common/async.js"); michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: Cu.import("resource://services-common/observers.js"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://services-sync/constants.js"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: michael@0: const DEFAULT_LOAD_FLAGS = michael@0: // Always validate the cache: michael@0: Ci.nsIRequest.LOAD_BYPASS_CACHE | michael@0: Ci.nsIRequest.INHIBIT_CACHING | michael@0: // Don't send user cookies over the wire (Bug 644734). michael@0: Ci.nsIRequest.LOAD_ANONYMOUS; michael@0: michael@0: /* michael@0: * AsyncResource represents a remote network resource, identified by a URI. michael@0: * Create an instance like so: michael@0: * michael@0: * let resource = new AsyncResource("http://foobar.com/path/to/resource"); michael@0: * michael@0: * The 'resource' object has the following methods to issue HTTP requests michael@0: * of the corresponding HTTP methods: michael@0: * michael@0: * get(callback) michael@0: * put(data, callback) michael@0: * post(data, callback) michael@0: * delete(callback) michael@0: * michael@0: * 'callback' is a function with the following signature: michael@0: * michael@0: * function callback(error, result) {...} michael@0: * michael@0: * 'error' will be null on successful requests. Likewise, result will not be michael@0: * passed (=undefined) when an error occurs. Note that this is independent of michael@0: * the status of the HTTP response. michael@0: */ michael@0: this.AsyncResource = function AsyncResource(uri) { michael@0: this._log = Log.repository.getLogger(this._logName); michael@0: this._log.level = michael@0: Log.Level[Svc.Prefs.get("log.logger.network.resources")]; michael@0: this.uri = uri; michael@0: this._headers = {}; michael@0: this._onComplete = Utils.bind2(this, this._onComplete); michael@0: } michael@0: AsyncResource.prototype = { michael@0: _logName: "Sync.AsyncResource", michael@0: michael@0: // ** {{{ AsyncResource.serverTime }}} ** michael@0: // michael@0: // Caches the latest server timestamp (X-Weave-Timestamp header). michael@0: serverTime: null, michael@0: michael@0: /** michael@0: * Callback to be invoked at request time to add authentication details. michael@0: * michael@0: * By default, a global authenticator is provided. If this is set, it will michael@0: * be used instead of the global one. michael@0: */ michael@0: authenticator: null, michael@0: michael@0: // The string to use as the base User-Agent in Sync requests. michael@0: // These strings will look something like michael@0: // michael@0: // Firefox/4.0 FxSync/1.8.0.20100101.mobile michael@0: // michael@0: // or michael@0: // michael@0: // Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop michael@0: // michael@0: _userAgent: michael@0: Services.appinfo.name + "/" + Services.appinfo.version + // Product. michael@0: " FxSync/" + WEAVE_VERSION + "." + // Sync. michael@0: Services.appinfo.appBuildID + ".", // Build. michael@0: michael@0: // Wait 5 minutes before killing a request. michael@0: ABORT_TIMEOUT: 300000, michael@0: michael@0: // ** {{{ AsyncResource.headers }}} ** michael@0: // michael@0: // Headers to be included when making a request for the resource. michael@0: // Note: Header names should be all lower case, there's no explicit michael@0: // check for duplicates due to case! michael@0: get headers() { michael@0: return this._headers; michael@0: }, michael@0: set headers(value) { michael@0: this._headers = value; michael@0: }, michael@0: setHeader: function Res_setHeader(header, value) { michael@0: this._headers[header.toLowerCase()] = value; michael@0: }, michael@0: get headerNames() { michael@0: return Object.keys(this.headers); michael@0: }, michael@0: michael@0: // ** {{{ AsyncResource.uri }}} ** michael@0: // michael@0: // URI representing this resource. michael@0: get uri() { michael@0: return this._uri; michael@0: }, michael@0: set uri(value) { michael@0: if (typeof value == 'string') michael@0: this._uri = CommonUtils.makeURI(value); michael@0: else michael@0: this._uri = value; michael@0: }, michael@0: michael@0: // ** {{{ AsyncResource.spec }}} ** michael@0: // michael@0: // Get the string representation of the URI. michael@0: get spec() { michael@0: if (this._uri) michael@0: return this._uri.spec; michael@0: return null; michael@0: }, michael@0: michael@0: // ** {{{ AsyncResource.data }}} ** michael@0: // michael@0: // Get and set the data encapulated in the resource. michael@0: _data: null, michael@0: get data() this._data, michael@0: set data(value) { michael@0: this._data = value; michael@0: }, michael@0: michael@0: // ** {{{ AsyncResource._createRequest }}} ** michael@0: // michael@0: // This method returns a new IO Channel for requests to be made michael@0: // through. It is never called directly, only {{{_doRequest}}} uses it michael@0: // to obtain a request channel. michael@0: // michael@0: _createRequest: function Res__createRequest(method) { michael@0: let channel = Services.io.newChannel(this.spec, null, null) michael@0: .QueryInterface(Ci.nsIRequest) michael@0: .QueryInterface(Ci.nsIHttpChannel); michael@0: michael@0: channel.loadFlags |= DEFAULT_LOAD_FLAGS; michael@0: michael@0: // Setup a callback to handle channel notifications. michael@0: let listener = new ChannelNotificationListener(this.headerNames); michael@0: channel.notificationCallbacks = listener; michael@0: michael@0: // Compose a UA string fragment from the various available identifiers. michael@0: if (Svc.Prefs.get("sendVersionInfo", true)) { michael@0: let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop"); michael@0: channel.setRequestHeader("user-agent", ua, false); michael@0: } michael@0: michael@0: let headers = this.headers; michael@0: michael@0: if (this.authenticator) { michael@0: let result = this.authenticator(this, method); michael@0: if (result && result.headers) { michael@0: for (let [k, v] in Iterator(result.headers)) { michael@0: headers[k.toLowerCase()] = v; michael@0: } michael@0: } michael@0: } else { michael@0: this._log.debug("No authenticator found."); michael@0: } michael@0: michael@0: for (let [key, value] in Iterator(headers)) { michael@0: if (key == 'authorization') michael@0: this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); michael@0: else michael@0: this._log.trace("HTTP Header " + key + ": " + headers[key]); michael@0: channel.setRequestHeader(key, headers[key], false); michael@0: } michael@0: return channel; michael@0: }, michael@0: michael@0: _onProgress: function Res__onProgress(channel) {}, michael@0: michael@0: _doRequest: function _doRequest(action, data, callback) { michael@0: this._log.trace("In _doRequest."); michael@0: this._callback = callback; michael@0: let channel = this._createRequest(action); michael@0: michael@0: if ("undefined" != typeof(data)) michael@0: this._data = data; michael@0: michael@0: // PUT and POST are treated differently because they have payload data. michael@0: if ("PUT" == action || "POST" == action) { michael@0: // Convert non-string bodies into JSON michael@0: if (this._data.constructor.toString() != String) michael@0: this._data = JSON.stringify(this._data); michael@0: michael@0: this._log.debug(action + " Length: " + this._data.length); michael@0: this._log.trace(action + " Body: " + this._data); michael@0: michael@0: let type = ('content-type' in this._headers) ? michael@0: this._headers['content-type'] : 'text/plain'; michael@0: michael@0: let stream = Cc["@mozilla.org/io/string-input-stream;1"]. michael@0: createInstance(Ci.nsIStringInputStream); michael@0: stream.setData(this._data, this._data.length); michael@0: michael@0: channel.QueryInterface(Ci.nsIUploadChannel); michael@0: channel.setUploadStream(stream, type, this._data.length); michael@0: } michael@0: michael@0: // Setup a channel listener so that the actual network operation michael@0: // is performed asynchronously. michael@0: let listener = new ChannelListener(this._onComplete, this._onProgress, michael@0: this._log, this.ABORT_TIMEOUT); michael@0: channel.requestMethod = action; michael@0: try { michael@0: channel.asyncOpen(listener, null); michael@0: } catch (ex) { michael@0: // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port. michael@0: this._log.warn("Caught an error in asyncOpen: " + CommonUtils.exceptionStr(ex)); michael@0: CommonUtils.nextTick(callback.bind(this, ex)); michael@0: } michael@0: }, michael@0: michael@0: _onComplete: function _onComplete(error, data, channel) { michael@0: this._log.trace("In _onComplete. Error is " + error + "."); michael@0: michael@0: if (error) { michael@0: this._callback(error); michael@0: return; michael@0: } michael@0: michael@0: this._data = data; michael@0: let action = channel.requestMethod; michael@0: michael@0: this._log.trace("Channel: " + channel); michael@0: this._log.trace("Action: " + action); michael@0: michael@0: // Process status and success first. This way a problem with headers michael@0: // doesn't fail to include accurate status information. michael@0: let status = 0; michael@0: let success = false; michael@0: michael@0: try { michael@0: status = channel.responseStatus; michael@0: success = channel.requestSucceeded; // HTTP status. michael@0: michael@0: this._log.trace("Status: " + status); michael@0: this._log.trace("Success: " + success); michael@0: michael@0: // Log the status of the request. michael@0: let mesg = [action, success ? "success" : "fail", status, michael@0: channel.URI.spec].join(" "); michael@0: this._log.debug("mesg: " + mesg); michael@0: michael@0: if (mesg.length > 200) michael@0: mesg = mesg.substr(0, 200) + "…"; michael@0: this._log.debug(mesg); michael@0: michael@0: // Additionally give the full response body when Trace logging. michael@0: if (this._log.level <= Log.Level.Trace) michael@0: this._log.trace(action + " body: " + data); michael@0: michael@0: } catch(ex) { michael@0: // Got a response, but an exception occurred during processing. michael@0: // This shouldn't occur. michael@0: this._log.warn("Caught unexpected exception " + CommonUtils.exceptionStr(ex) + michael@0: " in _onComplete."); michael@0: this._log.debug(CommonUtils.stackTrace(ex)); michael@0: } michael@0: michael@0: // Process headers. They can be empty, or the call can otherwise fail, so michael@0: // put this in its own try block. michael@0: let headers = {}; michael@0: try { michael@0: this._log.trace("Processing response headers."); michael@0: michael@0: // Read out the response headers if available. michael@0: channel.visitResponseHeaders({ michael@0: visitHeader: function visitHeader(header, value) { michael@0: headers[header.toLowerCase()] = value; michael@0: } michael@0: }); michael@0: michael@0: // This is a server-side safety valve to allow slowing down michael@0: // clients without hurting performance. michael@0: if (headers["x-weave-backoff"]) { michael@0: let backoff = headers["x-weave-backoff"]; michael@0: this._log.debug("Got X-Weave-Backoff: " + backoff); michael@0: Observers.notify("weave:service:backoff:interval", michael@0: parseInt(backoff, 10)); michael@0: } michael@0: michael@0: if (success && headers["x-weave-quota-remaining"]) { michael@0: Observers.notify("weave:service:quota:remaining", michael@0: parseInt(headers["x-weave-quota-remaining"], 10)); michael@0: } michael@0: } catch (ex) { michael@0: this._log.debug("Caught exception " + CommonUtils.exceptionStr(ex) + michael@0: " visiting headers in _onComplete."); michael@0: this._log.debug(CommonUtils.stackTrace(ex)); michael@0: } michael@0: michael@0: let ret = new String(data); michael@0: ret.status = status; michael@0: ret.success = success; michael@0: ret.headers = headers; michael@0: michael@0: // Make a lazy getter to convert the json response into an object. michael@0: // Note that this can cause a parse error to be thrown far away from the michael@0: // actual fetch, so be warned! michael@0: XPCOMUtils.defineLazyGetter(ret, "obj", function() { michael@0: try { michael@0: return JSON.parse(ret); michael@0: } catch (ex) { michael@0: this._log.warn("Got exception parsing response body: \"" + CommonUtils.exceptionStr(ex)); michael@0: // Stringify to avoid possibly printing non-printable characters. michael@0: this._log.debug("Parse fail: Response body starts: \"" + michael@0: JSON.stringify((ret + "").slice(0, 100)) + michael@0: "\"."); michael@0: throw ex; michael@0: } michael@0: }.bind(this)); michael@0: michael@0: this._callback(null, ret); michael@0: }, michael@0: michael@0: get: function get(callback) { michael@0: this._doRequest("GET", undefined, callback); michael@0: }, michael@0: michael@0: put: function put(data, callback) { michael@0: if (typeof data == "function") michael@0: [data, callback] = [undefined, data]; michael@0: this._doRequest("PUT", data, callback); michael@0: }, michael@0: michael@0: post: function post(data, callback) { michael@0: if (typeof data == "function") michael@0: [data, callback] = [undefined, data]; michael@0: this._doRequest("POST", data, callback); michael@0: }, michael@0: michael@0: delete: function delete_(callback) { michael@0: this._doRequest("DELETE", undefined, callback); michael@0: } michael@0: }; michael@0: michael@0: michael@0: /* michael@0: * Represent a remote network resource, identified by a URI, with a michael@0: * synchronous API. michael@0: * michael@0: * 'Resource' is not recommended for new code. Use the asynchronous API of michael@0: * 'AsyncResource' instead. michael@0: */ michael@0: this.Resource = function Resource(uri) { michael@0: AsyncResource.call(this, uri); michael@0: } michael@0: Resource.prototype = { michael@0: michael@0: __proto__: AsyncResource.prototype, michael@0: michael@0: _logName: "Sync.Resource", michael@0: michael@0: // ** {{{ Resource._request }}} ** michael@0: // michael@0: // Perform a particular HTTP request on the resource. This method michael@0: // is never called directly, but is used by the high-level michael@0: // {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods. michael@0: _request: function Res__request(action, data) { michael@0: let cb = Async.makeSyncCallback(); michael@0: function callback(error, ret) { michael@0: if (error) michael@0: cb.throw(error); michael@0: cb(ret); michael@0: } michael@0: michael@0: // The channel listener might get a failure code michael@0: try { michael@0: this._doRequest(action, data, callback); michael@0: return Async.waitForSyncCallback(cb); michael@0: } catch(ex) { michael@0: // Combine the channel stack with this request stack. Need to create michael@0: // a new error object for that. michael@0: let error = Error(ex.message); michael@0: error.result = ex.result; michael@0: let chanStack = []; michael@0: if (ex.stack) michael@0: chanStack = ex.stack.trim().split(/\n/).slice(1); michael@0: let requestStack = error.stack.split(/\n/).slice(1); michael@0: michael@0: // Strip out the args for the last 2 frames because they're usually HUGE! michael@0: for (let i = 0; i <= 1; i++) michael@0: requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@"); michael@0: michael@0: error.stack = chanStack.concat(requestStack).join("\n"); michael@0: throw error; michael@0: } michael@0: }, michael@0: michael@0: // ** {{{ Resource.get }}} ** michael@0: // michael@0: // Perform an asynchronous HTTP GET for this resource. michael@0: get: function Res_get() { michael@0: return this._request("GET"); michael@0: }, michael@0: michael@0: // ** {{{ Resource.put }}} ** michael@0: // michael@0: // Perform a HTTP PUT for this resource. michael@0: put: function Res_put(data) { michael@0: return this._request("PUT", data); michael@0: }, michael@0: michael@0: // ** {{{ Resource.post }}} ** michael@0: // michael@0: // Perform a HTTP POST for this resource. michael@0: post: function Res_post(data) { michael@0: return this._request("POST", data); michael@0: }, michael@0: michael@0: // ** {{{ Resource.delete }}} ** michael@0: // michael@0: // Perform a HTTP DELETE for this resource. michael@0: delete: function Res_delete() { michael@0: return this._request("DELETE"); michael@0: } michael@0: }; michael@0: michael@0: // = ChannelListener = michael@0: // michael@0: // This object implements the {{{nsIStreamListener}}} interface michael@0: // and is called as the network operation proceeds. michael@0: function ChannelListener(onComplete, onProgress, logger, timeout) { michael@0: this._onComplete = onComplete; michael@0: this._onProgress = onProgress; michael@0: this._log = logger; michael@0: this._timeout = timeout; michael@0: this.delayAbort(); michael@0: } michael@0: ChannelListener.prototype = { michael@0: michael@0: onStartRequest: function Channel_onStartRequest(channel) { michael@0: this._log.trace("onStartRequest called for channel " + channel + "."); michael@0: michael@0: try { michael@0: channel.QueryInterface(Ci.nsIHttpChannel); michael@0: } catch (ex) { michael@0: this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); michael@0: channel.cancel(Cr.NS_BINDING_ABORTED); michael@0: return; michael@0: } michael@0: michael@0: // Save the latest server timestamp when possible. michael@0: try { michael@0: AsyncResource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0; michael@0: } michael@0: catch(ex) {} michael@0: michael@0: this._log.trace("onStartRequest: " + channel.requestMethod + " " + michael@0: channel.URI.spec); michael@0: this._data = ''; michael@0: this.delayAbort(); michael@0: }, michael@0: michael@0: onStopRequest: function Channel_onStopRequest(channel, context, status) { michael@0: // Clear the abort timer now that the channel is done. michael@0: this.abortTimer.clear(); michael@0: michael@0: if (!this._onComplete) { michael@0: this._log.error("Unexpected error: _onComplete not defined in onStopRequest."); michael@0: this._onProgress = null; michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: channel.QueryInterface(Ci.nsIHttpChannel); michael@0: } catch (ex) { michael@0: this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); michael@0: michael@0: this._onComplete(ex, this._data, channel); michael@0: this._onComplete = this._onProgress = null; michael@0: return; michael@0: } michael@0: michael@0: let statusSuccess = Components.isSuccessCode(status); michael@0: let uri = channel && channel.URI && channel.URI.spec || ""; michael@0: this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " + michael@0: "isSuccessCode(" + status + ")? " + statusSuccess); michael@0: michael@0: if (this._data == '') { michael@0: this._data = null; michael@0: } michael@0: michael@0: // Pass back the failure code and stop execution. Use Components.Exception() michael@0: // instead of Error() so the exception is QI-able and can be passed across michael@0: // XPCOM borders while preserving the status code. michael@0: if (!statusSuccess) { michael@0: let message = Components.Exception("", status).name; michael@0: let error = Components.Exception(message, status); michael@0: michael@0: this._onComplete(error, undefined, channel); michael@0: this._onComplete = this._onProgress = null; michael@0: return; michael@0: } michael@0: michael@0: this._log.trace("Channel: flags = " + channel.loadFlags + michael@0: ", URI = " + uri + michael@0: ", HTTP success? " + channel.requestSucceeded); michael@0: this._onComplete(null, this._data, channel); michael@0: this._onComplete = this._onProgress = null; michael@0: }, michael@0: michael@0: onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) { michael@0: let siStream; michael@0: try { michael@0: siStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); michael@0: siStream.init(stream); michael@0: } catch (ex) { michael@0: this._log.warn("Exception creating nsIScriptableInputStream." + CommonUtils.exceptionStr(ex)); michael@0: this._log.debug("Parameters: " + req.URI.spec + ", " + stream + ", " + off + ", " + count); michael@0: // Cannot proceed, so rethrow and allow the channel to cancel itself. michael@0: throw ex; michael@0: } michael@0: michael@0: try { michael@0: this._data += siStream.read(count); michael@0: } catch (ex) { michael@0: this._log.warn("Exception thrown reading " + count + " bytes from " + siStream + "."); michael@0: throw ex; michael@0: } michael@0: michael@0: try { michael@0: this._onProgress(); michael@0: } catch (ex) { michael@0: this._log.warn("Got exception calling onProgress handler during fetch of " michael@0: + req.URI.spec); michael@0: this._log.debug(CommonUtils.exceptionStr(ex)); michael@0: this._log.trace("Rethrowing; expect a failure code from the HTTP channel."); michael@0: throw ex; michael@0: } michael@0: michael@0: this.delayAbort(); michael@0: }, michael@0: michael@0: /** michael@0: * Create or push back the abort timer that kills this request. michael@0: */ michael@0: delayAbort: function delayAbort() { michael@0: try { michael@0: CommonUtils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer"); michael@0: } catch (ex) { michael@0: this._log.warn("Got exception extending abort timer: " + CommonUtils.exceptionStr(ex)); michael@0: } michael@0: }, michael@0: michael@0: abortRequest: function abortRequest() { michael@0: // Ignore any callbacks if we happen to get any now michael@0: this.onStopRequest = function() {}; michael@0: let error = Components.Exception("Aborting due to channel inactivity.", michael@0: Cr.NS_ERROR_NET_TIMEOUT); michael@0: if (!this._onComplete) { michael@0: this._log.error("Unexpected error: _onComplete not defined in " + michael@0: "abortRequest."); michael@0: return; michael@0: } michael@0: this._onComplete(error); michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * This class handles channel notification events. michael@0: * michael@0: * An instance of this class is bound to each created channel. michael@0: * michael@0: * Optionally pass an array of header names. Each header named michael@0: * in this array will be copied between the channels in the michael@0: * event of a redirect. michael@0: */ michael@0: function ChannelNotificationListener(headersToCopy) { michael@0: this._headersToCopy = headersToCopy; michael@0: michael@0: this._log = Log.repository.getLogger(this._logName); michael@0: this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")]; michael@0: } michael@0: ChannelNotificationListener.prototype = { michael@0: _logName: "Sync.Resource", michael@0: michael@0: getInterface: function(aIID) { michael@0: return this.QueryInterface(aIID); michael@0: }, michael@0: michael@0: QueryInterface: function(aIID) { michael@0: if (aIID.equals(Ci.nsIBadCertListener2) || michael@0: aIID.equals(Ci.nsIInterfaceRequestor) || michael@0: aIID.equals(Ci.nsISupports) || michael@0: aIID.equals(Ci.nsIChannelEventSink)) michael@0: return this; michael@0: michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) { michael@0: let log = Log.repository.getLogger("Sync.CertListener"); michael@0: log.warn("Invalid HTTPS certificate encountered!"); michael@0: michael@0: // This suppresses the UI warning only. The request is still cancelled. michael@0: return true; michael@0: }, michael@0: michael@0: asyncOnChannelRedirect: michael@0: function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) { michael@0: michael@0: let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : ""; michael@0: let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : ""; michael@0: this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags); michael@0: michael@0: this._log.debug("Ensuring load flags are set."); michael@0: newChannel.loadFlags |= DEFAULT_LOAD_FLAGS; michael@0: michael@0: // For internal redirects, copy the headers that our caller set. michael@0: try { michael@0: if ((flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL) && michael@0: newChannel.URI.equals(oldChannel.URI)) { michael@0: this._log.debug("Copying headers for safe internal redirect."); michael@0: michael@0: // QI the channel so we can set headers on it. michael@0: try { michael@0: newChannel.QueryInterface(Ci.nsIHttpChannel); michael@0: } catch (ex) { michael@0: this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); michael@0: throw ex; michael@0: } michael@0: michael@0: for (let header of this._headersToCopy) { michael@0: let value = oldChannel.getRequestHeader(header); michael@0: if (value) { michael@0: let printed = (header == "authorization") ? "****" : value; michael@0: this._log.debug("Header: " + header + " = " + printed); michael@0: newChannel.setRequestHeader(header, value, false); michael@0: } else { michael@0: this._log.warn("No value for header " + header); michael@0: } michael@0: } michael@0: } michael@0: } catch (ex) { michael@0: this._log.error("Error copying headers: " + CommonUtils.exceptionStr(ex)); michael@0: } michael@0: michael@0: // We let all redirects proceed. michael@0: try { michael@0: callback.onRedirectVerifyCallback(Cr.NS_OK); michael@0: } catch (ex) { michael@0: this._log.error("onRedirectVerifyCallback threw!" + CommonUtils.exceptionStr(ex)); michael@0: } michael@0: } michael@0: };