Blame toolkit/modules/Http.jsm

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