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