tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 };