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