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