tor-browser

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

test_httpcancel.js (7495B)


      1 // This file ensures that canceling a channel early does not
      2 // send the request to the server (bug 350790)
      3 //
      4 // I've also shoehorned in a test that ENSURE_CALLED_BEFORE_CONNECT works as
      5 // expected: see comments that start with ENSURE_CALLED_BEFORE_CONNECT:
      6 //
      7 // This test also checks that cancelling a channel before asyncOpen, after
      8 // onStopRequest, or during onDataAvailable works as expected.
      9 
     10 "use strict";
     11 
     12 const { HttpServer } = ChromeUtils.importESModule(
     13  "resource://testing-common/httpd.sys.mjs"
     14 );
     15 const reason = "testing";
     16 
     17 function inChildProcess() {
     18  return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
     19 }
     20 
     21 var ios = Services.io;
     22 var ReferrerInfo = Components.Constructor(
     23  "@mozilla.org/referrer-info;1",
     24  "nsIReferrerInfo",
     25  "init"
     26 );
     27 var observer = {
     28  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
     29 
     30  observe(subject) {
     31    subject = subject.QueryInterface(Ci.nsIRequest);
     32    subject.cancelWithReason(Cr.NS_BINDING_ABORTED, reason);
     33 
     34    // ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
     35    try {
     36      subject.QueryInterface(Ci.nsIHttpChannel);
     37      let currentReferrer = subject.getRequestHeader("Referer");
     38      Assert.equal(currentReferrer, "http://site1.com/");
     39      var uri = ios.newURI("http://site2.com");
     40      subject.referrerInfo = new ReferrerInfo(
     41        Ci.nsIReferrerInfo.EMPTY,
     42        true,
     43        uri
     44      );
     45    } catch (ex) {
     46      do_throw("Exception: " + ex);
     47    }
     48  },
     49 };
     50 
     51 let cancelDuringOnStartListener = {
     52  onStartRequest: function test_onStartR(request) {
     53    Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
     54    // We didn't sync the reason to child process.
     55    if (!inChildProcess()) {
     56      Assert.equal(request.canceledReason, reason);
     57    }
     58 
     59    // ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
     60    try {
     61      request.QueryInterface(Ci.nsIHttpChannel);
     62      let currentReferrer = request.getRequestHeader("Referer");
     63      Assert.equal(currentReferrer, "http://site2.com/");
     64      var uri = ios.newURI("http://site3.com/");
     65 
     66      // Need to set NECKO_ERRORS_ARE_FATAL=0 else we'll abort process
     67      Services.env.set("NECKO_ERRORS_ARE_FATAL", "0");
     68      // we expect setting referrer to fail
     69      try {
     70        request.referrerInfo = new ReferrerInfo(
     71          Ci.nsIReferrerInfo.EMPTY,
     72          true,
     73          uri
     74        );
     75        do_throw("Error should have been thrown before getting here");
     76      } catch (ex) {}
     77    } catch (ex) {
     78      do_throw("Exception: " + ex);
     79    }
     80  },
     81 
     82  onDataAvailable: function test_ODA() {
     83    do_throw("Should not get any data!");
     84  },
     85 
     86  onStopRequest: function test_onStopR() {
     87    this.resolved();
     88  },
     89 };
     90 
     91 var cancelDuringOnDataListener = {
     92  data: "",
     93  channel: null,
     94  receivedSomeData: null,
     95  onStartRequest: function test_onStartR(request) {
     96    Assert.equal(request.status, Cr.NS_OK);
     97  },
     98 
     99  onDataAvailable: function test_ODA(request, stream, offset, count) {
    100    let string = NetUtil.readInputStreamToString(stream, count);
    101    Assert.ok(!string.includes("b"));
    102    this.data += string;
    103    this.channel.cancel(Cr.NS_BINDING_ABORTED);
    104    if (this.receivedSomeData) {
    105      this.receivedSomeData();
    106    }
    107  },
    108 
    109  onStopRequest: function test_onStopR(request) {
    110    Assert.ok(this.data.includes("a"), `data: ${this.data}`);
    111    Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
    112    this.resolved();
    113  },
    114 };
    115 
    116 function makeChan(url) {
    117  var chan = NetUtil.newChannel({
    118    uri: url,
    119    loadUsingSystemPrincipal: true,
    120  }).QueryInterface(Ci.nsIHttpChannel);
    121 
    122  // ENSURE_CALLED_BEFORE_CONNECT: set original value
    123  var uri = ios.newURI("http://site1.com");
    124  chan.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
    125  return chan;
    126 }
    127 
    128 var httpserv = null;
    129 
    130 add_task(async function setup() {
    131  httpserv = new HttpServer();
    132  httpserv.registerPathHandler("/failtest", failtest);
    133  httpserv.registerPathHandler("/cancel_middle", cancel_middle);
    134  httpserv.registerPathHandler("/normal_response", normal_response);
    135  httpserv.start(-1);
    136 
    137  registerCleanupFunction(async () => {
    138    await new Promise(resolve => httpserv.stop(resolve));
    139  });
    140 });
    141 
    142 add_task(async function test_cancel_during_onModifyRequest() {
    143  var chan = makeChan(
    144    "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
    145  );
    146 
    147  if (!inChildProcess()) {
    148    Services.obs.addObserver(observer, "http-on-modify-request");
    149  } else {
    150    do_send_remote_message("register-observer");
    151    await do_await_remote_message("register-observer-done");
    152  }
    153 
    154  await new Promise(resolve => {
    155    cancelDuringOnStartListener.resolved = resolve;
    156    chan.asyncOpen(cancelDuringOnStartListener);
    157  });
    158 
    159  if (!inChildProcess()) {
    160    Services.obs.removeObserver(observer, "http-on-modify-request");
    161  } else {
    162    do_send_remote_message("unregister-observer");
    163    await do_await_remote_message("unregister-observer-done");
    164  }
    165 });
    166 
    167 add_task(async function test_cancel_before_asyncOpen() {
    168  var chan = makeChan(
    169    "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
    170  );
    171 
    172  chan.cancel(Cr.NS_BINDING_ABORTED);
    173 
    174  Assert.throws(
    175    () => {
    176      chan.asyncOpen(cancelDuringOnStartListener);
    177    },
    178    /NS_BINDING_ABORTED/,
    179    "cannot open if already cancelled"
    180  );
    181 });
    182 
    183 add_task(async function test_cancel_during_onData() {
    184  var chan = makeChan(
    185    "http://localhost:" + httpserv.identity.primaryPort + "/cancel_middle"
    186  );
    187 
    188  await new Promise(resolve => {
    189    cancelDuringOnDataListener.resolved = resolve;
    190    cancelDuringOnDataListener.channel = chan;
    191    chan.asyncOpen(cancelDuringOnDataListener);
    192  });
    193 });
    194 
    195 var cancelAfterOnStopListener = {
    196  data: "",
    197  channel: null,
    198  onStartRequest: function test_onStartR(request) {
    199    Assert.equal(request.status, Cr.NS_OK);
    200  },
    201 
    202  onDataAvailable: function test_ODA(request, stream, offset, count) {
    203    let string = NetUtil.readInputStreamToString(stream, count);
    204    this.data += string;
    205  },
    206 
    207  onStopRequest: function test_onStopR(request) {
    208    info("onStopRequest");
    209    Assert.equal(request.status, Cr.NS_OK);
    210    this.resolved();
    211  },
    212 };
    213 
    214 add_task(async function test_cancel_after_onStop() {
    215  var chan = makeChan(
    216    "http://localhost:" + httpserv.identity.primaryPort + "/normal_response"
    217  );
    218 
    219  await new Promise(resolve => {
    220    cancelAfterOnStopListener.resolved = resolve;
    221    cancelAfterOnStopListener.channel = chan;
    222    chan.asyncOpen(cancelAfterOnStopListener);
    223  });
    224  Assert.equal(chan.status, Cr.NS_OK);
    225 
    226  // For now it's unclear if cancelling after onStop should throw,
    227  // silently fail, or overwrite the channel's status as we currently do.
    228  // See discussion in bug 1553083
    229  chan.cancel(Cr.NS_BINDING_ABORTED);
    230  Assert.equal(chan.status, Cr.NS_BINDING_ABORTED);
    231 });
    232 
    233 // PATHS
    234 
    235 // /failtest
    236 function failtest() {
    237  do_throw("This should not be reached");
    238 }
    239 
    240 function cancel_middle(metadata, response) {
    241  response.processAsync();
    242  response.setStatusLine(metadata.httpVersion, 200, "OK");
    243  let str1 = "a".repeat(128 * 1024);
    244  response.write(str1, str1.length);
    245  response.bodyOutputStream.flush();
    246 
    247  let p = new Promise(resolve => {
    248    cancelDuringOnDataListener.receivedSomeData = resolve;
    249  });
    250  p.then(() => {
    251    let str2 = "b".repeat(128 * 1024);
    252    response.write(str2, str2.length);
    253    response.finish();
    254  });
    255 }
    256 
    257 function normal_response(metadata, response) {
    258  response.setStatusLine(metadata.httpVersion, 200, "OK");
    259  let str1 = "Is this normal?";
    260  response.write(str1, str1.length);
    261 }