summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/Http.sys.mjs
blob: faa487abe68d6bc0af1bb74e7571bdee909a7ba4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/* 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/. */

// Strictly follow RFC 3986 when encoding URI components.
// Accepts a unescaped string and returns the URI encoded string for use in
// an HTTP request.
export 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.sys.mjs).
 *
 * 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"]].
 */
export 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;
}