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"];
13 // //////////////////////////////////////////////////////////////////////////////
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",
26 // //////////////////////////////////////////////////////////////////////////////
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.
36 * The input stream to read from
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.
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.
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
57 // make a stream copier
59 "@mozilla.org/network/async-stream-copier;1"
60 ].createInstance(Ci.nsIAsyncStreamCopier2);
64 null /* Default event target */,
65 0 /* Default length */,
73 onStartRequest(aRequest) {},
74 onStopRequest(aRequest, aStatusCode) {
75 aCallback(aStatusCode);
83 copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
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.
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.
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.
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
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.
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);
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
137 pump.init(aSource, 0, 0, true);
138 pump.asyncRead(listener, null);
142 let channel = aSource;
143 if (!(channel instanceof Ci.nsIChannel)) {
144 channel = this.newChannel(aSource);
148 channel.asyncOpen(listener);
150 let exception = new Components.Exception(
151 "Failed to open input source '" + channel.originalURI.spec + "'",
153 Components.stack.caller,
162 * Constructs a new URI for the given spec, character set, and base URI, or
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
170 * @param aBaseURI [optional]
171 * The base URI for the spec. Only used if aTarget is not an
174 * @return an nsIURI object.
176 newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) {
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
186 if (aTarget instanceof Ci.nsIFile) {
187 return Services.io.newFileURI(aTarget);
190 return Services.io.newURI(aTarget, aOriginCharset, aBaseURI);
194 * Constructs a new channel for the given source.
196 * Keep in mind that URIs coming from a webpage should *never* use the
197 * systemPrincipal as the loadingPrincipal.
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:
205 * The full URI spec string, nsIURI or nsIFile to create the
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.
212 * triggeringPrincipal:
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.
223 * @return an nsIChannel object.
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
239 loadUsingSystemPrincipal,
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
253 if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
254 uri = this.newURI(uri);
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
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
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
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
296 securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
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
308 contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
311 let channel = Services.io.newChannelFromURI(
314 loadingPrincipal || null,
315 triggeringPrincipal || null,
319 if (loadUsingSystemPrincipal) {
320 channel.loadInfo.allowDeprecatedSystemRequests = true;
326 * Reads aCount bytes from aInputStream into a string.
328 * @param aInputStream
329 * The input stream to read from.
331 * The number of bytes to read from the stream.
332 * @param aOptions [optional]
334 * The character encoding of stream data.
336 * The character to replace unknown byte sequences.
337 * If unset, it causes an exceptions to be thrown.
339 * @return the bytes from the input stream in string form.
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
348 readInputStreamToString: function NetUtil_readInputStreamToString(
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
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
371 if (aOptions && "charset" in aOptions) {
372 let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
373 Ci.nsIConverterInputStream
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;
385 cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement);
387 cis.readString(-1, str);
391 // Adjust the stack so it throws at the caller's location.
392 throw new Components.Exception(
395 Components.stack.caller,
401 let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
402 Ci.nsIScriptableInputStream
404 sis.init(aInputStream);
406 return sis.readBytes(aCount);
408 // Adjust the stack so it throws at the caller's location.
409 throw new Components.Exception(
412 Components.stack.caller,
419 * Reads aCount bytes from aInputStream into a string.
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.
426 * @return the bytes from the input stream in string form.
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.
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
445 aCount = aInputStream.available();
448 let stream = new BinaryInputStream(aInputStream);
449 let result = new ArrayBuffer(aCount);
450 stream.readArrayBuffer(result.byteLength, result);