Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
michael@0 | 2 | * vim: sw=4 ts=4 sts=4 et filetype=javascript |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | this.EXPORTED_SYMBOLS = [ |
michael@0 | 8 | "NetUtil", |
michael@0 | 9 | ]; |
michael@0 | 10 | |
michael@0 | 11 | /** |
michael@0 | 12 | * Necko utilities |
michael@0 | 13 | */ |
michael@0 | 14 | |
michael@0 | 15 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 16 | //// Constants |
michael@0 | 17 | |
michael@0 | 18 | const Ci = Components.interfaces; |
michael@0 | 19 | const Cc = Components.classes; |
michael@0 | 20 | const Cr = Components.results; |
michael@0 | 21 | const Cu = Components.utils; |
michael@0 | 22 | |
michael@0 | 23 | const PR_UINT32_MAX = 0xffffffff; |
michael@0 | 24 | |
michael@0 | 25 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 26 | //// NetUtil Object |
michael@0 | 27 | |
michael@0 | 28 | this.NetUtil = { |
michael@0 | 29 | /** |
michael@0 | 30 | * Function to perform simple async copying from aSource (an input stream) |
michael@0 | 31 | * to aSink (an output stream). The copy will happen on some background |
michael@0 | 32 | * thread. Both streams will be closed when the copy completes. |
michael@0 | 33 | * |
michael@0 | 34 | * @param aSource |
michael@0 | 35 | * The input stream to read from |
michael@0 | 36 | * @param aSink |
michael@0 | 37 | * The output stream to write to |
michael@0 | 38 | * @param aCallback [optional] |
michael@0 | 39 | * A function that will be called at copy completion with a single |
michael@0 | 40 | * argument: the nsresult status code for the copy operation. |
michael@0 | 41 | * |
michael@0 | 42 | * @return An nsIRequest representing the copy operation (for example, this |
michael@0 | 43 | * can be used to cancel the copying). The consumer can ignore the |
michael@0 | 44 | * return value if desired. |
michael@0 | 45 | */ |
michael@0 | 46 | asyncCopy: function NetUtil_asyncCopy(aSource, aSink, |
michael@0 | 47 | aCallback = null) |
michael@0 | 48 | { |
michael@0 | 49 | if (!aSource || !aSink) { |
michael@0 | 50 | let exception = new Components.Exception( |
michael@0 | 51 | "Must have a source and a sink", |
michael@0 | 52 | Cr.NS_ERROR_INVALID_ARG, |
michael@0 | 53 | Components.stack.caller |
michael@0 | 54 | ); |
michael@0 | 55 | throw exception; |
michael@0 | 56 | } |
michael@0 | 57 | |
michael@0 | 58 | // make a stream copier |
michael@0 | 59 | var copier = Cc["@mozilla.org/network/async-stream-copier;1"]. |
michael@0 | 60 | createInstance(Ci.nsIAsyncStreamCopier2); |
michael@0 | 61 | copier.init(aSource, aSink, |
michael@0 | 62 | null /* Default event target */, |
michael@0 | 63 | 0 /* Default length */, |
michael@0 | 64 | true, true /* Auto-close */); |
michael@0 | 65 | |
michael@0 | 66 | var observer; |
michael@0 | 67 | if (aCallback) { |
michael@0 | 68 | observer = { |
michael@0 | 69 | onStartRequest: function(aRequest, aContext) {}, |
michael@0 | 70 | onStopRequest: function(aRequest, aContext, aStatusCode) { |
michael@0 | 71 | aCallback(aStatusCode); |
michael@0 | 72 | } |
michael@0 | 73 | } |
michael@0 | 74 | } else { |
michael@0 | 75 | observer = null; |
michael@0 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | // start the copying |
michael@0 | 79 | copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null); |
michael@0 | 80 | return copier; |
michael@0 | 81 | }, |
michael@0 | 82 | |
michael@0 | 83 | /** |
michael@0 | 84 | * Asynchronously opens a source and fetches the response. A source can be |
michael@0 | 85 | * an nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream. The |
michael@0 | 86 | * provided callback will get an input stream containing the response, the |
michael@0 | 87 | * result code, and a reference to the request. |
michael@0 | 88 | * |
michael@0 | 89 | * @param aSource |
michael@0 | 90 | * The nsIURI, nsIFile, string spec, nsIChannel, or nsIInputStream |
michael@0 | 91 | * to open. |
michael@0 | 92 | * @param aCallback |
michael@0 | 93 | * The callback function that will be notified upon completion. It |
michael@0 | 94 | * will get two arguments: |
michael@0 | 95 | * 1) An nsIInputStream containing the data from aSource, if any. |
michael@0 | 96 | * 2) The status code from opening the source. |
michael@0 | 97 | * 3) Reference to the nsIRequest. |
michael@0 | 98 | */ |
michael@0 | 99 | asyncFetch: function NetUtil_asyncOpen(aSource, aCallback) |
michael@0 | 100 | { |
michael@0 | 101 | if (!aSource || !aCallback) { |
michael@0 | 102 | let exception = new Components.Exception( |
michael@0 | 103 | "Must have a source and a callback", |
michael@0 | 104 | Cr.NS_ERROR_INVALID_ARG, |
michael@0 | 105 | Components.stack.caller |
michael@0 | 106 | ); |
michael@0 | 107 | throw exception; |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | // Create a pipe that will create our output stream that we can use once |
michael@0 | 111 | // we have gotten all the data. |
michael@0 | 112 | let pipe = Cc["@mozilla.org/pipe;1"]. |
michael@0 | 113 | createInstance(Ci.nsIPipe); |
michael@0 | 114 | pipe.init(true, true, 0, PR_UINT32_MAX, null); |
michael@0 | 115 | |
michael@0 | 116 | // Create a listener that will give data to the pipe's output stream. |
michael@0 | 117 | let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]. |
michael@0 | 118 | createInstance(Ci.nsISimpleStreamListener); |
michael@0 | 119 | listener.init(pipe.outputStream, { |
michael@0 | 120 | onStartRequest: function(aRequest, aContext) {}, |
michael@0 | 121 | onStopRequest: function(aRequest, aContext, aStatusCode) { |
michael@0 | 122 | pipe.outputStream.close(); |
michael@0 | 123 | aCallback(pipe.inputStream, aStatusCode, aRequest); |
michael@0 | 124 | } |
michael@0 | 125 | }); |
michael@0 | 126 | |
michael@0 | 127 | // Input streams are handled slightly differently from everything else. |
michael@0 | 128 | if (aSource instanceof Ci.nsIInputStream) { |
michael@0 | 129 | let pump = Cc["@mozilla.org/network/input-stream-pump;1"]. |
michael@0 | 130 | createInstance(Ci.nsIInputStreamPump); |
michael@0 | 131 | pump.init(aSource, -1, -1, 0, 0, true); |
michael@0 | 132 | pump.asyncRead(listener, null); |
michael@0 | 133 | return; |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | let channel = aSource; |
michael@0 | 137 | if (!(channel instanceof Ci.nsIChannel)) { |
michael@0 | 138 | channel = this.newChannel(aSource); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | try { |
michael@0 | 142 | channel.asyncOpen(listener, null); |
michael@0 | 143 | } |
michael@0 | 144 | catch (e) { |
michael@0 | 145 | let exception = new Components.Exception( |
michael@0 | 146 | "Failed to open input source '" + channel.originalURI.spec + "'", |
michael@0 | 147 | e.result, |
michael@0 | 148 | Components.stack.caller, |
michael@0 | 149 | aSource, |
michael@0 | 150 | e |
michael@0 | 151 | ); |
michael@0 | 152 | throw exception; |
michael@0 | 153 | } |
michael@0 | 154 | }, |
michael@0 | 155 | |
michael@0 | 156 | /** |
michael@0 | 157 | * Constructs a new URI for the given spec, character set, and base URI, or |
michael@0 | 158 | * an nsIFile. |
michael@0 | 159 | * |
michael@0 | 160 | * @param aTarget |
michael@0 | 161 | * The string spec for the desired URI or an nsIFile. |
michael@0 | 162 | * @param aOriginCharset [optional] |
michael@0 | 163 | * The character set for the URI. Only used if aTarget is not an |
michael@0 | 164 | * nsIFile. |
michael@0 | 165 | * @param aBaseURI [optional] |
michael@0 | 166 | * The base URI for the spec. Only used if aTarget is not an |
michael@0 | 167 | * nsIFile. |
michael@0 | 168 | * |
michael@0 | 169 | * @return an nsIURI object. |
michael@0 | 170 | */ |
michael@0 | 171 | newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) |
michael@0 | 172 | { |
michael@0 | 173 | if (!aTarget) { |
michael@0 | 174 | let exception = new Components.Exception( |
michael@0 | 175 | "Must have a non-null string spec or nsIFile object", |
michael@0 | 176 | Cr.NS_ERROR_INVALID_ARG, |
michael@0 | 177 | Components.stack.caller |
michael@0 | 178 | ); |
michael@0 | 179 | throw exception; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | if (aTarget instanceof Ci.nsIFile) { |
michael@0 | 183 | return this.ioService.newFileURI(aTarget); |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | return this.ioService.newURI(aTarget, aOriginCharset, aBaseURI); |
michael@0 | 187 | }, |
michael@0 | 188 | |
michael@0 | 189 | /** |
michael@0 | 190 | * Constructs a new channel for the given spec, character set, and base URI, |
michael@0 | 191 | * or nsIURI, or nsIFile. |
michael@0 | 192 | * |
michael@0 | 193 | * @param aWhatToLoad |
michael@0 | 194 | * The string spec for the desired URI, an nsIURI, or an nsIFile. |
michael@0 | 195 | * @param aOriginCharset [optional] |
michael@0 | 196 | * The character set for the URI. Only used if aWhatToLoad is a |
michael@0 | 197 | * string. |
michael@0 | 198 | * @param aBaseURI [optional] |
michael@0 | 199 | * The base URI for the spec. Only used if aWhatToLoad is a string. |
michael@0 | 200 | * |
michael@0 | 201 | * @return an nsIChannel object. |
michael@0 | 202 | */ |
michael@0 | 203 | newChannel: function NetUtil_newChannel(aWhatToLoad, aOriginCharset, |
michael@0 | 204 | aBaseURI) |
michael@0 | 205 | { |
michael@0 | 206 | if (!aWhatToLoad) { |
michael@0 | 207 | let exception = new Components.Exception( |
michael@0 | 208 | "Must have a non-null string spec, nsIURI, or nsIFile object", |
michael@0 | 209 | Cr.NS_ERROR_INVALID_ARG, |
michael@0 | 210 | Components.stack.caller |
michael@0 | 211 | ); |
michael@0 | 212 | throw exception; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | let uri = aWhatToLoad; |
michael@0 | 216 | if (!(aWhatToLoad instanceof Ci.nsIURI)) { |
michael@0 | 217 | // We either have a string or an nsIFile that we'll need a URI for. |
michael@0 | 218 | uri = this.newURI(aWhatToLoad, aOriginCharset, aBaseURI); |
michael@0 | 219 | } |
michael@0 | 220 | |
michael@0 | 221 | return this.ioService.newChannelFromURI(uri); |
michael@0 | 222 | }, |
michael@0 | 223 | |
michael@0 | 224 | /** |
michael@0 | 225 | * Reads aCount bytes from aInputStream into a string. |
michael@0 | 226 | * |
michael@0 | 227 | * @param aInputStream |
michael@0 | 228 | * The input stream to read from. |
michael@0 | 229 | * @param aCount |
michael@0 | 230 | * The number of bytes to read from the stream. |
michael@0 | 231 | * @param aOptions [optional] |
michael@0 | 232 | * charset |
michael@0 | 233 | * The character encoding of stream data. |
michael@0 | 234 | * replacement |
michael@0 | 235 | * The character to replace unknown byte sequences. |
michael@0 | 236 | * If unset, it causes an exceptions to be thrown. |
michael@0 | 237 | * |
michael@0 | 238 | * @return the bytes from the input stream in string form. |
michael@0 | 239 | * |
michael@0 | 240 | * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream. |
michael@0 | 241 | * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would |
michael@0 | 242 | * block the calling thread (non-blocking mode only). |
michael@0 | 243 | * @throws NS_ERROR_FAILURE if there are not enough bytes available to read |
michael@0 | 244 | * aCount amount of data. |
michael@0 | 245 | * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences |
michael@0 | 246 | */ |
michael@0 | 247 | readInputStreamToString: function NetUtil_readInputStreamToString(aInputStream, |
michael@0 | 248 | aCount, |
michael@0 | 249 | aOptions) |
michael@0 | 250 | { |
michael@0 | 251 | if (!(aInputStream instanceof Ci.nsIInputStream)) { |
michael@0 | 252 | let exception = new Components.Exception( |
michael@0 | 253 | "First argument should be an nsIInputStream", |
michael@0 | 254 | Cr.NS_ERROR_INVALID_ARG, |
michael@0 | 255 | Components.stack.caller |
michael@0 | 256 | ); |
michael@0 | 257 | throw exception; |
michael@0 | 258 | } |
michael@0 | 259 | |
michael@0 | 260 | if (!aCount) { |
michael@0 | 261 | let exception = new Components.Exception( |
michael@0 | 262 | "Non-zero amount of bytes must be specified", |
michael@0 | 263 | Cr.NS_ERROR_INVALID_ARG, |
michael@0 | 264 | Components.stack.caller |
michael@0 | 265 | ); |
michael@0 | 266 | throw exception; |
michael@0 | 267 | } |
michael@0 | 268 | |
michael@0 | 269 | if (aOptions && "charset" in aOptions) { |
michael@0 | 270 | let cis = Cc["@mozilla.org/intl/converter-input-stream;1"]. |
michael@0 | 271 | createInstance(Ci.nsIConverterInputStream); |
michael@0 | 272 | try { |
michael@0 | 273 | // When replacement is set, the character that is unknown sequence |
michael@0 | 274 | // replaces with aOptions.replacement character. |
michael@0 | 275 | if (!("replacement" in aOptions)) { |
michael@0 | 276 | // aOptions.replacement isn't set. |
michael@0 | 277 | // If input stream has unknown sequences for aOptions.charset, |
michael@0 | 278 | // throw NS_ERROR_ILLEGAL_INPUT. |
michael@0 | 279 | aOptions.replacement = 0; |
michael@0 | 280 | } |
michael@0 | 281 | |
michael@0 | 282 | cis.init(aInputStream, aOptions.charset, aCount, |
michael@0 | 283 | aOptions.replacement); |
michael@0 | 284 | let str = {}; |
michael@0 | 285 | cis.readString(-1, str); |
michael@0 | 286 | cis.close(); |
michael@0 | 287 | return str.value; |
michael@0 | 288 | } |
michael@0 | 289 | catch (e) { |
michael@0 | 290 | // Adjust the stack so it throws at the caller's location. |
michael@0 | 291 | throw new Components.Exception(e.message, e.result, |
michael@0 | 292 | Components.stack.caller, e.data); |
michael@0 | 293 | } |
michael@0 | 294 | } |
michael@0 | 295 | |
michael@0 | 296 | let sis = Cc["@mozilla.org/scriptableinputstream;1"]. |
michael@0 | 297 | createInstance(Ci.nsIScriptableInputStream); |
michael@0 | 298 | sis.init(aInputStream); |
michael@0 | 299 | try { |
michael@0 | 300 | return sis.readBytes(aCount); |
michael@0 | 301 | } |
michael@0 | 302 | catch (e) { |
michael@0 | 303 | // Adjust the stack so it throws at the caller's location. |
michael@0 | 304 | throw new Components.Exception(e.message, e.result, |
michael@0 | 305 | Components.stack.caller, e.data); |
michael@0 | 306 | } |
michael@0 | 307 | }, |
michael@0 | 308 | |
michael@0 | 309 | /** |
michael@0 | 310 | * Returns a reference to nsIIOService. |
michael@0 | 311 | * |
michael@0 | 312 | * @return a reference to nsIIOService. |
michael@0 | 313 | */ |
michael@0 | 314 | get ioService() |
michael@0 | 315 | { |
michael@0 | 316 | delete this.ioService; |
michael@0 | 317 | return this.ioService = Cc["@mozilla.org/network/io-service;1"]. |
michael@0 | 318 | getService(Ci.nsIIOService); |
michael@0 | 319 | }, |
michael@0 | 320 | }; |
michael@0 | 321 | |
michael@0 | 322 | //////////////////////////////////////////////////////////////////////////////// |
michael@0 | 323 | //// Initialization |
michael@0 | 324 | |
michael@0 | 325 | Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 326 | |
michael@0 | 327 | // Define our lazy getters. |
michael@0 | 328 | XPCOMUtils.defineLazyServiceGetter(this, "ioUtil", "@mozilla.org/io-util;1", |
michael@0 | 329 | "nsIIOUtil"); |