diff options
Diffstat (limited to 'mailnews/base/prefs/content/accountcreation/fetchhttp.js')
-rw-r--r-- | mailnews/base/prefs/content/accountcreation/fetchhttp.js | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/mailnews/base/prefs/content/accountcreation/fetchhttp.js b/mailnews/base/prefs/content/accountcreation/fetchhttp.js new file mode 100644 index 000000000..04f5272cd --- /dev/null +++ b/mailnews/base/prefs/content/accountcreation/fetchhttp.js @@ -0,0 +1,267 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * This is a small wrapper around XMLHttpRequest, which solves various + * inadequacies of the API, e.g. error handling. It is entirely generic and + * can be used for purposes outside of even mail. + * + * It does not provide download progress, but assumes that the + * fetched resource is so small (<1 10 KB) that the roundtrip and + * response generation is far more significant than the + * download time of the response. In other words, it's fine for RPC, + * but not for bigger file downloads. + */ + +Components.utils.import("resource://gre/modules/JXON.js"); + +/** + * Set up a fetch. + * + * @param url {String} URL of the server function. + * ATTENTION: The caller needs to make sure that the URL is secure to call. + * @param urlArgs {Object, associative array} Parameters to add + * to the end of the URL as query string. E.g. + * { foo: "bla", bar: "blub blub" } will add "?foo=bla&bar=blub%20blub" + * to the URL + * (unless the URL already has a "?", then it adds "&foo..."). + * The values will be urlComponentEncoded, so pass them unencoded. + * @param post {Boolean} HTTP GET or POST + * Only influences the HTTP request method, + * i.e. first line of the HTTP request, not the body or parameters. + * Use POST when you modify server state, + * GET when you only request information. + * + * @param successCallback {Function(result {String})} + * Called when the server call worked (no errors). + * |result| will contain the body of the HTTP reponse, as string. + * @param errorCallback {Function(ex)} + * Called in case of error. ex contains the error + * with a user-displayable but not localized |.message| and maybe a + * |.code|, which can be either + * - an nsresult error code, + * - an HTTP result error code (0...1000) or + * - negative: 0...-100 : + * -2 = can't resolve server in DNS etc. + * -4 = response body (e.g. XML) malformed + */ +/* not yet supported: + * @param headers {Object, associative array} Like urlArgs, + * just that the params will be added as HTTP headers. + * { foo: "blub blub" } will add "Foo: Blub blub" + * The values will be urlComponentEncoded, apart from space, + * so pass them unencoded. + * @param headerArgs {Object, associative array} Like urlArgs, + * just that the params will be added as HTTP headers. + * { foo: "blub blub" } will add "X-Moz-Arg-Foo: Blub blub" + * The values will be urlComponentEncoded, apart from space, + * so pass them unencoded. + * @param bodyArgs {Object, associative array} Like urlArgs, + * just that the params will be sent x-url-encoded in the body, + * like a HTML form post. + * The values will be urlComponentEncoded, so pass them unencoded. + * This cannot be used together with |uploadBody|. + * @param uploadbody {Object} Arbitrary object, which to use as + * body of the HTTP request. Will also set the mimetype accordingly. + * Only supported object types, currently only E4X is supported + * (sending XML). + * Usually, you have nothing to upload, so just pass |null|. + */ +function FetchHTTP(url, urlArgs, post, successCallback, errorCallback) +{ + assert(typeof(successCallback) == "function", "BUG: successCallback"); + assert(typeof(errorCallback) == "function", "BUG: errorCallback"); + this._url = sanitize.string(url); + if (!urlArgs) + urlArgs = {}; + + this._urlArgs = urlArgs; + this._post = sanitize.boolean(post); + this._successCallback = successCallback; + this._errorCallback = errorCallback; +} +FetchHTTP.prototype = +{ + __proto__: Abortable.prototype, + _url : null, // URL as passed to ctor, without arguments + _urlArgs : null, + _post : null, + _successCallback : null, + _errorCallback : null, + _request : null, // the XMLHttpRequest object + result : null, + + start : function() + { + var url = this._url; + for (var name in this._urlArgs) + { + url += (!url.includes("?") ? "?" : "&") + + name + "=" + encodeURIComponent(this._urlArgs[name]); + } + this._request = new XMLHttpRequest(); + let request = this._request; + request.open(this._post ? "POST" : "GET", url); + request.channel.loadGroup = null; + // needs bug 407190 patch v4 (or higher) - uncomment if that lands. + // try { + // var channel = request.channel.QueryInterface(Ci.nsIHttpChannel2); + // channel.connectTimeout = 5; + // channel.requestTimeout = 5; + // } catch (e) { dump(e + "\n"); } + + var me = this; + request.onload = function() { me._response(true); } + request.onerror = function() { me._response(false); } + request.send(null); + }, + _response : function(success, exStored) + { + try + { + var errorCode = null; + var errorStr = null; + + if (success && this._request.status >= 200 && + this._request.status < 300) // HTTP level success + { + try + { + // response + var mimetype = this._request.getResponseHeader("Content-Type"); + if (!mimetype) + mimetype = ""; + mimetype = mimetype.split(";")[0]; + if (mimetype == "text/xml" || + mimetype == "application/xml" || + mimetype == "text/rdf") + { + this.result = JXON.build(this._request.responseXML); + } + else + { + //ddump("mimetype: " + mimetype + " only supported as text"); + this.result = this._request.responseText; + } + //ddump("result:\n" + this.result); + } + catch (e) + { + success = false; + errorStr = getStringBundle( + "chrome://messenger/locale/accountCreationUtil.properties") + .GetStringFromName("bad_response_content.error"); + errorCode = -4; + } + } + else + { + success = false; + try + { + errorCode = this._request.status; + errorStr = this._request.statusText; + } catch (e) { + // If we can't resolve the hostname in DNS etc., .statusText throws + errorCode = -2; + errorStr = getStringBundle( + "chrome://messenger/locale/accountCreationUtil.properties") + .GetStringFromName("cannot_contact_server.error"); + ddump(errorStr); + } + } + + // Callbacks + if (success) + { + try { + this._successCallback(this.result); + } catch (e) { + logException(e); + this._error(e); + } + } + else if (exStored) + this._error(exStored); + else + this._error(new ServerException(errorStr, errorCode, this._url)); + + if (this._finishedCallback) + { + try { + this._finishedCallback(this); + } catch (e) { + logException(e); + this._error(e); + } + } + + } catch (e) { + // error in our fetchhttp._response() code + logException(e); + this._error(e); + } + }, + _error : function(e) + { + try { + this._errorCallback(e); + } catch (e) { + // error in errorCallback, too! + logException(e); + alertPrompt("Error in errorCallback for fetchhttp", e); + } + }, + /** + * Call this between start() and finishedCallback fired. + */ + cancel : function(ex) + { + assert(!this.result, "Call already returned"); + + this._request.abort(); + + // Need to manually call error handler + // <https://bugzilla.mozilla.org/show_bug.cgi?id=218236#c11> + this._response(false, ex ? ex : new UserCancelledException()); + }, + /** + * Allows caller or lib to be notified when the call is done. + * This is useful to enable and disable a Cancel button in the UI, + * which allows to cancel the network request. + */ + setFinishedCallback : function(finishedCallback) + { + this._finishedCallback = finishedCallback; + } +} + +function CancelledException(msg) +{ + Exception.call(this, msg); +} +CancelledException.prototype = Object.create(Exception.prototype); +CancelledException.prototype.constructor = CancelledException; + +function UserCancelledException(msg) +{ + // The user knows they cancelled so I don't see a need + // for a message to that effect. + if (!msg) + msg = "User cancelled"; + CancelledException.call(this, msg); +} +UserCancelledException.prototype = Object.create(CancelledException.prototype); +UserCancelledException.prototype.constructor = UserCancelledException; + +function ServerException(msg, code, uri) +{ + Exception.call(this, msg); + this.code = code; + this.uri = uri; +} +ServerException.prototype = Object.create(Exception.prototype); +ServerException.prototype.constructor = ServerException; + |