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