netwerk/base/src/NetUtil.jsm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

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");

mercurial