| |
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
| |
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
| |
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
4 |
| |
5 const EXPORTED_SYMBOLS = ["httpRequest", "percentEncode"]; |
| |
6 |
| |
7 const {classes: Cc, interfaces: Ci, utils: Cu} = Components; |
| |
8 |
| |
9 // Strictly follow RFC 3986 when encoding URI components. |
| |
10 // Accepts a unescaped string and returns the URI encoded string for use in |
| |
11 // an HTTP request. |
| |
12 function percentEncode(aString) |
| |
13 encodeURIComponent(aString).replace(/[!'()]/g, escape).replace(/\*/g, "%2A"); |
| |
14 |
| |
15 /* |
| |
16 * aOptions can have a variety of fields: |
| |
17 * headers, an array of headers |
| |
18 * postData, this can be: |
| |
19 * a string: send it as is |
| |
20 * an array of parameters: encode as form values |
| |
21 * null/undefined: no POST data. |
| |
22 * method, GET, POST or PUT (this is set automatically if postData exists). |
| |
23 * onLoad, a function handle to call when the load is complete, it takes two |
| |
24 * parameters: the responseText and the XHR object. |
| |
25 * onError, a function handle to call when an error occcurs, it takes three |
| |
26 * parameters: the error, the responseText and the XHR object. |
| |
27 * logger, an object that implements the debug and log methods (e.g. log.jsm). |
| |
28 * |
| |
29 * Headers or post data are given as an array of arrays, for each each inner |
| |
30 * array the first value is the key and the second is the value, e.g. |
| |
31 * [["key1", "value1"], ["key2", "value2"]]. |
| |
32 */ |
| |
33 function httpRequest(aUrl, aOptions) { |
| |
34 let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] |
| |
35 .createInstance(Ci.nsIXMLHttpRequest); |
| |
36 xhr.mozBackgroundRequest = true; // no error dialogs |
| |
37 xhr.open(aOptions.method || (aOptions.postData ? "POST" : "GET"), aUrl); |
| |
38 xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS | // don't send cookies |
| |
39 Ci.nsIChannel.LOAD_BYPASS_CACHE | |
| |
40 Ci.nsIChannel.INHIBIT_CACHING; |
| |
41 xhr.onerror = function(aProgressEvent) { |
| |
42 if (aOptions.onError) { |
| |
43 // adapted from toolkit/mozapps/extensions/nsBlocklistService.js |
| |
44 let request = aProgressEvent.target; |
| |
45 let status; |
| |
46 try { |
| |
47 // may throw (local file or timeout) |
| |
48 status = request.status; |
| |
49 } |
| |
50 catch (e) { |
| |
51 request = request.channel.QueryInterface(Ci.nsIRequest); |
| |
52 status = request.status; |
| |
53 } |
| |
54 // When status is 0 we don't have a valid channel. |
| |
55 let statusText = status ? request.statusText : "offline"; |
| |
56 aOptions.onError(statusText, null, this); |
| |
57 } |
| |
58 }; |
| |
59 xhr.onload = function(aRequest) { |
| |
60 try { |
| |
61 let target = aRequest.target; |
| |
62 if (aOptions.logger) |
| |
63 aOptions.logger.debug("Received response: " + target.responseText); |
| |
64 if (target.status < 200 || target.status >= 300) { |
| |
65 let errorText = target.responseText; |
| |
66 if (!errorText || /<(ht|\?x)ml\b/i.test(errorText)) |
| |
67 errorText = target.statusText; |
| |
68 throw target.status + " - " + errorText; |
| |
69 } |
| |
70 if (aOptions.onLoad) |
| |
71 aOptions.onLoad(target.responseText, this); |
| |
72 } catch (e) { |
| |
73 Cu.reportError(e); |
| |
74 if (aOptions.onError) |
| |
75 aOptions.onError(e, aRequest.target.responseText, this); |
| |
76 } |
| |
77 }; |
| |
78 |
| |
79 if (aOptions.headers) { |
| |
80 aOptions.headers.forEach(function(header) { |
| |
81 xhr.setRequestHeader(header[0], header[1]); |
| |
82 }); |
| |
83 } |
| |
84 |
| |
85 // Handle adding postData as defined above. |
| |
86 let POSTData = aOptions.postData || ""; |
| |
87 if (Array.isArray(POSTData)) { |
| |
88 xhr.setRequestHeader("Content-Type", |
| |
89 "application/x-www-form-urlencoded; charset=utf-8"); |
| |
90 POSTData = POSTData.map(function(p) p[0] + "=" + percentEncode(p[1])) |
| |
91 .join("&"); |
| |
92 } |
| |
93 |
| |
94 if (aOptions.logger) { |
| |
95 aOptions.logger.log("sending request to " + aUrl + " (POSTData = " + |
| |
96 POSTData + ")"); |
| |
97 } |
| |
98 xhr.send(POSTData); |
| |
99 return xhr; |
| |
100 } |