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: const EXPORTED_SYMBOLS = ["httpRequest", "percentEncode"]; michael@0: michael@0: const {classes: Cc, interfaces: Ci, utils: Cu} = Components; michael@0: michael@0: // Strictly follow RFC 3986 when encoding URI components. michael@0: // Accepts a unescaped string and returns the URI encoded string for use in michael@0: // an HTTP request. michael@0: function percentEncode(aString) michael@0: encodeURIComponent(aString).replace(/[!'()]/g, escape).replace(/\*/g, "%2A"); michael@0: michael@0: /* michael@0: * aOptions can have a variety of fields: michael@0: * headers, an array of headers michael@0: * postData, this can be: michael@0: * a string: send it as is michael@0: * an array of parameters: encode as form values michael@0: * null/undefined: no POST data. michael@0: * method, GET, POST or PUT (this is set automatically if postData exists). michael@0: * onLoad, a function handle to call when the load is complete, it takes two michael@0: * parameters: the responseText and the XHR object. michael@0: * onError, a function handle to call when an error occcurs, it takes three michael@0: * parameters: the error, the responseText and the XHR object. michael@0: * logger, an object that implements the debug and log methods (e.g. log.jsm). michael@0: * michael@0: * Headers or post data are given as an array of arrays, for each each inner michael@0: * array the first value is the key and the second is the value, e.g. michael@0: * [["key1", "value1"], ["key2", "value2"]]. michael@0: */ michael@0: function httpRequest(aUrl, aOptions) { michael@0: let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] michael@0: .createInstance(Ci.nsIXMLHttpRequest); michael@0: xhr.mozBackgroundRequest = true; // no error dialogs michael@0: xhr.open(aOptions.method || (aOptions.postData ? "POST" : "GET"), aUrl); michael@0: xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS | // don't send cookies michael@0: Ci.nsIChannel.LOAD_BYPASS_CACHE | michael@0: Ci.nsIChannel.INHIBIT_CACHING; michael@0: xhr.onerror = function(aProgressEvent) { michael@0: if (aOptions.onError) { michael@0: // adapted from toolkit/mozapps/extensions/nsBlocklistService.js michael@0: let request = aProgressEvent.target; michael@0: let status; michael@0: try { michael@0: // may throw (local file or timeout) michael@0: status = request.status; michael@0: } michael@0: catch (e) { michael@0: request = request.channel.QueryInterface(Ci.nsIRequest); michael@0: status = request.status; michael@0: } michael@0: // When status is 0 we don't have a valid channel. michael@0: let statusText = status ? request.statusText : "offline"; michael@0: aOptions.onError(statusText, null, this); michael@0: } michael@0: }; michael@0: xhr.onload = function(aRequest) { michael@0: try { michael@0: let target = aRequest.target; michael@0: if (aOptions.logger) michael@0: aOptions.logger.debug("Received response: " + target.responseText); michael@0: if (target.status < 200 || target.status >= 300) { michael@0: let errorText = target.responseText; michael@0: if (!errorText || /<(ht|\?x)ml\b/i.test(errorText)) michael@0: errorText = target.statusText; michael@0: throw target.status + " - " + errorText; michael@0: } michael@0: if (aOptions.onLoad) michael@0: aOptions.onLoad(target.responseText, this); michael@0: } catch (e) { michael@0: Cu.reportError(e); michael@0: if (aOptions.onError) michael@0: aOptions.onError(e, aRequest.target.responseText, this); michael@0: } michael@0: }; michael@0: michael@0: if (aOptions.headers) { michael@0: aOptions.headers.forEach(function(header) { michael@0: xhr.setRequestHeader(header[0], header[1]); michael@0: }); michael@0: } michael@0: michael@0: // Handle adding postData as defined above. michael@0: let POSTData = aOptions.postData || ""; michael@0: if (Array.isArray(POSTData)) { michael@0: xhr.setRequestHeader("Content-Type", michael@0: "application/x-www-form-urlencoded; charset=utf-8"); michael@0: POSTData = POSTData.map(function(p) p[0] + "=" + percentEncode(p[1])) michael@0: .join("&"); michael@0: } michael@0: michael@0: if (aOptions.logger) { michael@0: aOptions.logger.log("sending request to " + aUrl + " (POSTData = " + michael@0: POSTData + ")"); michael@0: } michael@0: xhr.send(POSTData); michael@0: return xhr; michael@0: }