diff --git a/services/sync/modules/policies.js b/services/sync/modules/policies.js index fd4dcb30c69e..76a3516fd2a8 100644 --- a/services/sync/modules/policies.js +++ b/services/sync/modules/policies.js @@ -1014,6 +1014,7 @@ ErrorHandler.prototype = { * This method also looks for "side-channel" warnings. */ checkServerError(resp) { + // In this case we were passed a resolved value of Resource#_doRequest. switch (resp.status) { case 200: case 404: @@ -1083,6 +1084,7 @@ ErrorHandler.prototype = { break; } + // In this other case we were passed a rejection value. switch (resp.result) { case Cr.NS_ERROR_UNKNOWN_HOST: case Cr.NS_ERROR_CONNECTION_REFUSED: diff --git a/services/sync/modules/resource.js b/services/sync/modules/resource.js index 359ea1f501f8..3fedf707c716 100644 --- a/services/sync/modules/resource.js +++ b/services/sync/modules/resource.js @@ -7,20 +7,13 @@ this.EXPORTED_SYMBOLS = ["Resource"]; const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/Log.jsm"); -Cu.import("resource://services-common/async.js"); Cu.import("resource://services-common/observers.js"); Cu.import("resource://services-common/utils.js"); -Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/util.js"); - -const DEFAULT_LOAD_FLAGS = - // Always validate the cache: - Ci.nsIRequest.LOAD_BYPASS_CACHE | - Ci.nsIRequest.INHIBIT_CACHING | - // Don't send user cookies over the wire (Bug 644734). - Ci.nsIRequest.LOAD_ANONYMOUS; +const {setTimeout, clearTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {}); +Cu.importGlobalProperties(["fetch"]); +/* global AbortController */ /* * Resource represents a remote network resource, identified by a URI. @@ -35,31 +28,18 @@ const DEFAULT_LOAD_FLAGS = * put(data, callback) * post(data, callback) * delete(callback) - * - * 'callback' is a function with the following signature: - * - * function callback(error, result) {...} - * - * 'error' will be null on successful requests. Likewise, result will not be - * passed (=undefined) when an error occurs. Note that this is independent of - * the status of the HTTP response. */ this.Resource = function Resource(uri) { this._log = Log.repository.getLogger(this._logName); - this._log.level = - Log.Level[Svc.Prefs.get("log.logger.network.resources")]; + this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")]; this.uri = uri; this._headers = {}; - this._onComplete = Utils.bind2(this, this._onComplete); }; +// (static) Caches the latest server timestamp (X-Weave-Timestamp header). +Resource.serverTime = null; Resource.prototype = { _logName: "Sync.Resource", - // ** {{{ Resource.serverTime }}} ** - // - // Caches the latest server timestamp (X-Weave-Timestamp header). - serverTime: null, - /** * Callback to be invoked at request time to add authentication details. * @@ -71,243 +51,152 @@ Resource.prototype = { // Wait 5 minutes before killing a request. ABORT_TIMEOUT: 300000, - // ** {{{ Resource.headers }}} ** - // // Headers to be included when making a request for the resource. // Note: Header names should be all lower case, there's no explicit // check for duplicates due to case! get headers() { return this._headers; }, - set headers(value) { - this._headers = value; + set headers(_) { + throw new Error("headers can't be mutated directly. Please use setHeader."); }, - setHeader: function Res_setHeader(header, value) { + setHeader(header, value) { this._headers[header.toLowerCase()] = value; }, - get headerNames() { - return Object.keys(this.headers); - }, - // ** {{{ Resource.uri }}} ** - // // URI representing this resource. get uri() { return this._uri; }, set uri(value) { - if (typeof value == "string") + if (typeof value == "string") { this._uri = CommonUtils.makeURI(value); - else + } else { this._uri = value; + } }, - // ** {{{ Resource.spec }}} ** - // // Get the string representation of the URI. get spec() { - if (this._uri) + if (this._uri) { return this._uri.spec; + } return null; }, - // ** {{{ Resource.data }}} ** - // - // Get and set the data encapulated in the resource. - _data: null, - get data() { - return this._data; - }, - set data(value) { - this._data = value; - }, + /** + * @param {string} method HTTP method + * @returns {Headers} + */ + _buildHeaders(method) { + const headers = new Headers(this._headers); - // ** {{{ Resource._createRequest }}} ** - // - // This method returns a new IO Channel for requests to be made - // through. It is never called directly, only {{{_doRequest}}} uses it - // to obtain a request channel. - // - _createRequest(method) { - this.method = method; - let channel = NetUtil.newChannel({uri: this.spec, loadUsingSystemPrincipal: true}) - .QueryInterface(Ci.nsIRequest) - .QueryInterface(Ci.nsIHttpChannel); - - channel.loadFlags |= DEFAULT_LOAD_FLAGS; - - // Setup a callback to handle channel notifications. - let listener = new ChannelNotificationListener(this.headerNames); - channel.notificationCallbacks = listener; - - // Compose a UA string fragment from the various available identifiers. if (Svc.Prefs.get("sendVersionInfo", true)) { - channel.setRequestHeader("user-agent", Utils.userAgent, false); + headers.append("user-agent", Utils.userAgent); } - let headers = this.headers; - if (this.authenticator) { - let result = this.authenticator(this, method); + const result = this.authenticator(this, method); if (result && result.headers) { - for (let [k, v] of Object.entries(result.headers)) { - headers[k.toLowerCase()] = v; + for (const [k, v] of Object.entries(result.headers)) { + headers.append(k.toLowerCase(), v); } } } else { this._log.debug("No authenticator found."); } - for (let key of Object.keys(headers)) { - if (key == "authorization") - this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); - else - this._log.trace("HTTP Header " + key + ": " + headers[key]); - channel.setRequestHeader(key, headers[key], false); - } - return channel; - }, - - _onProgress(channel) {}, - - _doRequest(action, data) { - this._log.trace("In _doRequest."); - return new Promise((resolve, reject) => { - // Don't bother starting a network request if we're shutting down. - Async.checkAppReady(); - this._deferred = { resolve, reject }; - let channel = this._createRequest(action); - - if ("undefined" != typeof(data)) - this._data = data; - - // PUT and POST are treated differently because they have payload data. - if ("PUT" == action || "POST" == action) { - // Convert non-string bodies into JSON - if (this._data.constructor.toString() != String) - this._data = JSON.stringify(this._data); - - this._log.debug(action + " Length: " + this._data.length); - this._log.trace(action + " Body: " + this._data); - - let type = ("content-type" in this._headers) ? - this._headers["content-type"] : "text/plain"; - - let stream = Cc["@mozilla.org/io/string-input-stream;1"]. - createInstance(Ci.nsIStringInputStream); - stream.setData(this._data, this._data.length); - - channel.QueryInterface(Ci.nsIUploadChannel); - channel.setUploadStream(stream, type, this._data.length); - } - - // Setup a channel listener so that the actual network operation - // is performed asynchronously. - let listener = new ChannelListener(this._onComplete, this._onProgress, - this._log, this.ABORT_TIMEOUT); - channel.requestMethod = action; - channel.asyncOpen2(listener); - }); - }, - - _onComplete(ex, data, channel) { - this._log.trace("In _onComplete. An error occurred.", ex); - - if (ex) { - if (!Async.isShutdownException(ex)) { - this._log.warn("${action} request to ${url} failed: ${ex}", - { action: this.method, url: this.uri.spec, ex}); - } - this._deferred.reject(ex); - return; + // PUT and POST are treated differently because they have payload data. + if (("PUT" == method || "POST" == method) && !headers.has("content-type")) { + headers.append("content-type", "text/plain"); } - this._data = data; - let action = channel.requestMethod; - - this._log.trace("Channel: " + channel); - this._log.trace("Action: " + action); - - // Process status and success first. This way a problem with headers - // doesn't fail to include accurate status information. - let status = 0; - let success = false; - - try { - status = channel.responseStatus; - success = channel.requestSucceeded; // HTTP status. - - this._log.trace("Status: " + status); - this._log.trace("Success: " + success); - - // Log the status of the request. - let mesg = [action, success ? "success" : "fail", status, - channel.URI.spec].join(" "); - this._log.debug("mesg: " + mesg); - - if (mesg.length > 200) - mesg = mesg.substr(0, 200) + "…"; - this._log.debug(mesg); - - // Additionally give the full response body when Trace logging. - if (this._log.level <= Log.Level.Trace) - this._log.trace(action + " body: " + data); - - } catch (ex) { - // Got a response, but an exception occurred during processing. - // This shouldn't occur. - this._log.warn("Caught unexpected exception in _oncomplete", ex); - } - - // Process headers. They can be empty, or the call can otherwise fail, so - // put this in its own try block. - let headers = {}; - try { - this._log.trace("Processing response headers."); - - // Read out the response headers if available. - channel.visitResponseHeaders({ - visitHeader: function visitHeader(header, value) { - headers[header.toLowerCase()] = value; + if (this._log.level <= Log.Level.Trace) { + for (const [k, v] of headers) { + if (k == "authorization") { + this._log.trace(`HTTP Header ${k}: ***** (suppressed)`); + } else { + this._log.trace(`HTTP Header ${k}: ${v}`); } - }); - - // This is a server-side safety valve to allow slowing down - // clients without hurting performance. - if (headers["x-weave-backoff"]) { - let backoff = headers["x-weave-backoff"]; - this._log.debug("Got X-Weave-Backoff: " + backoff); - Observers.notify("weave:service:backoff:interval", - parseInt(backoff, 10)); } - - if (success && headers["x-weave-quota-remaining"]) { - Observers.notify("weave:service:quota:remaining", - parseInt(headers["x-weave-quota-remaining"], 10)); - } - - let contentLength = headers["content-length"]; - if (success && contentLength && data && - contentLength != data.length) { - this._log.warn("The response body's length of: " + data.length + - " doesn't match the header's content-length of: " + - contentLength + "."); - } - } catch (ex) { - this._log.debug("Caught exception visiting headers in _onComplete", ex); } + return headers; + }, + + /** + * @param {string} method HTTP method + * @param {string} data HTTP body + * @param {object} signal AbortSignal instance + * @returns {Request} + */ + _createRequest(method, data, signal) { + const headers = this._buildHeaders(method); + const init = { + cache: "no-store", // No cache. + headers, + method, + signal, + mozErrors: true // Return nsresult error codes instead of a generic + // NetworkError when fetch rejects. + }; + + if (data) { + if (!(typeof data == "string" || data instanceof String)) { + data = JSON.stringify(data); + } + this._log.debug(`${method} Length: ${data.length}`); + this._log.trace(`${method} Body: ${data}`); + init.body = data; + } + return new Request(this.uri.spec, init); + }, + + /** + * @param {string} method HTTP method + * @param {string} [data] HTTP body + * @returns {Response} + */ + async _doRequest(method, data = null) { + const controller = new AbortController(); + const request = this._createRequest(method, data, controller.signal); + const responsePromise = fetch(request); // Rejects on network failure. + let didTimeout = false; + const timeoutId = setTimeout(() => { + didTimeout = true; + this._log.error(`Request timed out after ${this.ABORT_TIMEOUT}ms. Aborting.`); + controller.abort(); + }, this.ABORT_TIMEOUT); + let response; + try { + response = await responsePromise; + } catch (e) { + if (!didTimeout) { + throw e; + } + throw Components.Exception("Request aborted (timeout)", Cr.NS_ERROR_NET_TIMEOUT); + } finally { + clearTimeout(timeoutId); + } + return this._processResponse(response, method); + }, + + async _processResponse(response, method) { + const data = await response.text(); + this._logResponse(response, method, data); + this._processResponseHeaders(response); + // Changes below need to be processed in bug 1295510 that's why eslint is ignored // eslint-disable-next-line no-new-wrappers - let ret = new String(data); - ret.url = channel.URI.spec; - ret.status = status; - ret.success = success; - ret.headers = headers; - - if (!success) { - this._log.warn(`${action} request to ${ret.url} failed with status ${status}`); + const ret = new String(data); + ret.url = response.url; + ret.status = response.status; + ret.success = response.ok; + ret.headers = {}; + for (const [k, v] of response.headers) { + ret.headers[k] = v; } + // Make a lazy getter to convert the json response into an object. // Note that this can cause a parse error to be thrown far away from the // actual fetch, so be warned! @@ -324,11 +213,46 @@ Resource.prototype = { } }); - this._deferred.resolve(ret); + return ret; + }, + + _logResponse(response, method, data) { + const { status, ok: success, url } = response; + + // Log the status of the request. + this._log.debug(`${method} ${success ? "success" : "fail"} ${status} ${url}`); + + // Additionally give the full response body when Trace logging. + if (this._log.level <= Log.Level.Trace) { + this._log.trace(`${method} body`, data); + } + + if (!success) { + this._log.warn(`${method} request to ${url} failed with status ${status}`); + } + }, + + _processResponseHeaders({ headers, ok: success }) { + if (headers.has("x-weave-timestamp")) { + Resource.serverTime = parseFloat(headers.get("x-weave-timestamp")); + } + // This is a server-side safety valve to allow slowing down + // clients without hurting performance. + if (headers.has("x-weave-backoff")) { + let backoff = headers.get("x-weave-backoff"); + this._log.debug(`Got X-Weave-Backoff: ${backoff}`); + Observers.notify("weave:service:backoff:interval", + parseInt(backoff, 10)); + } + + if (success && headers.has("x-weave-quota-remaining")) { + Observers.notify("weave:service:quota:remaining", + parseInt(headers.get("x-weave-quota-remaining"), 10)); + } }, get() { - return this._doRequest("GET", undefined); + return this._doRequest("GET"); }, put(data) { @@ -340,237 +264,6 @@ Resource.prototype = { }, delete() { - return this._doRequest("DELETE", undefined); - } -}; - -// = ChannelListener = -// -// This object implements the {{{nsIStreamListener}}} interface -// and is called as the network operation proceeds. -function ChannelListener(onComplete, onProgress, logger, timeout) { - this._onComplete = onComplete; - this._onProgress = onProgress; - this._log = logger; - this._timeout = timeout; - this.delayAbort(); -} -ChannelListener.prototype = { - - onStartRequest: function Channel_onStartRequest(channel) { - this._log.trace("onStartRequest called for channel " + channel + "."); - - try { - channel.QueryInterface(Ci.nsIHttpChannel); - } catch (ex) { - this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); - channel.cancel(Cr.NS_BINDING_ABORTED); - return; - } - - // Save the latest server timestamp when possible. - try { - Resource.serverTime = channel.getResponseHeader("X-Weave-Timestamp") - 0; - } catch (ex) {} - - this._log.trace("onStartRequest: " + channel.requestMethod + " " + - channel.URI.spec); - this._data = ""; - this.delayAbort(); - }, - - onStopRequest: function Channel_onStopRequest(channel, context, status) { - // Clear the abort timer now that the channel is done. - this.abortTimer.clear(); - - if (!this._onComplete) { - this._log.error("Unexpected error: _onComplete not defined in onStopRequest."); - this._onProgress = null; - return; - } - - try { - channel.QueryInterface(Ci.nsIHttpChannel); - } catch (ex) { - this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); - - this._onComplete(ex, this._data, channel); - this._onComplete = this._onProgress = null; - return; - } - - let statusSuccess = Components.isSuccessCode(status); - let uri = channel && channel.URI && channel.URI.spec || ""; - this._log.trace("Channel for " + channel.requestMethod + " " + uri + ": " + - "isSuccessCode(" + status + ")? " + statusSuccess); - - if (this._data == "") { - this._data = null; - } - - // Pass back the failure code and stop execution. Use Components.Exception() - // instead of Error() so the exception is QI-able and can be passed across - // XPCOM borders while preserving the status code. - if (!statusSuccess) { - let message = Components.Exception("", status).name; - let error = Components.Exception(message, status); - - this._onComplete(error, undefined, channel); - this._onComplete = this._onProgress = null; - return; - } - - this._log.trace("Channel: flags = " + channel.loadFlags + - ", URI = " + uri + - ", HTTP success? " + channel.requestSucceeded); - this._onComplete(null, this._data, channel); - this._onComplete = this._onProgress = null; - }, - - onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) { - let siStream; - try { - siStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream); - siStream.init(stream); - } catch (ex) { - this._log.warn("Exception creating nsIScriptableInputStream", ex); - this._log.debug("Parameters: " + req.URI.spec + ", " + stream + ", " + off + ", " + count); - // Cannot proceed, so rethrow and allow the channel to cancel itself. - throw ex; - } - - try { - this._data += siStream.read(count); - } catch (ex) { - this._log.warn("Exception thrown reading " + count + " bytes from " + siStream + "."); - throw ex; - } - - try { - let httpChannel = req.QueryInterface(Ci.nsIHttpChannel); - this._onProgress(httpChannel); - } catch (ex) { - if (Async.isShutdownException(ex)) { - throw ex; - } - this._log.warn("Got exception calling onProgress handler during fetch of " - + req.URI.spec, ex); - this._log.trace("Rethrowing; expect a failure code from the HTTP channel."); - throw ex; - } - - this.delayAbort(); - }, - - /** - * Create or push back the abort timer that kills this request. - */ - delayAbort: function delayAbort() { - try { - CommonUtils.namedTimer(this.abortRequest, this._timeout, this, "abortTimer"); - } catch (ex) { - this._log.warn("Got exception extending abort timer", ex); - } - }, - - abortRequest: function abortRequest() { - // Ignore any callbacks if we happen to get any now - this.onStopRequest = function() {}; - let error = Components.Exception("Aborting due to channel inactivity.", - Cr.NS_ERROR_NET_TIMEOUT); - if (!this._onComplete) { - this._log.error("Unexpected error: _onComplete not defined in " + - "abortRequest."); - return; - } - this._onComplete(error); - } -}; - -/** - * This class handles channel notification events. - * - * An instance of this class is bound to each created channel. - * - * Optionally pass an array of header names. Each header named - * in this array will be copied between the channels in the - * event of a redirect. - */ -function ChannelNotificationListener(headersToCopy) { - this._headersToCopy = headersToCopy; - - this._log = Log.repository.getLogger(this._logName); - this._log.level = Log.Level[Svc.Prefs.get("log.logger.network.resources")]; -} -ChannelNotificationListener.prototype = { - _logName: "Sync.Resource", - - getInterface(aIID) { - return this.QueryInterface(aIID); - }, - - QueryInterface(aIID) { - if (aIID.equals(Ci.nsIBadCertListener2) || - aIID.equals(Ci.nsIInterfaceRequestor) || - aIID.equals(Ci.nsISupports) || - aIID.equals(Ci.nsIChannelEventSink)) - return this; - - throw Cr.NS_ERROR_NO_INTERFACE; - }, - - notifyCertProblem: function certProblem(socketInfo, sslStatus, targetHost) { - let log = Log.repository.getLogger("Sync.CertListener"); - log.warn("Invalid HTTPS certificate encountered!"); - - // This suppresses the UI warning only. The request is still cancelled. - return true; - }, - - asyncOnChannelRedirect: - function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) { - - let oldSpec = (oldChannel && oldChannel.URI) ? oldChannel.URI.spec : ""; - let newSpec = (newChannel && newChannel.URI) ? newChannel.URI.spec : ""; - this._log.debug("Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags); - - this._log.debug("Ensuring load flags are set."); - newChannel.loadFlags |= DEFAULT_LOAD_FLAGS; - - // For internal redirects, copy the headers that our caller set. - try { - if ((flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL) && - newChannel.URI.equals(oldChannel.URI)) { - this._log.debug("Copying headers for safe internal redirect."); - - // QI the channel so we can set headers on it. - try { - newChannel.QueryInterface(Ci.nsIHttpChannel); - } catch (ex) { - this._log.error("Unexpected error: channel is not a nsIHttpChannel!"); - throw ex; - } - - for (let header of this._headersToCopy) { - let value = oldChannel.getRequestHeader(header); - if (value) { - let printed = (header == "authorization") ? "****" : value; - this._log.debug("Header: " + header + " = " + printed); - newChannel.setRequestHeader(header, value, false); - } else { - this._log.warn("No value for header " + header); - } - } - } - } catch (ex) { - this._log.error("Error copying headers", ex); - } - - // We let all redirects proceed. - try { - callback.onRedirectVerifyCallback(Cr.NS_OK); - } catch (ex) { - this._log.error("onRedirectVerifyCallback threw!", ex); - } + return this._doRequest("DELETE"); } }; diff --git a/services/sync/tests/unit/test_resource.js b/services/sync/tests/unit/test_resource.js index 32326fbbd277..5db16ab4a1e6 100644 --- a/services/sync/tests/unit/test_resource.js +++ b/services/sync/tests/unit/test_resource.js @@ -129,7 +129,7 @@ function server_quota_error(request, response) { function server_headers(metadata, response) { let ignore_headers = ["host", "user-agent", "accept", "accept-language", "accept-encoding", "accept-charset", "keep-alive", - "connection", "pragma", "cache-control", + "connection", "pragma", "origin", "cache-control", "content-length"]; let headers = metadata.headers; let header_names = []; @@ -249,9 +249,6 @@ add_test(function test_members() { do_check_eq(res.spec, uri); do_check_eq(typeof res.headers, "object"); do_check_eq(typeof res.authenticator, "object"); - // Initially res.data is null since we haven't performed a GET or - // PUT/POST request yet. - do_check_eq(res.data, null); run_next_test(); }); @@ -263,8 +260,6 @@ add_task(async function test_get() { do_check_eq(content, "This path exists"); do_check_eq(content.status, 200); do_check_true(content.success); - // res.data has been updated with the result from the request - do_check_eq(res.data, content); // Observe logging messages. let resLogger = res._log; @@ -344,7 +339,6 @@ add_task(async function test_put_string() { let content = await res_upload.put(JSON.stringify(sample_data)); do_check_eq(content, "Valid data upload via PUT"); do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); }); add_task(async function test_put_object() { @@ -353,27 +347,6 @@ add_task(async function test_put_object() { let content = await res_upload.put(sample_data); do_check_eq(content, "Valid data upload via PUT"); do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); -}); - -add_task(async function test_put_data_string() { - _("PUT without data arg (uses resource.data) (string)"); - let res_upload = new Resource(server.baseURI + "/upload"); - res_upload.data = JSON.stringify(sample_data); - let content = await res_upload.put(); - do_check_eq(content, "Valid data upload via PUT"); - do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); -}); - -add_task(async function test_put_data_object() { - _("PUT without data arg (uses resource.data) (object)"); - let res_upload = new Resource(server.baseURI + "/upload"); - res_upload.data = sample_data; - let content = await res_upload.put(); - do_check_eq(content, "Valid data upload via PUT"); - do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); }); add_task(async function test_post_string() { @@ -382,7 +355,6 @@ add_task(async function test_post_string() { let content = await res_upload.post(JSON.stringify(sample_data)); do_check_eq(content, "Valid data upload via POST"); do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); }); add_task(async function test_post_object() { @@ -391,27 +363,6 @@ add_task(async function test_post_object() { let content = await res_upload.post(sample_data); do_check_eq(content, "Valid data upload via POST"); do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); -}); - -add_task(async function test_post_data_string() { - _("POST without data arg (uses resource.data) (string)"); - let res_upload = new Resource(server.baseURI + "/upload"); - res_upload.data = JSON.stringify(sample_data); - let content = await res_upload.post(); - do_check_eq(content, "Valid data upload via POST"); - do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); -}); - -add_task(async function test_post_data_object() { - _("POST without data arg (uses resource.data) (object)"); - let res_upload = new Resource(server.baseURI + "/upload"); - res_upload.data = sample_data; - let content = await res_upload.post(); - do_check_eq(content, "Valid data upload via POST"); - do_check_eq(content.status, 200); - do_check_eq(res_upload.data, content); }); add_task(async function test_delete() { @@ -483,14 +434,6 @@ add_task(async function test_setHeader_overwrite() { "x-what-is-weave": "more awesomer"})); }); -add_task(async function test_headers_object() { - _("Setting headers object"); - let res_headers = new Resource(server.baseURI + "/headers"); - res_headers.headers = {}; - let content = await res_headers.get(); - do_check_eq(content, "{}"); -}); - add_task(async function test_put_override_content_type() { _("PUT: override default Content-Type"); let res_headers = new Resource(server.baseURI + "/headers"); @@ -537,59 +480,16 @@ add_task(async function test_quota_notice() { }); add_task(async function test_preserve_exceptions() { - _("Error handling in ChannelListener etc. preserves exception information"); + _("Error handling preserves exception information"); let res11 = new Resource("http://localhost:12345/does/not/exist"); await Assert.rejects(res11.get(), error => { do_check_neq(error, null); do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED); - do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED"); + do_check_eq(error.name, "NS_ERROR_CONNECTION_REFUSED"); return true; }); }); -add_task(async function test_xpc_exception_handling() { - _("Exception handling inside fetches."); - let res14 = new Resource(server.baseURI + "/json"); - res14._onProgress = function(rec) { - // Provoke an XPC exception without a Javascript wrapper. - Services.io.newURI("::::::::"); - }; - let warnings = []; - res14._log.warn = function(msg) { warnings.push(msg); }; - - await Assert.rejects(res14.get(), error => { - do_check_eq(error.result, Cr.NS_ERROR_MALFORMED_URI); - do_check_eq(error.message, "NS_ERROR_MALFORMED_URI"); - return true; - }); - do_check_eq(warnings.pop(), - "${action} request to ${url} failed: ${ex}"); - do_check_eq(warnings.pop(), - "Got exception calling onProgress handler during fetch of " + - server.baseURI + "/json"); -}); - -add_task(async function test_js_exception_handling() { - _("JS exception handling inside fetches."); - let res15 = new Resource(server.baseURI + "/json"); - res15._onProgress = function(rec) { - throw new Error("BOO!"); - }; - let warnings = []; - res15._log.warn = function(msg) { warnings.push(msg); }; - - await Assert.rejects(res15.get(), error => { - do_check_eq(error.result, Cr.NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS); - do_check_eq(error.message, "NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"); - return true; - }); - do_check_eq(warnings.pop(), - "${action} request to ${url} failed: ${ex}"); - do_check_eq(warnings.pop(), - "Got exception calling onProgress handler during fetch of " + - server.baseURI + "/json"); -}); - add_task(async function test_timeout() { _("Ensure channel timeouts are thrown appropriately."); let res19 = new Resource(server.baseURI + "/json"); diff --git a/services/sync/tests/unit/test_warn_on_truncated_response.js b/services/sync/tests/unit/test_warn_on_truncated_response.js deleted file mode 100644 index ade48a337a04..000000000000 --- a/services/sync/tests/unit/test_warn_on_truncated_response.js +++ /dev/null @@ -1,67 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -Cu.import("resource://testing-common/httpd.js"); -Cu.import("resource://services-sync/resource.js"); - -function run_test() { - initTestLogging("Trace"); - run_next_test(); -} - -var BODY = "response body"; -// contentLength needs to be longer than the response body -// length in order to get a mismatch between what is sent in -// the response and the content-length header value. -var contentLength = BODY.length + 1; - -function contentHandler(request, response) { - _("Handling request."); - response.setHeader("Content-Type", "text/plain"); - response.setStatusLine(request.httpVersion, 200, "OK"); - response.bodyOutputStream.write(BODY, contentLength); -} - -function getWarningMessages(log) { - let warnMessages = []; - let warn = log.warn; - log.warn = function(message) { - let regEx = /The response body\'s length of: \d+ doesn\'t match the header\'s content-length of: \d+/i; - if (message.match(regEx)) { - warnMessages.push(message); - } - warn.call(log, message); - }; - return warnMessages; -} - -add_task(async function test_resource_logs_content_length_mismatch() { - _("Issuing request."); - let httpServer = httpd_setup({"/content": contentHandler}); - let resource = new Resource(httpServer.baseURI + "/content"); - - let warnMessages = getWarningMessages(resource._log); - let result = await resource.get(); - - notEqual(warnMessages.length, 0, "test that a warning was logged"); - notEqual(result.length, contentLength); - equal(result, BODY); - - await promiseStopServer(httpServer); -}); - -add_task(async function test_async_resource_logs_content_length_mismatch() { - _("Issuing request."); - let httpServer = httpd_setup({"/content": contentHandler}); - let asyncResource = new Resource(httpServer.baseURI + "/content"); - - let warnMessages = getWarningMessages(asyncResource._log); - - let content = await asyncResource.get(); - equal(content, BODY); - notEqual(warnMessages.length, 0, "test that warning was logged"); - notEqual(content.length, contentLength); - await promiseStopServer(httpServer); -}); diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini index 1b535196ea20..1d6d960775f2 100644 --- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -189,7 +189,6 @@ support-files = prefs_test_prefs_store.js [test_tab_store.js] [test_tab_tracker.js] -[test_warn_on_truncated_response.js] [test_postqueue.js] # Synced tabs.