Source code

Revision control

Other Tools

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/. */
6
7
var EXPORTED_SYMBOLS = ["NetUtil"];
8
9
/**
10
* Necko utilities
11
*/
12
13
// //////////////////////////////////////////////////////////////////////////////
14
// // Constants
15
16
const PR_UINT32_MAX = 0xffffffff;
17
18
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
19
20
const BinaryInputStream = Components.Constructor(
21
"@mozilla.org/binaryinputstream;1",
22
"nsIBinaryInputStream",
23
"setInputStream"
24
);
25
26
// //////////////////////////////////////////////////////////////////////////////
27
// // NetUtil Object
28
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
}
56
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
);
69
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
}
81
82
// start the copying
83
copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
84
return copier;
85
},
86
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
}
114
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);
119
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
});
131
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
}
141
142
let channel = aSource;
143
if (!(channel instanceof Ci.nsIChannel)) {
144
channel = this.newChannel(aSource);
145
}
146
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
},
160
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
}
185
186
if (aTarget instanceof Ci.nsIFile) {
187
return Services.io.newFileURI(aTarget);
188
}
189
190
return Services.io.newURI(aTarget, aOriginCharset, aBaseURI);
191
},
192
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
}
234
235
let {
236
uri,
237
loadingNode,
238
loadingPrincipal,
239
loadUsingSystemPrincipal,
240
triggeringPrincipal,
241
securityFlags,
242
contentPolicyType,
243
} = aWhatToLoad;
244
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
}
252
253
if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
254
uri = this.newURI(uri);
255
}
256
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
}
266
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
}
286
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_DATA_IS_NULL;
297
}
298
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
}
310
311
return Services.io.newChannelFromURI(
312
uri,
313
loadingNode || null,
314
loadingPrincipal || null,
315
triggeringPrincipal || null,
316
securityFlags,
317
contentPolicyType
318
);
319
},
320
321
/**
322
* Reads aCount bytes from aInputStream into a string.
323
*
324
* @param aInputStream
325
* The input stream to read from.
326
* @param aCount
327
* The number of bytes to read from the stream.
328
* @param aOptions [optional]
329
* charset
330
* The character encoding of stream data.
331
* replacement
332
* The character to replace unknown byte sequences.
333
* If unset, it causes an exceptions to be thrown.
334
*
335
* @return the bytes from the input stream in string form.
336
*
337
* @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
338
* @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
339
* block the calling thread (non-blocking mode only).
340
* @throws NS_ERROR_FAILURE if there are not enough bytes available to read
341
* aCount amount of data.
342
* @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
343
*/
344
readInputStreamToString: function NetUtil_readInputStreamToString(
345
aInputStream,
346
aCount,
347
aOptions
348
) {
349
if (!(aInputStream instanceof Ci.nsIInputStream)) {
350
let exception = new Components.Exception(
351
"First argument should be an nsIInputStream",
352
Cr.NS_ERROR_INVALID_ARG,
353
Components.stack.caller
354
);
355
throw exception;
356
}
357
358
if (!aCount) {
359
let exception = new Components.Exception(
360
"Non-zero amount of bytes must be specified",
361
Cr.NS_ERROR_INVALID_ARG,
362
Components.stack.caller
363
);
364
throw exception;
365
}
366
367
if (aOptions && "charset" in aOptions) {
368
let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
369
Ci.nsIConverterInputStream
370
);
371
try {
372
// When replacement is set, the character that is unknown sequence
373
// replaces with aOptions.replacement character.
374
if (!("replacement" in aOptions)) {
375
// aOptions.replacement isn't set.
376
// If input stream has unknown sequences for aOptions.charset,
377
// throw NS_ERROR_ILLEGAL_INPUT.
378
aOptions.replacement = 0;
379
}
380
381
cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement);
382
let str = {};
383
cis.readString(-1, str);
384
cis.close();
385
return str.value;
386
} catch (e) {
387
// Adjust the stack so it throws at the caller's location.
388
throw new Components.Exception(
389
e.message,
390
e.result,
391
Components.stack.caller,
392
e.data
393
);
394
}
395
}
396
397
let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
398
Ci.nsIScriptableInputStream
399
);
400
sis.init(aInputStream);
401
try {
402
return sis.readBytes(aCount);
403
} catch (e) {
404
// Adjust the stack so it throws at the caller's location.
405
throw new Components.Exception(
406
e.message,
407
e.result,
408
Components.stack.caller,
409
e.data
410
);
411
}
412
},
413
414
/**
415
* Reads aCount bytes from aInputStream into a string.
416
*
417
* @param {nsIInputStream} aInputStream
418
* The input stream to read from.
419
* @param {integer} [aCount = aInputStream.available()]
420
* The number of bytes to read from the stream.
421
*
422
* @return the bytes from the input stream in string form.
423
*
424
* @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
425
* @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
426
* block the calling thread (non-blocking mode only).
427
* @throws NS_ERROR_FAILURE if there are not enough bytes available to read
428
* aCount amount of data.
429
*/
430
readInputStream(aInputStream, aCount) {
431
if (!(aInputStream instanceof Ci.nsIInputStream)) {
432
let exception = new Components.Exception(
433
"First argument should be an nsIInputStream",
434
Cr.NS_ERROR_INVALID_ARG,
435
Components.stack.caller
436
);
437
throw exception;
438
}
439
440
if (!aCount) {
441
aCount = aInputStream.available();
442
}
443
444
let stream = new BinaryInputStream(aInputStream);
445
let result = new ArrayBuffer(aCount);
446
stream.readArrayBuffer(result.byteLength, result);
447
return result;
448
},
449
};