Bug 1729952 [wpt PR 30477] - Fix timeout in grid-positioned-item-dynamic-change-006...
[gecko.git] / netwerk / base / NetUtil.jsm
blobf901b9805b9dcb14c7429c27c4aa390bc308006d
1 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
2  * vim: sw=4 ts=4 sts=4 et filetype=javascript
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 var EXPORTED_SYMBOLS = ["NetUtil"];
9 /**
10  * Necko utilities
11  */
13 // //////////////////////////////////////////////////////////////////////////////
14 // // Constants
16 const PR_UINT32_MAX = 0xffffffff;
18 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
20 const BinaryInputStream = Components.Constructor(
21   "@mozilla.org/binaryinputstream;1",
22   "nsIBinaryInputStream",
23   "setInputStream"
26 // //////////////////////////////////////////////////////////////////////////////
27 // // NetUtil Object
29 var NetUtil = {
30   /**
31    * Function to perform simple async copying from aSource (an input stream)
32    * to aSink (an output stream).  The copy will happen on some background
33    * thread.  Both streams will be closed when the copy completes.
34    *
35    * @param aSource
36    *        The input stream to read from
37    * @param aSink
38    *        The output stream to write to
39    * @param aCallback [optional]
40    *        A function that will be called at copy completion with a single
41    *        argument: the nsresult status code for the copy operation.
42    *
43    * @return An nsIRequest representing the copy operation (for example, this
44    *         can be used to cancel the copying).  The consumer can ignore the
45    *         return value if desired.
46    */
47   asyncCopy: function NetUtil_asyncCopy(aSource, aSink, aCallback = null) {
48     if (!aSource || !aSink) {
49       let exception = new Components.Exception(
50         "Must have a source and a sink",
51         Cr.NS_ERROR_INVALID_ARG,
52         Components.stack.caller
53       );
54       throw exception;
55     }
57     // make a stream copier
58     var copier = Cc[
59       "@mozilla.org/network/async-stream-copier;1"
60     ].createInstance(Ci.nsIAsyncStreamCopier2);
61     copier.init(
62       aSource,
63       aSink,
64       null /* Default event target */,
65       0 /* Default length */,
66       true,
67       true /* Auto-close */
68     );
70     var observer;
71     if (aCallback) {
72       observer = {
73         onStartRequest(aRequest) {},
74         onStopRequest(aRequest, aStatusCode) {
75           aCallback(aStatusCode);
76         },
77       };
78     } else {
79       observer = null;
80     }
82     // start the copying
83     copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
84     return copier;
85   },
87   /**
88    * Asynchronously opens a source and fetches the response.  While the fetch
89    * is asynchronous, I/O may happen on the main thread.  When reading from
90    * a local file, prefer using "OS.File" methods instead.
91    *
92    * @param aSource
93    *        This argument can be one of the following:
94    *         - An options object that will be passed to NetUtil.newChannel.
95    *         - An existing nsIChannel.
96    *         - An existing nsIInputStream.
97    *        Using an nsIURI, nsIFile, or string spec directly is deprecated.
98    * @param aCallback
99    *        The callback function that will be notified upon completion.  It
100    *        will get these arguments:
101    *        1) An nsIInputStream containing the data from aSource, if any.
102    *        2) The status code from opening the source.
103    *        3) Reference to the nsIRequest.
104    */
105   asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) {
106     if (!aSource || !aCallback) {
107       let exception = new Components.Exception(
108         "Must have a source and a callback",
109         Cr.NS_ERROR_INVALID_ARG,
110         Components.stack.caller
111       );
112       throw exception;
113     }
115     // Create a pipe that will create our output stream that we can use once
116     // we have gotten all the data.
117     let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
118     pipe.init(true, true, 0, PR_UINT32_MAX, null);
120     // Create a listener that will give data to the pipe's output stream.
121     let listener = Cc[
122       "@mozilla.org/network/simple-stream-listener;1"
123     ].createInstance(Ci.nsISimpleStreamListener);
124     listener.init(pipe.outputStream, {
125       onStartRequest(aRequest) {},
126       onStopRequest(aRequest, aStatusCode) {
127         pipe.outputStream.close();
128         aCallback(pipe.inputStream, aStatusCode, aRequest);
129       },
130     });
132     // Input streams are handled slightly differently from everything else.
133     if (aSource instanceof Ci.nsIInputStream) {
134       let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
135         Ci.nsIInputStreamPump
136       );
137       pump.init(aSource, 0, 0, true);
138       pump.asyncRead(listener, null);
139       return;
140     }
142     let channel = aSource;
143     if (!(channel instanceof Ci.nsIChannel)) {
144       channel = this.newChannel(aSource);
145     }
147     try {
148       channel.asyncOpen(listener);
149     } catch (e) {
150       let exception = new Components.Exception(
151         "Failed to open input source '" + channel.originalURI.spec + "'",
152         e.result,
153         Components.stack.caller,
154         aSource,
155         e
156       );
157       throw exception;
158     }
159   },
161   /**
162    * Constructs a new URI for the given spec, character set, and base URI, or
163    * an nsIFile.
164    *
165    * @param aTarget
166    *        The string spec for the desired URI or an nsIFile.
167    * @param aOriginCharset [optional]
168    *        The character set for the URI.  Only used if aTarget is not an
169    *        nsIFile.
170    * @param aBaseURI [optional]
171    *        The base URI for the spec.  Only used if aTarget is not an
172    *        nsIFile.
173    *
174    * @return an nsIURI object.
175    */
176   newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) {
177     if (!aTarget) {
178       let exception = new Components.Exception(
179         "Must have a non-null string spec or nsIFile object",
180         Cr.NS_ERROR_INVALID_ARG,
181         Components.stack.caller
182       );
183       throw exception;
184     }
186     if (aTarget instanceof Ci.nsIFile) {
187       return Services.io.newFileURI(aTarget);
188     }
190     return Services.io.newURI(aTarget, aOriginCharset, aBaseURI);
191   },
193   /**
194    * Constructs a new channel for the given source.
195    *
196    * Keep in mind that URIs coming from a webpage should *never* use the
197    * systemPrincipal as the loadingPrincipal.
198    *
199    * @param aWhatToLoad
200    *        This argument used to be a string spec for the desired URI, an
201    *        nsIURI, or an nsIFile.  Now it should be an options object with
202    *        the following properties:
203    *        {
204    *          uri:
205    *            The full URI spec string, nsIURI or nsIFile to create the
206    *            channel for.
207    *            Note that this cannot be an nsIFile if you have to specify a
208    *            non-default charset or base URI.  Call NetUtil.newURI first if
209    *            you need to construct an URI using those options.
210    *          loadingNode:
211    *          loadingPrincipal:
212    *          triggeringPrincipal:
213    *          securityFlags:
214    *          contentPolicyType:
215    *            These will be used as values for the nsILoadInfo object on the
216    *            created channel. For details, see nsILoadInfo in nsILoadInfo.idl
217    *          loadUsingSystemPrincipal:
218    *            Set this to true to use the system principal as
219    *            loadingPrincipal.  This must be omitted if loadingPrincipal or
220    *            loadingNode are present.
221    *            This should be used with care as it skips security checks.
222    *        }
223    * @return an nsIChannel object.
224    */
225   newChannel: function NetUtil_newChannel(aWhatToLoad) {
226     // Make sure the API is called using only the options object.
227     if (typeof aWhatToLoad != "object" || arguments.length != 1) {
228       throw new Components.Exception(
229         "newChannel requires a single object argument",
230         Cr.NS_ERROR_INVALID_ARG,
231         Components.stack.caller
232       );
233     }
235     let {
236       uri,
237       loadingNode,
238       loadingPrincipal,
239       loadUsingSystemPrincipal,
240       triggeringPrincipal,
241       securityFlags,
242       contentPolicyType,
243     } = aWhatToLoad;
245     if (!uri) {
246       throw new Components.Exception(
247         "newChannel requires the 'uri' property on the options object.",
248         Cr.NS_ERROR_INVALID_ARG,
249         Components.stack.caller
250       );
251     }
253     if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
254       uri = this.newURI(uri);
255     }
257     if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
258       throw new Components.Exception(
259         "newChannel requires at least one of the 'loadingNode'," +
260           " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
261           " properties on the options object.",
262         Cr.NS_ERROR_INVALID_ARG,
263         Components.stack.caller
264       );
265     }
267     if (loadUsingSystemPrincipal === true) {
268       if (loadingNode || loadingPrincipal) {
269         throw new Components.Exception(
270           "newChannel does not accept 'loadUsingSystemPrincipal'" +
271             " if the 'loadingNode' or 'loadingPrincipal' properties" +
272             " are present on the options object.",
273           Cr.NS_ERROR_INVALID_ARG,
274           Components.stack.caller
275         );
276       }
277       loadingPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
278     } else if (loadUsingSystemPrincipal !== undefined) {
279       throw new Components.Exception(
280         "newChannel requires the 'loadUsingSystemPrincipal'" +
281           " property on the options object to be 'true' or 'undefined'.",
282         Cr.NS_ERROR_INVALID_ARG,
283         Components.stack.caller
284       );
285     }
287     if (securityFlags === undefined) {
288       if (!loadUsingSystemPrincipal) {
289         throw new Components.Exception(
290           "newChannel requires the 'securityFlags' property on" +
291             " the options object unless loading from system principal.",
292           Cr.NS_ERROR_INVALID_ARG,
293           Components.stack.caller
294         );
295       }
296       securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
297     }
299     if (contentPolicyType === undefined) {
300       if (!loadUsingSystemPrincipal) {
301         throw new Components.Exception(
302           "newChannel requires the 'contentPolicyType' property on" +
303             " the options object unless loading from system principal.",
304           Cr.NS_ERROR_INVALID_ARG,
305           Components.stack.caller
306         );
307       }
308       contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
309     }
311     let channel = Services.io.newChannelFromURI(
312       uri,
313       loadingNode || null,
314       loadingPrincipal || null,
315       triggeringPrincipal || null,
316       securityFlags,
317       contentPolicyType
318     );
319     if (loadUsingSystemPrincipal) {
320       channel.loadInfo.allowDeprecatedSystemRequests = true;
321     }
322     return channel;
323   },
325   /**
326    * Reads aCount bytes from aInputStream into a string.
327    *
328    * @param aInputStream
329    *        The input stream to read from.
330    * @param aCount
331    *        The number of bytes to read from the stream.
332    * @param aOptions [optional]
333    *        charset
334    *          The character encoding of stream data.
335    *        replacement
336    *          The character to replace unknown byte sequences.
337    *          If unset, it causes an exceptions to be thrown.
338    *
339    * @return the bytes from the input stream in string form.
340    *
341    * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
342    * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
343    *         block the calling thread (non-blocking mode only).
344    * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
345    *         aCount amount of data.
346    * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
347    */
348   readInputStreamToString: function NetUtil_readInputStreamToString(
349     aInputStream,
350     aCount,
351     aOptions
352   ) {
353     if (!(aInputStream instanceof Ci.nsIInputStream)) {
354       let exception = new Components.Exception(
355         "First argument should be an nsIInputStream",
356         Cr.NS_ERROR_INVALID_ARG,
357         Components.stack.caller
358       );
359       throw exception;
360     }
362     if (!aCount) {
363       let exception = new Components.Exception(
364         "Non-zero amount of bytes must be specified",
365         Cr.NS_ERROR_INVALID_ARG,
366         Components.stack.caller
367       );
368       throw exception;
369     }
371     if (aOptions && "charset" in aOptions) {
372       let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
373         Ci.nsIConverterInputStream
374       );
375       try {
376         // When replacement is set, the character that is unknown sequence
377         // replaces with aOptions.replacement character.
378         if (!("replacement" in aOptions)) {
379           // aOptions.replacement isn't set.
380           // If input stream has unknown sequences for aOptions.charset,
381           // throw NS_ERROR_ILLEGAL_INPUT.
382           aOptions.replacement = 0;
383         }
385         cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement);
386         let str = {};
387         cis.readString(-1, str);
388         cis.close();
389         return str.value;
390       } catch (e) {
391         // Adjust the stack so it throws at the caller's location.
392         throw new Components.Exception(
393           e.message,
394           e.result,
395           Components.stack.caller,
396           e.data
397         );
398       }
399     }
401     let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
402       Ci.nsIScriptableInputStream
403     );
404     sis.init(aInputStream);
405     try {
406       return sis.readBytes(aCount);
407     } catch (e) {
408       // Adjust the stack so it throws at the caller's location.
409       throw new Components.Exception(
410         e.message,
411         e.result,
412         Components.stack.caller,
413         e.data
414       );
415     }
416   },
418   /**
419    * Reads aCount bytes from aInputStream into a string.
420    *
421    * @param {nsIInputStream} aInputStream
422    *        The input stream to read from.
423    * @param {integer} [aCount = aInputStream.available()]
424    *        The number of bytes to read from the stream.
425    *
426    * @return the bytes from the input stream in string form.
427    *
428    * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
429    * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
430    *         block the calling thread (non-blocking mode only).
431    * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
432    *         aCount amount of data.
433    */
434   readInputStream(aInputStream, aCount) {
435     if (!(aInputStream instanceof Ci.nsIInputStream)) {
436       let exception = new Components.Exception(
437         "First argument should be an nsIInputStream",
438         Cr.NS_ERROR_INVALID_ARG,
439         Components.stack.caller
440       );
441       throw exception;
442     }
444     if (!aCount) {
445       aCount = aInputStream.available();
446     }
448     let stream = new BinaryInputStream(aInputStream);
449     let result = new ArrayBuffer(aCount);
450     stream.readArrayBuffer(result.byteLength, result);
451     return result;
452   },