|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 this.EXPORTED_SYMBOLS = [ |
|
8 "NetUtil", |
|
9 ]; |
|
10 |
|
11 /** |
|
12 * Necko utilities |
|
13 */ |
|
14 |
|
15 //////////////////////////////////////////////////////////////////////////////// |
|
16 //// Constants |
|
17 |
|
18 const Ci = Components.interfaces; |
|
19 const Cc = Components.classes; |
|
20 const Cr = Components.results; |
|
21 const Cu = Components.utils; |
|
22 |
|
23 const PR_UINT32_MAX = 0xffffffff; |
|
24 |
|
25 //////////////////////////////////////////////////////////////////////////////// |
|
26 //// NetUtil Object |
|
27 |
|
28 this.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 { |
|
49 if (!aSource || !aSink) { |
|
50 let exception = new Components.Exception( |
|
51 "Must have a source and a sink", |
|
52 Cr.NS_ERROR_INVALID_ARG, |
|
53 Components.stack.caller |
|
54 ); |
|
55 throw exception; |
|
56 } |
|
57 |
|
58 // make a stream copier |
|
59 var copier = Cc["@mozilla.org/network/async-stream-copier;1"]. |
|
60 createInstance(Ci.nsIAsyncStreamCopier2); |
|
61 copier.init(aSource, aSink, |
|
62 null /* Default event target */, |
|
63 0 /* Default length */, |
|
64 true, true /* Auto-close */); |
|
65 |
|
66 var observer; |
|
67 if (aCallback) { |
|
68 observer = { |
|
69 onStartRequest: function(aRequest, aContext) {}, |
|
70 onStopRequest: function(aRequest, aContext, aStatusCode) { |
|
71 aCallback(aStatusCode); |
|
72 } |
|
73 } |
|
74 } else { |
|
75 observer = null; |
|
76 } |
|
77 |
|
78 // start the copying |
|
79 copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null); |
|
80 return copier; |
|
81 }, |
|
82 |
|
83 /** |
|
84 * Asynchronously opens a source and fetches the response. A source can be |
|
85 * an nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream. The |
|
86 * provided callback will get an input stream containing the response, the |
|
87 * result code, and a reference to the request. |
|
88 * |
|
89 * @param aSource |
|
90 * The nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream |
|
91 * to open. |
|
92 * @param aCallback |
|
93 * The callback function that will be notified upon completion. It |
|
94 * will get two arguments: |
|
95 * 1) An nsIInputStream containing the data from aSource, if any. |
|
96 * 2) The status code from opening the source. |
|
97 * 3) Reference to the nsIRequest. |
|
98 */ |
|
99 asyncFetch: function NetUtil_asyncOpen(aSource, aCallback) |
|
100 { |
|
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: function(aRequest, aContext) {}, |
|
121 onStopRequest: function(aRequest, aContext, 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, -1, -1, 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, null); |
|
143 } |
|
144 catch (e) { |
|
145 let exception = new Components.Exception( |
|
146 "Failed to open input source '" + channel.originalURI.spec + "'", |
|
147 e.result, |
|
148 Components.stack.caller, |
|
149 aSource, |
|
150 e |
|
151 ); |
|
152 throw exception; |
|
153 } |
|
154 }, |
|
155 |
|
156 /** |
|
157 * Constructs a new URI for the given spec, character set, and base URI, or |
|
158 * an nsIFile. |
|
159 * |
|
160 * @param aTarget |
|
161 * The string spec for the desired URI or an nsIFile. |
|
162 * @param aOriginCharset [optional] |
|
163 * The character set for the URI. Only used if aTarget is not an |
|
164 * nsIFile. |
|
165 * @param aBaseURI [optional] |
|
166 * The base URI for the spec. Only used if aTarget is not an |
|
167 * nsIFile. |
|
168 * |
|
169 * @return an nsIURI object. |
|
170 */ |
|
171 newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) |
|
172 { |
|
173 if (!aTarget) { |
|
174 let exception = new Components.Exception( |
|
175 "Must have a non-null string spec or nsIFile object", |
|
176 Cr.NS_ERROR_INVALID_ARG, |
|
177 Components.stack.caller |
|
178 ); |
|
179 throw exception; |
|
180 } |
|
181 |
|
182 if (aTarget instanceof Ci.nsIFile) { |
|
183 return this.ioService.newFileURI(aTarget); |
|
184 } |
|
185 |
|
186 return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI); |
|
187 }, |
|
188 |
|
189 /** |
|
190 * Constructs a new channel for the given spec, character set, and base URI, |
|
191 * or nsIURI, or nsIFile. |
|
192 * |
|
193 * @param aWhatToLoad |
|
194 * The string spec for the desired URI, an nsIURI, or an nsIFile. |
|
195 * @param aOriginCharset [optional] |
|
196 * The character set for the URI. Only used if aWhatToLoad is a |
|
197 * string. |
|
198 * @param aBaseURI [optional] |
|
199 * The base URI for the spec. Only used if aWhatToLoad is a string. |
|
200 * |
|
201 * @return an nsIChannel object. |
|
202 */ |
|
203 newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, |
|
204 aBaseURI) |
|
205 { |
|
206 if (!aWhatToLoad) { |
|
207 let exception = new Components.Exception( |
|
208 "Must have a non-null string spec, nsIURI, or nsIFile object", |
|
209 Cr.NS_ERROR_INVALID_ARG, |
|
210 Components.stack.caller |
|
211 ); |
|
212 throw exception; |
|
213 } |
|
214 |
|
215 let uri = aWhatToLoad; |
|
216 if (!(aWhatToLoad instanceof Ci.nsIURI)) { |
|
217 // We either have a string or an nsIFile that we'll need a URI for. |
|
218 uri = this.newURI(aWhatToLoad, aOriginCharset, aBaseURI); |
|
219 } |
|
220 |
|
221 return this.ioService.newChannelFromURI(uri); |
|
222 }, |
|
223 |
|
224 /** |
|
225 * Reads aCount bytes from aInputStream into a string. |
|
226 * |
|
227 * @param aInputStream |
|
228 * The input stream to read from. |
|
229 * @param aCount |
|
230 * The number of bytes to read from the stream. |
|
231 * @param aOptions [optional] |
|
232 * charset |
|
233 * The character encoding of stream data. |
|
234 * replacement |
|
235 * The character to replace unknown byte sequences. |
|
236 * If unset, it causes an exceptions to be thrown. |
|
237 * |
|
238 * @return the bytes from the input stream in string form. |
|
239 * |
|
240 * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. |
|
241 * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would |
|
242 * block the calling thread (non-blocking mode only). |
|
243 * @throws NS_ERROR_FAILURE if there are not enough bytes available to read |
|
244 * aCount amount of data. |
|
245 * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences |
|
246 */ |
|
247 readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream, |
|
248 aCount, |
|
249 aOptions) |
|
250 { |
|
251 if (!(aInputStream instanceof Ci.nsIInputStream)) { |
|
252 let exception = new Components.Exception( |
|
253 "First argument should be an nsIInputStream", |
|
254 Cr.NS_ERROR_INVALID_ARG, |
|
255 Components.stack.caller |
|
256 ); |
|
257 throw exception; |
|
258 } |
|
259 |
|
260 if (!aCount) { |
|
261 let exception = new Components.Exception( |
|
262 "Non-zero amount of bytes must be specified", |
|
263 Cr.NS_ERROR_INVALID_ARG, |
|
264 Components.stack.caller |
|
265 ); |
|
266 throw exception; |
|
267 } |
|
268 |
|
269 if (aOptions && "charset" in aOptions) { |
|
270 let cis = Cc["@mozilla.org/intl/converter-input-stream;1"]. |
|
271 createInstance(Ci.nsIConverterInputStream); |
|
272 try { |
|
273 // When replacement is set, the character that is unknown sequence |
|
274 // replaces with aOptions.replacement character. |
|
275 if (!("replacement" in aOptions)) { |
|
276 // aOptions.replacement isn't set. |
|
277 // If input stream has unknown sequences for aOptions.charset, |
|
278 // throw NS_ERROR_ILLEGAL_INPUT. |
|
279 aOptions.replacement = 0; |
|
280 } |
|
281 |
|
282 cis.init(aInputStream, aOptions.charset, aCount, |
|
283 aOptions.replacement); |
|
284 let str = {}; |
|
285 cis.readString(-1, str); |
|
286 cis.close(); |
|
287 return str.value; |
|
288 } |
|
289 catch (e) { |
|
290 // Adjust the stack so it throws at the caller's location. |
|
291 throw new Components.Exception(e.message, e.result, |
|
292 Components.stack.caller, e.data); |
|
293 } |
|
294 } |
|
295 |
|
296 let sis = Cc["@mozilla.org/scriptableinputstream;1"]. |
|
297 createInstance(Ci.nsIScriptableInputStream); |
|
298 sis.init(aInputStream); |
|
299 try { |
|
300 return sis.readBytes(aCount); |
|
301 } |
|
302 catch (e) { |
|
303 // Adjust the stack so it throws at the caller's location. |
|
304 throw new Components.Exception(e.message, e.result, |
|
305 Components.stack.caller, e.data); |
|
306 } |
|
307 }, |
|
308 |
|
309 /** |
|
310 * Returns a reference to nsIIOService. |
|
311 * |
|
312 * @return a reference to nsIIOService. |
|
313 */ |
|
314 get ioService() |
|
315 { |
|
316 delete this.ioService; |
|
317 return this.ioService = Cc["@mozilla.org/network/io-service;1"]. |
|
318 getService(Ci.nsIIOService); |
|
319 }, |
|
320 }; |
|
321 |
|
322 //////////////////////////////////////////////////////////////////////////////// |
|
323 //// Initialization |
|
324 |
|
325 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
326 |
|
327 // Define our lazy getters. |
|
328 XPCOMUtils.defineLazyServiceGetter(this, "ioUtil", "@mozilla.org/io-util;1", |
|
329 "nsIIOUtil"); |