|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 this.EXPORTED_SYMBOLS = [ |
|
6 "AsyncResource", |
|
7 "Resource" |
|
8 ]; |
|
9 |
|
10 const Cc = Components.classes; |
|
11 const Ci = Components.interfaces; |
|
12 const Cr = Components.results; |
|
13 const Cu = Components.utils; |
|
14 |
|
15 Cu.import("resource://gre/modules/Preferences.jsm"); |
|
16 Cu.import("resource://services-common/async.js"); |
|
17 Cu.import("resource://gre/modules/Log.jsm"); |
|
18 Cu.import("resource://services-common/observers.js"); |
|
19 Cu.import("resource://services-common/utils.js"); |
|
20 Cu.import("resource://services-sync/constants.js"); |
|
21 Cu.import("resource://services-sync/util.js"); |
|
22 |
|
23 const DEFAULT_LOAD_FLAGS = |
|
24 // Always validate the cache: |
|
25 Ci.nsIRequest.LOAD_BYPASS_CACHE | |
|
26 Ci.nsIRequest.INHIBIT_CACHING | |
|
27 // Don't send user cookies over the wire (Bug 644734). |
|
28 Ci.nsIRequest.LOAD_ANONYMOUS; |
|
29 |
|
30 /* |
|
31 * AsyncResource represents a remote network resource, identified by a URI. |
|
32 * Create an instance like so: |
|
33 * |
|
34 * let resource = new AsyncResource("http://foobar.com/path/to/resource"); |
|
35 * |
|
36 * The 'resource' object has the following methods to issue HTTP requests |
|
37 * of the corresponding HTTP methods: |
|
38 * |
|
39 * get(callback) |
|
40 * put(data, callback) |
|
41 * post(data, callback) |
|
42 * delete(callback) |
|
43 * |
|
44 * 'callback' is a function with the following signature: |
|
45 * |
|
46 * function callback(error, result) {...} |
|
47 * |
|
48 * 'error' will be null on successful requests. Likewise, result will not be |
|
49 * passed (=undefined) when an error occurs. Note that this is independent of |
|
50 * the status of the HTTP response. |
|
51 */ |
|
52 this.AsyncResource = function AsyncResource(uri) { |
|
53 this._log = Log.repository.getLogger(this._logName); |
|
54 this._log.level = |
|
55 Log.Level[Svc.Prefs.get("log.logger.network.resources")]; |
|
56 this.uri = uri; |
|
57 this._headers = {}; |
|
58 this._onComplete = Utils.bind2(this, this._onComplete); |
|
59 } |
|
60 AsyncResource.prototype = { |
|
61 _logName: "Sync.AsyncResource", |
|
62 |
|
63 // ** {{{ AsyncResource.serverTime }}} ** |
|
64 // |
|
65 // Caches the latest server timestamp (X-Weave-Timestamp header). |
|
66 serverTime: null, |
|
67 |
|
68 /** |
|
69 * Callback to be invoked at request time to add authentication details. |
|
70 * |
|
71 * By default, a global authenticator is provided. If this is set, it will |
|
72 * be used instead of the global one. |
|
73 */ |
|
74 authenticator: null, |
|
75 |
|
76 // The string to use as the base User-Agent in Sync requests. |
|
77 // These strings will look something like |
|
78 // |
|
79 // Firefox/4.0 FxSync/1.8.0.20100101.mobile |
|
80 // |
|
81 // or |
|
82 // |
|
83 // Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop |
|
84 // |
|
85 _userAgent: |
|
86 Services.appinfo.name + "/" + Services.appinfo.version + // Product. |
|
87 " FxSync/" + WEAVE_VERSION + "." + // Sync. |
|
88 Services.appinfo.appBuildID + ".", // Build. |
|
89 |
|
90 // Wait 5 minutes before killing a request. |
|
91 ABORT_TIMEOUT: 300000, |
|
92 |
|
93 // ** {{{ AsyncResource.headers }}} ** |
|
94 // |
|
95 // Headers to be included when making a request for the resource. |
|
96 // Note: Header names should be all lower case, there's no explicit |
|
97 // check for duplicates due to case! |
|
98 get headers() { |
|
99 return this._headers; |
|
100 }, |
|
101 set headers(value) { |
|
102 this._headers = value; |
|
103 }, |
|
104 setHeader: function Res_setHeader(header, value) { |
|
105 this._headers[header.toLowerCase()] = value; |
|
106 }, |
|
107 get headerNames() { |
|
108 return Object.keys(this.headers); |
|
109 }, |
|
110 |
|
111 // ** {{{ AsyncResource.uri }}} ** |
|
112 // |
|
113 // URI representing this resource. |
|
114 get uri() { |
|
115 return this._uri; |
|
116 }, |
|
117 set uri(value) { |
|
118 if (typeof value == 'string') |
|
119 this._uri = CommonUtils.makeURI(value); |
|
120 else |
|
121 this._uri = value; |
|
122 }, |
|
123 |
|
124 // ** {{{ AsyncResource.spec }}} ** |
|
125 // |
|
126 // Get the string representation of the URI. |
|
127 get spec() { |
|
128 if (this._uri) |
|
129 return this._uri.spec; |
|
130 return null; |
|
131 }, |
|
132 |
|
133 // ** {{{ AsyncResource.data }}} ** |
|
134 // |
|
135 // Get and set the data encapulated in the resource. |
|
136 _data: null, |
|
137 get data() this._data, |
|
138 set data(value) { |
|
139 this._data = value; |
|
140 }, |
|
141 |
|
142 // ** {{{ AsyncResource._createRequest }}} ** |
|
143 // |
|
144 // This method returns a new IO Channel for requests to be made |
|
145 // through. It is never called directly, only {{{_doRequest}}} uses it |
|
146 // to obtain a request channel. |
|
147 // |
|
148 _createRequest: function Res__createRequest(method) { |
|
149 let channel = Services.io.newChannel(this.spec, null, null) |
|
150 .QueryInterface(Ci.nsIRequest) |
|
151 .QueryInterface(Ci.nsIHttpChannel); |
|
152 |
|
153 channel.loadFlags |= DEFAULT_LOAD_FLAGS; |
|
154 |
|
155 // Setup a callback to handle channel notifications. |
|
156 let listener = new ChannelNotificationListener(this.headerNames); |
|
157 channel.notificationCallbacks = listener; |
|
158 |
|
159 // Compose a UA string fragment from the various available identifiers. |
|
160 if (Svc.Prefs.get("sendVersionInfo", true)) { |
|
161 let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop"); |
|
162 channel.setRequestHeader("user-agent", ua, false); |
|
163 } |
|
164 |
|
165 let headers = this.headers; |
|
166 |
|
167 if (this.authenticator) { |
|
168 let result = this.authenticator(this, method); |
|
169 if (result && result.headers) { |
|
170 for (let [k, v] in Iterator(result.headers)) { |
|
171 headers[k.toLowerCase()] = v; |
|
172 } |
|
173 } |
|
174 } else { |
|
175 this._log.debug("No authenticator found."); |
|
176 } |
|
177 |
|
178 for (let [key, value] in Iterator(headers)) { |
|
179 if (key == 'authorization') |
|
180 this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); |
|
181 else |
|
182 this._log.trace("HTTP Header " + key + ": " + headers[key]); |
|
183 channel.setRequestHeader(key, headers[key], false); |
|
184 } |
|
185 return channel; |
|
186 }, |
|
187 |
|
188 _onProgress: function Res__onProgress(channel) {}, |
|
189 |
|
190 _doRequest: function _doRequest(action, data, callback) { |
|
191 this._log.trace("In _doRequest."); |
|
192 this._callback = callback; |
|
193 let channel = this._createRequest(action); |
|
194 |
|
195 if ("undefined" != typeof(data)) |
|
196 this._data = data; |
|
197 |
|
198 // PUT and POST are treated differently because they have payload data. |
|
199 if ("PUT" == action || "POST" == action) { |
|
200 // Convert non-string bodies into JSON |
|
201 if (this._data.constructor.toString() != String) |
|
202 this._data = JSON.stringify(this._data); |
|
203 |
|
204 this._log.debug(action + " Length: " + this._data.length); |
|
205 this._log.trace(action + " Body: " + this._data); |
|
206 |
|
207 let type = ('content-type' in this._headers) ? |
|
208 this._headers['content-type'] : 'text/plain'; |
|
209 |
|
210 let stream = Cc["@mozilla.org/io/string-input-stream;1"]. |
|
211 createInstance(Ci.nsIStringInputStream); |
|
212 stream.setData(this._data, this._data.length); |
|
213 |
|
214 channel.QueryInterface(Ci.nsIUploadChannel); |
|
215 channel.setUploadStream(stream, type, this._data.length); |
|
216 } |
|
217 |
|
218 // Setup a channel listener so that the actual network operation |
|
219 // is performed asynchronously. |
|
220 let listener = new ChannelListener(this._onComplete, this._onProgress, |
|
221 this._log, this.ABORT_TIMEOUT); |
|
222 channel.requestMethod = action; |
|
223 try { |
|
224 channel.asyncOpen(listener, null); |
|
225 } catch (ex) { |
|
226 // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port. |
|
227 this._log.warn("Caught an error in asyncOpen: " + CommonUtils.exceptionStr(ex)); |
|
228 CommonUtils.nextTick(callback.bind(this, ex)); |
|
229 } |
|
230 }, |
|
231 |
|
232 _onComplete: function _onComplete(error, data, channel) { |
|
233 this._log.trace("In _onComplete. Error is " + error + "."); |
|
234 |
|
235 if (error) { |
|
236 this._callback(error); |
|
237 return; |
|
238 } |
|
239 |
|
240 this._data = data; |
|
241 let action = channel.requestMethod; |
|
242 |
|
243 this._log.trace("Channel: " + channel); |
|
244 this._log.trace("Action: " + action); |
|
245 |
|
246 // Process status and success first. This way a problem with headers |
|
247 // doesn't fail to include accurate status information. |
|
248 let status = 0; |
|
249 let success = false; |
|
250 |
|
251 try { |
|
252 status = channel.responseStatus; |
|
253 success = channel.requestSucceeded; // HTTP status. |
|
254 |
|
255 this._log.trace("Status: " + status); |
|
256 this._log.trace("Success: " + success); |
|
257 |
|
258 // Log the status of the request. |
|
259 let mesg = [action, success ? "success" : "fail", status, |
|
260 channel.URI.spec].join(" "); |
|
261 this._log.debug("mesg: " + mesg); |
|
262 |
|
263 if (mesg.length > 200) |
|
264 mesg = mesg.substr(0, 200) + "…"; |
|
265 this._log.debug(mesg); |
|
266 |
|
267 // Additionally give the full response body when Trace logging. |
|
268 if (this._log.level <= Log.Level.Trace) |
|
269 this._log.trace(action + " body: " + data); |
|
270 |
|
271 } catch(ex) { |
|
272 // Got a response, but an exception occurred during processing. |
|
273 // This shouldn't occur. |
|
274 this._log.warn("Caught unexpected exception " + CommonUtils.exceptionStr(ex) + |
|
275 " in _onComplete."); |
|
276 this._log.debug(CommonUtils.stackTrace(ex)); |
|
277 } |
|
278 |
|
279 // Process headers. They can be empty, or the call can otherwise fail, so |
|
280 // put this in its own try block. |
|
281 let headers = {}; |
|
282 try { |
|
283 this._log.trace("Processing response headers."); |
|
284 |
|
285 // Read out the response headers if available. |
|
286 channel.visitResponseHeaders({ |
|
287 visitHeader: function visitHeader(header, value) { |
|
288 headers[header.toLowerCase()] = value; |
|
289 } |
|
290 }); |
|
291 |
|
292 // This is a server-side safety valve to allow slowing down |
|
293 // clients without hurting performance. |
|
294 if (headers["x-weave-backoff"]) { |
|
295 let backoff = headers["x-weave-backoff"]; |
|
296 this._log.debug("Got X-Weave-Backoff: " + backoff); |
|
297 Observers.notify("weave:service:backoff:interval", |
|
298 parseInt(backoff, 10)); |
|
299 } |
|
300 |
|
301 if (success && headers["x-weave-quota-remaining"]) { |
|
302 Observers.notify("weave:service:quota:remaining", |
|
303 parseInt(headers["x-weave-quota-remaining"], 10)); |
|
304 } |
|
305 } catch (ex) { |
|
306 this._log.debug("Caught exception " + CommonUtils.exceptionStr(ex) + |
|
307 " visiting headers in _onComplete."); |
|
308 this._log.debug(CommonUtils.stackTrace(ex)); |
|
309 } |
|
310 |
|
311 let ret = new String(data); |
|
312 ret.status = status; |
|
313 ret.success = success; |
|
314 ret.headers = headers; |
|
315 |
|
316 // Make a lazy getter to convert the json response into an object. |
|
317 // Note that this can cause a parse error to be thrown far away from the |
|
318 // actual fetch, so be warned! |
|
319 XPCOMUtils.defineLazyGetter(ret, "obj", function() { |
|
320 try { |
|
321 return JSON.parse(ret); |
|
322 } catch (ex) { |
|
323 this._log.warn("Got exception parsing response body: \"" + CommonUtils.exceptionStr(ex)); |
|
324 // Stringify to avoid possibly printing non-printable characters. |
|
325 this._log.debug("Parse fail: Response body starts: \"" + |
|
326 JSON.stringify((ret + "").slice(0, 100)) + |
|
327 "\"."); |
|
328 throw ex; |
|
329 } |
|
330 }.bind(this)); |
|
331 |
|
332 this._callback(null, ret); |
|
333 }, |
|
334 |
|
335 get: function get(callback) { |
|
336 this._doRequest("GET", undefined, callback); |
|
337 }, |
|
338 |
|
339 put: function put(data, callback) { |
|
340 if (typeof data == "function") |
|
341 [data, callback] = [undefined, data]; |
|
342 this._doRequest("PUT", data, callback); |
|
343 }, |
|
344 |
|
345 post: function post(data, callback) { |
|
346 if (typeof data == "function") |
|
347 [data, callback] = [undefined, data]; |
|
348 this._doRequest("POST", data, callback); |
|
349 }, |
|
350 |
|
351 delete: function delete_(callback) { |
|
352 this._doRequest("DELETE", undefined, callback); |
|
353 } |
|
354 }; |
|
355 |
|
356 |
|
357 /* |
|
358 * Represent a remote network resource, identified by a URI, with a |
|
359 * synchronous API. |
|
360 * |
|
361 * 'Resource' is not recommended for new code. Use the asynchronous API of |
|
362 * 'AsyncResource' instead. |
|
363 */ |
|
364 this.Resource = function Resource(uri) { |
|
365 AsyncResource.call(this, uri); |
|
366 } |
|
367 Resource.prototype = { |
|
368 |
|
369 __proto__: AsyncResource.prototype, |
|
370 |
|
371 _logName: "Sync.Resource", |
|
372 |
|
373 // ** {{{ Resource._request }}} ** |
|
374 // |
|
375 // Perform a particular HTTP request on the resource. This method |
|
376 // is never called directly, but is used by the high-level |
|
377 // {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods. |
|
378 _request: function Res__request(action, data) { |
|
379 let cb = Async.makeSyncCallback(); |
|
380 function callback(error, ret) { |
|
381 if (error) |
|
382 cb.throw(error); |
|
383 cb(ret); |
|
384 } |
|
385 |
|
386 // The channel listener might get a failure code |
|
387 try { |
|
388 this._doRequest(action, data, callback); |
|
389 return Async.waitForSyncCallback(cb); |
|
390 } catch(ex) { |
|
391 // Combine the channel stack with this request stack. Need to create |
|
392 // a new error object for that. |
|
393 let error = Error(ex.message); |
|
394 error.result = ex.result; |
|
395 let chanStack = []; |
|
396 if (ex.stack) |
|
397 chanStack = ex.stack.trim().split(/\n/).slice(1); |
|
398 let requestStack = error.stack.split(/\n/).slice(1); |
|
399 |
|
400 // Strip out the args for the last 2 frames because they're usually HUGE! |
|
401 for (let i = 0; i <= 1; i++) |
|
402 requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@"); |
|
403 |
|
404 error.stack = chanStack.concat(requestStack).join("\n"); |
|
405 throw error; |
|
406 } |
|
407 }, |
|
408 |
|
409 // ** {{{ Resource.get }}} ** |
|
410 // |
|
411 // Perform an asynchronous HTTP GET for this resource. |
|
412 get: function Res_get() { |
|
413 return this._request("GET"); |
|
414 }, |
|
415 |
|
416 // ** {{{ Resource.put }}} ** |
|
417 // |
|
418 // Perform a HTTP PUT for this resource. |
|
419 put: function Res_put(data) { |
|
420 return this._request("PUT", data); |
|
421 }, |
|
422 |
|
423 // ** {{{ Resource.post }}} ** |
|
424 // |
|
425 // Perform a HTTP POST for this resource. |
|
426 post: function Res_post(data) { |
|
427 return this._request("POST", data); |
|
428 }, |
|
429 |
|
430 // ** {{{ Resource.delete }}} ** |
|
431 // |
|
432 // Perform a HTTP DELETE for this resource. |
|
433 delete: function Res_delete() { |
|
434 return this._request("DELETE"); |
|
435 } |
|
436 }; |
|
437 |
|
438 // = ChannelListener = |
|
439 // |
|
440 // This object implements the {{{nsIStreamListener}}} interface |
|
441 // and is called as the network operation proceeds. |
|
442 function ChannelListener(onComplete, onProgress, logger, timeout) { |
|
443 this._onComplete = onComplete; |
|
444 this._onProgress = onProgress; |
|
445 this._log = logger; |
|
446 this._timeout = timeout; |
|
447 this.delayAbort(); |
|
448 } |
|
449 ChannelListener.prototype = { |
|
450 |
|
451 onStartRequest: function Channel_onStartRequest(channel) { |
|
452 this._log.trace("onStartRequest called for channel " + channel + "."); |
|
453 |
|
454 try { |
|
455 channel.QueryInterface(Ci.nsIHttpChannel); |
|
456 } catch (ex) { |
|
457 this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); |
|
458 channel.cancel(Cr.NS_BINDING_ABORTED); |
|
459 return; |
|
460 } |
|
461 |
|
462 // Save the latest server timestamp when possible. |
|
463 try { |
|
464 AsyncResource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0; |
|
465 } |
|
466 catch(ex) {} |
|
467 |
|
468 this._log.trace("onStartRequest: " + channel.requestMethod + " " + |
|
469 channel.URI.spec); |
|
470 this._data = ''; |
|
471 this.delayAbort(); |
|
472 }, |
|
473 |
|
474 onStopRequest: function Channel_onStopRequest(channel, context, status) { |
|
475 // Clear the abort timer now that the channel is done. |
|
476 this.abortTimer.clear(); |
|
477 |
|
478 if (!this._onComplete) { |
|
479 this._log.error("Unexpected error: _onComplete not defined in onStopRequest."); |
|
480 this._onProgress = null; |
|
481 return; |
|
482 } |
|
483 |
|
484 try { |
|
485 channel.QueryInterface(Ci.nsIHttpChannel); |
|
486 } catch (ex) { |
|
487 this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); |
|
488 |
|
489 this._onComplete(ex, this._data, channel); |
|
490 this._onComplete = this._onProgress = null; |
|
491 return; |
|
492 } |
|
493 |
|
494 let statusSuccess = Components.isSuccessCode(status); |
|
495 let uri = channel && channel.URI && channel.URI.spec || "<unknown>"; |
|
496 this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " + |
|
497 "isSuccessCode(" + status + ")? " + statusSuccess); |
|
498 |
|
499 if (this._data == '') { |
|
500 this._data = null; |
|
501 } |
|
502 |
|
503 // Pass back the failure code and stop execution. Use Components.Exception() |
|
504 // instead of Error() so the exception is QI-able and can be passed across |
|
505 // XPCOM borders while preserving the status code. |
|
506 if (!statusSuccess) { |
|
507 let message = Components.Exception("", status).name; |
|
508 let error = Components.Exception(message, status); |
|
509 |
|
510 this._onComplete(error, undefined, channel); |
|
511 this._onComplete = this._onProgress = null; |
|
512 return; |
|
513 } |
|
514 |
|
515 this._log.trace("Channel: flags = " + channel.loadFlags + |
|
516 ", URI = " + uri + |
|
517 ", HTTP success? " + channel.requestSucceeded); |
|
518 this._onComplete(null, this._data, channel); |
|
519 this._onComplete = this._onProgress = null; |
|
520 }, |
|
521 |
|
522 onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) { |
|
523 let siStream; |
|
524 try { |
|
525 siStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); |
|
526 siStream.init(stream); |
|
527 } catch (ex) { |
|
528 this._log.warn("Exception creating nsIScriptableInputStream." + CommonUtils.exceptionStr(ex)); |
|
529 this._log.debug("Parameters: " + req.URI.spec + ", " + stream + ", " + off + ", " + count); |
|
530 // Cannot proceed, so rethrow and allow the channel to cancel itself. |
|
531 throw ex; |
|
532 } |
|
533 |
|
534 try { |
|
535 this._data += siStream.read(count); |
|
536 } catch (ex) { |
|
537 this._log.warn("Exception thrown reading " + count + " bytes from " + siStream + "."); |
|
538 throw ex; |
|
539 } |
|
540 |
|
541 try { |
|
542 this._onProgress(); |
|
543 } catch (ex) { |
|
544 this._log.warn("Got exception calling onProgress handler during fetch of " |
|
545 + req.URI.spec); |
|
546 this._log.debug(CommonUtils.exceptionStr(ex)); |
|
547 this._log.trace("Rethrowing; expect a failure code from the HTTP channel."); |
|
548 throw ex; |
|
549 } |
|
550 |
|
551 this.delayAbort(); |
|
552 }, |
|
553 |
|
554 /** |
|
555 * Create or push back the abort timer that kills this request. |
|
556 */ |
|
557 delayAbort: function delayAbort() { |
|
558 try { |
|
559 CommonUtils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer"); |
|
560 } catch (ex) { |
|
561 this._log.warn("Got exception extending abort timer: " + CommonUtils.exceptionStr(ex)); |
|
562 } |
|
563 }, |
|
564 |
|
565 abortRequest: function abortRequest() { |
|
566 // Ignore any callbacks if we happen to get any now |
|
567 this.onStopRequest = function() {}; |
|
568 let error = Components.Exception("Aborting due to channel inactivity.", |
|
569 Cr.NS_ERROR_NET_TIMEOUT); |
|
570 if (!this._onComplete) { |
|
571 this._log.error("Unexpected error: _onComplete not defined in " + |
|
572 "abortRequest."); |
|
573 return; |
|
574 } |
|
575 this._onComplete(error); |
|
576 } |
|
577 }; |
|
578 |
|
579 /** |
|
580 * This class handles channel notification events. |
|
581 * |
|
582 * An instance of this class is bound to each created channel. |
|
583 * |
|
584 * Optionally pass an array of header names. Each header named |
|
585 * in this array will be copied between the channels in the |
|
586 * event of a redirect. |
|
587 */ |
|
588 function ChannelNotificationListener(headersToCopy) { |
|
589 this._headersToCopy = headersToCopy; |
|
590 |
|
591 this._log = Log.repository.getLogger(this._logName); |
|
592 this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")]; |
|
593 } |
|
594 ChannelNotificationListener.prototype = { |
|
595 _logName: "Sync.Resource", |
|
596 |
|
597 getInterface: function(aIID) { |
|
598 return this.QueryInterface(aIID); |
|
599 }, |
|
600 |
|
601 QueryInterface: function(aIID) { |
|
602 if (aIID.equals(Ci.nsIBadCertListener2) || |
|
603 aIID.equals(Ci.nsIInterfaceRequestor) || |
|
604 aIID.equals(Ci.nsISupports) || |
|
605 aIID.equals(Ci.nsIChannelEventSink)) |
|
606 return this; |
|
607 |
|
608 throw Cr.NS_ERROR_NO_INTERFACE; |
|
609 }, |
|
610 |
|
611 notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) { |
|
612 let log = Log.repository.getLogger("Sync.CertListener"); |
|
613 log.warn("Invalid HTTPS certificate encountered!"); |
|
614 |
|
615 // This suppresses the UI warning only. The request is still cancelled. |
|
616 return true; |
|
617 }, |
|
618 |
|
619 asyncOnChannelRedirect: |
|
620 function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) { |
|
621 |
|
622 let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : "<undefined>"; |
|
623 let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : "<undefined>"; |
|
624 this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags); |
|
625 |
|
626 this._log.debug("Ensuring load flags are set."); |
|
627 newChannel.loadFlags |= DEFAULT_LOAD_FLAGS; |
|
628 |
|
629 // For internal redirects, copy the headers that our caller set. |
|
630 try { |
|
631 if ((flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL) && |
|
632 newChannel.URI.equals(oldChannel.URI)) { |
|
633 this._log.debug("Copying headers for safe internal redirect."); |
|
634 |
|
635 // QI the channel so we can set headers on it. |
|
636 try { |
|
637 newChannel.QueryInterface(Ci.nsIHttpChannel); |
|
638 } catch (ex) { |
|
639 this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); |
|
640 throw ex; |
|
641 } |
|
642 |
|
643 for (let header of this._headersToCopy) { |
|
644 let value = oldChannel.getRequestHeader(header); |
|
645 if (value) { |
|
646 let printed = (header == "authorization") ? "****" : value; |
|
647 this._log.debug("Header: " + header + " = " + printed); |
|
648 newChannel.setRequestHeader(header, value, false); |
|
649 } else { |
|
650 this._log.warn("No value for header " + header); |
|
651 } |
|
652 } |
|
653 } |
|
654 } catch (ex) { |
|
655 this._log.error("Error copying headers: " + CommonUtils.exceptionStr(ex)); |
|
656 } |
|
657 |
|
658 // We let all redirects proceed. |
|
659 try { |
|
660 callback.onRedirectVerifyCallback(Cr.NS_OK); |
|
661 } catch (ex) { |
|
662 this._log.error("onRedirectVerifyCallback threw!" + CommonUtils.exceptionStr(ex)); |
|
663 } |
|
664 } |
|
665 }; |