tor-browser

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

http2_test_common.js (33490B)


      1 // test HTTP/2
      2 
      3 "use strict";
      4 
      5 /* import-globals-from head_channels.js */
      6 
      7 // Generate a small and a large post with known pre-calculated md5 sums
      8 function generateContent(size) {
      9  var content = "";
     10  for (var i = 0; i < size; i++) {
     11    content += "0";
     12  }
     13  return content;
     14 }
     15 
     16 var posts = [];
     17 posts.push(generateContent(10));
     18 posts.push(generateContent(250000));
     19 posts.push(generateContent(128000));
     20 
     21 // pre-calculated md5sums (in hex) of the above posts
     22 var md5s = [
     23  "f1b708bba17f1ce948dc979f4d7092bc",
     24  "2ef8d3b6c8f329318eb1a119b12622b6",
     25 ];
     26 
     27 var bigListenerData = generateContent(128 * 1024);
     28 var bigListenerMD5 = "8f607cfdd2c87d6a7eedb657dafbd836";
     29 
     30 function checkIsHttp2(request) {
     31  try {
     32    if (request.getResponseHeader("X-Firefox-Spdy") == "h2") {
     33      if (request.getResponseHeader("X-Connection-Http2") == "yes") {
     34        return true;
     35      }
     36      return false; // Weird case, but the server disagrees with us
     37    }
     38  } catch (e) {
     39    // Nothing to do here
     40  }
     41  return false;
     42 }
     43 
     44 var Http2CheckListener = function () {};
     45 
     46 Http2CheckListener.prototype = {
     47  onStartRequestFired: false,
     48  onDataAvailableFired: false,
     49  isHttp2Connection: false,
     50  shouldBeHttp2: true,
     51  accum: 0,
     52  expected: -1,
     53  shouldSucceed: true,
     54 
     55  onStartRequest: function testOnStartRequest(request) {
     56    this.onStartRequestFired = true;
     57    if (this.shouldSucceed && !Components.isSuccessCode(request.status)) {
     58      do_throw("Channel should have a success code! (" + request.status + ")");
     59    } else if (
     60      !this.shouldSucceed &&
     61      Components.isSuccessCode(request.status)
     62    ) {
     63      do_throw("Channel succeeded unexpectedly!");
     64    }
     65 
     66    Assert.ok(request instanceof Ci.nsIHttpChannel);
     67    if (this.noResponseStatus) {
     68      Assert.throws(
     69        () => request.responseStatus,
     70        /NS_ERROR_NOT_AVAILABLE/,
     71        "getting response status should throw"
     72      );
     73      return;
     74    }
     75    Assert.equal(request.requestSucceeded, this.shouldSucceed);
     76    if (this.shouldSucceed) {
     77      Assert.equal(request.responseStatus, 200);
     78    }
     79  },
     80 
     81  onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
     82    this.onDataAvailableFired = true;
     83    this.isHttp2Connection = checkIsHttp2(request);
     84    this.accum += cnt;
     85    read_stream(stream, cnt);
     86  },
     87 
     88  onStopRequest: function testOnStopRequest(request, status) {
     89    Assert.ok(this.onStartRequestFired);
     90    if (this.expected != -1) {
     91      Assert.equal(this.accum, this.expected);
     92    }
     93 
     94    if (this.shouldSucceed) {
     95      Assert.ok(Components.isSuccessCode(status));
     96      Assert.ok(this.onDataAvailableFired);
     97      Assert.equal(this.isHttp2Connection, this.shouldBeHttp2);
     98    } else {
     99      Assert.ok(!Components.isSuccessCode(status));
    100    }
    101    request.QueryInterface(Ci.nsIProxiedChannel);
    102    var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    103    this.finish({ httpProxyConnectResponseCode });
    104  },
    105 };
    106 
    107 /*
    108 * Support for testing valid multiplexing of streams
    109 */
    110 
    111 var multiplexContent = generateContent(30 * 1024);
    112 
    113 /* Listener class to control the testing of multiplexing */
    114 var Http2MultiplexListener = function () {};
    115 
    116 Http2MultiplexListener.prototype = new Http2CheckListener();
    117 
    118 Http2MultiplexListener.prototype.streamID = 0;
    119 Http2MultiplexListener.prototype.buffer = "";
    120 
    121 Http2MultiplexListener.prototype.onDataAvailable = function (
    122  request,
    123  stream,
    124  off,
    125  cnt
    126 ) {
    127  this.onDataAvailableFired = true;
    128  this.isHttp2Connection = checkIsHttp2(request);
    129  this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID"));
    130  var data = read_stream(stream, cnt);
    131  this.buffer = this.buffer.concat(data);
    132 };
    133 
    134 Http2MultiplexListener.prototype.onStopRequest = function (request) {
    135  Assert.ok(this.onStartRequestFired);
    136  Assert.ok(this.onDataAvailableFired);
    137  Assert.ok(this.isHttp2Connection);
    138  Assert.equal(this.buffer, multiplexContent);
    139 
    140  request.QueryInterface(Ci.nsIProxiedChannel);
    141  // This is what does most of the hard work for us
    142  var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    143  var streamID = this.streamID;
    144  this.finish({ httpProxyConnectResponseCode, streamID });
    145 };
    146 
    147 // Does the appropriate checks for header gatewaying
    148 var Http2HeaderListener = function (name, callback) {
    149  this.name = name;
    150  this.callback = callback;
    151 };
    152 
    153 Http2HeaderListener.prototype = new Http2CheckListener();
    154 Http2HeaderListener.prototype.value = "";
    155 
    156 Http2HeaderListener.prototype.onDataAvailable = function (
    157  request,
    158  stream,
    159  off,
    160  cnt
    161 ) {
    162  this.onDataAvailableFired = true;
    163  this.isHttp2Connection = checkIsHttp2(request);
    164  var hvalue = request.getResponseHeader(this.name);
    165  Assert.notEqual(hvalue, "");
    166  this.callback(hvalue);
    167  read_stream(stream, cnt);
    168 };
    169 
    170 const pushHdrTxt =
    171  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    172 const pullHdrTxt = pushHdrTxt.split("").reverse().join("");
    173 
    174 function checkContinuedHeaders(getHeader, headerPrefix, headerText) {
    175  for (var i = 0; i < 265; i++) {
    176    Assert.equal(getHeader(headerPrefix + 1), headerText);
    177  }
    178 }
    179 
    180 // Does the appropriate checks for a large GET response
    181 var Http2BigListener = function () {};
    182 
    183 Http2BigListener.prototype = new Http2CheckListener();
    184 Http2BigListener.prototype.buffer = "";
    185 
    186 Http2BigListener.prototype.onDataAvailable = function (
    187  request,
    188  stream,
    189  off,
    190  cnt
    191 ) {
    192  this.onDataAvailableFired = true;
    193  this.isHttp2Connection = checkIsHttp2(request);
    194  this.buffer = this.buffer.concat(read_stream(stream, cnt));
    195  // We know the server should send us the same data as our big post will be,
    196  // so the md5 should be the same
    197  Assert.equal(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
    198 };
    199 
    200 Http2BigListener.prototype.onStopRequest = function (request) {
    201  Assert.ok(this.onStartRequestFired);
    202  Assert.ok(this.onDataAvailableFired);
    203  Assert.ok(this.isHttp2Connection);
    204 
    205  // Don't want to flood output, so don't use do_check_eq
    206  Assert.equal(this.buffer, bigListenerData);
    207 
    208  request.QueryInterface(Ci.nsIProxiedChannel);
    209  var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    210  this.finish({ httpProxyConnectResponseCode });
    211 };
    212 
    213 var Http2HugeSuspendedListener = function () {};
    214 
    215 Http2HugeSuspendedListener.prototype = new Http2CheckListener();
    216 Http2HugeSuspendedListener.prototype.count = 0;
    217 
    218 Http2HugeSuspendedListener.prototype.onDataAvailable = function (
    219  request,
    220  stream,
    221  off,
    222  cnt
    223 ) {
    224  this.onDataAvailableFired = true;
    225  this.isHttp2Connection = checkIsHttp2(request);
    226  this.count += cnt;
    227  read_stream(stream, cnt);
    228 };
    229 
    230 Http2HugeSuspendedListener.prototype.onStopRequest = function (request) {
    231  Assert.ok(this.onStartRequestFired);
    232  Assert.ok(this.onDataAvailableFired);
    233  Assert.ok(this.isHttp2Connection);
    234  Assert.equal(this.count, 1024 * 1024 * 1); // 1mb of data expected
    235  request.QueryInterface(Ci.nsIProxiedChannel);
    236  var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    237  this.finish({ httpProxyConnectResponseCode });
    238 };
    239 
    240 // Does the appropriate checks for POSTs
    241 var Http2PostListener = function (expected_md5) {
    242  this.expected_md5 = expected_md5;
    243 };
    244 
    245 Http2PostListener.prototype = new Http2CheckListener();
    246 Http2PostListener.prototype.expected_md5 = "";
    247 
    248 Http2PostListener.prototype.onDataAvailable = function (
    249  request,
    250  stream,
    251  off,
    252  cnt
    253 ) {
    254  this.onDataAvailableFired = true;
    255  this.isHttp2Connection = checkIsHttp2(request);
    256  read_stream(stream, cnt);
    257  Assert.equal(
    258    this.expected_md5,
    259    request.getResponseHeader("X-Calculated-MD5")
    260  );
    261 };
    262 
    263 var ResumeStalledChannelListener = function () {};
    264 
    265 ResumeStalledChannelListener.prototype = {
    266  onStartRequestFired: false,
    267  onDataAvailableFired: false,
    268  isHttp2Connection: false,
    269  shouldBeHttp2: true,
    270  resumable: null,
    271 
    272  onStartRequest: function testOnStartRequest(request) {
    273    this.onStartRequestFired = true;
    274    if (!Components.isSuccessCode(request.status)) {
    275      do_throw("Channel should have a success code! (" + request.status + ")");
    276    }
    277 
    278    Assert.ok(request instanceof Ci.nsIHttpChannel);
    279    Assert.equal(request.responseStatus, 200);
    280    Assert.equal(request.requestSucceeded, true);
    281  },
    282 
    283  onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
    284    this.onDataAvailableFired = true;
    285    this.isHttp2Connection = checkIsHttp2(request);
    286    read_stream(stream, cnt);
    287  },
    288 
    289  onStopRequest: function testOnStopRequest(request, status) {
    290    Assert.ok(this.onStartRequestFired);
    291    Assert.ok(Components.isSuccessCode(status));
    292    Assert.ok(this.onDataAvailableFired);
    293    Assert.equal(this.isHttp2Connection, this.shouldBeHttp2);
    294    this.resumable.resume();
    295  },
    296 };
    297 
    298 // test a large download that creates stream flow control and
    299 // confirm we can do another independent stream while the download
    300 // stream is stuck
    301 async function test_http2_blocking_download(serverPort) {
    302  var chan = makeHTTPChannel(`https://localhost:${serverPort}/bigdownload`);
    303  var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
    304  internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2
    305  var p = new Promise(resolve => {
    306    var listener = new Http2CheckListener();
    307    listener.finish = resolve;
    308    listener.expected = 3 * 1024 * 1024;
    309    chan.asyncOpen(listener);
    310    chan.suspend();
    311  });
    312  // wait 5 seconds so that stream flow control kicks in and then see if we
    313  // can do a basic transaction (i.e. session not blocked). afterwards resume
    314  // channel
    315  do_timeout(5000, function () {
    316    var simpleChannel = makeHTTPChannel(`https://localhost:${serverPort}/`);
    317    var sl = new ResumeStalledChannelListener();
    318    sl.resumable = chan;
    319    simpleChannel.asyncOpen(sl);
    320  });
    321  return p;
    322 }
    323 
    324 // Make sure we make a HTTP2 connection and both us and the server mark it as such
    325 async function test_http2_basic(serverPort, origin = "localhost") {
    326  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`);
    327  var p = new Promise(resolve => {
    328    var listener = new Http2CheckListener();
    329    listener.finish = resolve;
    330    chan.asyncOpen(listener);
    331  });
    332  return p;
    333 }
    334 
    335 async function test_http2_basic_unblocked_dep(
    336  serverPort,
    337  origin = "localhost"
    338 ) {
    339  var chan = makeHTTPChannel(
    340    `https://${origin}:${serverPort}/basic_unblocked_dep`
    341  );
    342  var cos = chan.QueryInterface(Ci.nsIClassOfService);
    343  cos.addClassFlags(Ci.nsIClassOfService.Unblocked);
    344  return new Promise(resolve => {
    345    var listener = new Http2CheckListener();
    346    listener.finish = resolve;
    347    chan.asyncOpen(listener);
    348  });
    349 }
    350 
    351 // make sure we don't use h2 when disallowed
    352 async function test_http2_nospdy(serverPort, origin = "localhost") {
    353  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`);
    354  return new Promise(resolve => {
    355    var listener = new Http2CheckListener();
    356    listener.finish = resolve;
    357    var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal);
    358    internalChannel.allowSpdy = false;
    359    listener.shouldBeHttp2 = false;
    360    chan.asyncOpen(listener);
    361  });
    362 }
    363 
    364 // Support for making sure XHR works over SPDY
    365 function checkXhr(xhr, finish) {
    366  if (xhr.readyState != 4) {
    367    return;
    368  }
    369 
    370  Assert.equal(xhr.status, 200);
    371  Assert.equal(checkIsHttp2(xhr), true);
    372  finish();
    373 }
    374 
    375 // Fires off an XHR request over h2
    376 async function test_http2_xhr(serverPort, origin = "localhost") {
    377  return new Promise(resolve => {
    378    var req = new XMLHttpRequest();
    379    req.open("GET", `https://${origin}:${serverPort}/`, true);
    380    req.addEventListener("readystatechange", function () {
    381      checkXhr(req, resolve);
    382    });
    383    req.send(null);
    384  });
    385 }
    386 
    387 var Http2ConcurrentListener = function () {};
    388 
    389 Http2ConcurrentListener.prototype = new Http2CheckListener();
    390 Http2ConcurrentListener.prototype.count = 0;
    391 Http2ConcurrentListener.prototype.target = 0;
    392 Http2ConcurrentListener.prototype.reset = 0;
    393 Http2ConcurrentListener.prototype.recvdHdr = 0;
    394 
    395 Http2ConcurrentListener.prototype.onStopRequest = function (request) {
    396  this.count++;
    397  Assert.ok(this.isHttp2Connection);
    398  if (this.recvdHdr > 0) {
    399    Assert.equal(request.getResponseHeader("X-Recvd"), this.recvdHdr);
    400  }
    401 
    402  if (this.count == this.target) {
    403    if (this.reset > 0) {
    404      Services.prefs.setIntPref(
    405        "network.http.http2.default-concurrent",
    406        this.reset
    407      );
    408    }
    409    request.QueryInterface(Ci.nsIProxiedChannel);
    410    var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    411    this.finish({ httpProxyConnectResponseCode });
    412  }
    413 };
    414 
    415 async function test_http2_concurrent(
    416  concurrent_channels,
    417  serverPort,
    418  origin = "localhost"
    419 ) {
    420  var p = new Promise(resolve => {
    421    var concurrent_listener = new Http2ConcurrentListener();
    422    concurrent_listener.finish = resolve;
    423    concurrent_listener.target = 201;
    424    concurrent_listener.reset = Services.prefs.getIntPref(
    425      "network.http.http2.default-concurrent"
    426    );
    427    Services.prefs.setIntPref("network.http.http2.default-concurrent", 100);
    428 
    429    for (var i = 0; i < concurrent_listener.target; i++) {
    430      concurrent_channels[i] = makeHTTPChannel(
    431        `https://${origin}:${serverPort}/750ms`
    432      );
    433      concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
    434      concurrent_channels[i].asyncOpen(concurrent_listener);
    435    }
    436  });
    437  return p;
    438 }
    439 
    440 async function test_http2_concurrent_post(
    441  concurrent_channels,
    442  serverPort,
    443  origin = "localhost"
    444 ) {
    445  return new Promise(resolve => {
    446    var concurrent_listener = new Http2ConcurrentListener();
    447    concurrent_listener.finish = resolve;
    448    concurrent_listener.target = 8;
    449    concurrent_listener.recvdHdr = posts[2].length;
    450    concurrent_listener.reset = Services.prefs.getIntPref(
    451      "network.http.http2.default-concurrent"
    452    );
    453    Services.prefs.setIntPref("network.http.http2.default-concurrent", 3);
    454 
    455    for (var i = 0; i < concurrent_listener.target; i++) {
    456      concurrent_channels[i] = makeHTTPChannel(
    457        `https://${origin}:${serverPort}/750msPost`
    458      );
    459      concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
    460      var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
    461        Ci.nsIStringInputStream
    462      );
    463      stream.setByteStringData(posts[2]);
    464      var uchan = concurrent_channels[i].QueryInterface(Ci.nsIUploadChannel);
    465      uchan.setUploadStream(stream, "text/plain", stream.available());
    466      concurrent_channels[i].requestMethod = "POST";
    467      concurrent_channels[i].asyncOpen(concurrent_listener);
    468    }
    469  });
    470 }
    471 
    472 // Test to make sure we get multiplexing right
    473 async function test_http2_multiplex(serverPort) {
    474  let chan1 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex1`);
    475  let chan2 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex2`);
    476  let listener1 = new Http2MultiplexListener();
    477  let listener2 = new Http2MultiplexListener();
    478 
    479  let promises = [];
    480  let p1 = new Promise(resolve => {
    481    listener1.finish = resolve;
    482  });
    483  promises.push(p1);
    484  let p2 = new Promise(resolve => {
    485    listener2.finish = resolve;
    486  });
    487  promises.push(p2);
    488 
    489  chan1.asyncOpen(listener1);
    490  chan2.asyncOpen(listener2);
    491  return Promise.all(promises);
    492 }
    493 
    494 // Test to make sure we gateway non-standard headers properly
    495 async function test_http2_header(serverPort, origin = "localhost") {
    496  let chan = makeHTTPChannel(`https://${origin}:${serverPort}/header`);
    497  let hvalue = "Headers are fun";
    498  chan.setRequestHeader("X-Test-Header", hvalue, false);
    499  return new Promise(resolve => {
    500    let listener = new Http2HeaderListener("X-Received-Test-Header", function (
    501      received_hvalue
    502    ) {
    503      Assert.equal(received_hvalue, hvalue);
    504    });
    505    listener.finish = resolve;
    506    chan.asyncOpen(listener);
    507  });
    508 }
    509 
    510 // Test to make sure headers with invalid characters in the name are rejected
    511 async function test_http2_invalid_response_header(
    512  serverPort,
    513  invalid_kind,
    514  origin = "localhost"
    515 ) {
    516  return new Promise(resolve => {
    517    var listener = new Http2CheckListener();
    518    listener.finish = resolve;
    519    listener.shouldSucceed = false;
    520    var chan = makeHTTPChannel(
    521      `https://${origin}:${serverPort}/invalid_response_header/${invalid_kind}`
    522    );
    523    chan.asyncOpen(listener);
    524  });
    525 }
    526 
    527 // Test to make sure cookies are split into separate fields before compression
    528 async function test_http2_cookie_crumbling(serverPort, origin = "localhost") {
    529  var chan = makeHTTPChannel(
    530    `https://${origin}:${serverPort}/cookie_crumbling`
    531  );
    532  var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort();
    533  chan.setRequestHeader("Cookie", cookiesSent.join("; "), false);
    534  return new Promise(resolve => {
    535    var listener = new Http2HeaderListener("X-Received-Header-Pairs", function (
    536      pairsReceived
    537    ) {
    538      var cookiesReceived = JSON.parse(pairsReceived)
    539        .filter(function (pair) {
    540          return pair[0] == "cookie";
    541        })
    542        .map(function (pair) {
    543          return pair[1];
    544        })
    545        .sort();
    546      Assert.equal(cookiesReceived.length, cookiesSent.length);
    547      cookiesReceived.forEach(function (cookieReceived, index) {
    548        Assert.equal(cookiesSent[index], cookieReceived);
    549      });
    550    });
    551    listener.finish = resolve;
    552    chan.asyncOpen(listener);
    553  });
    554 }
    555 
    556 // this is a basic test where the server sends a simple document with 2 header
    557 // blocks. bug 1027364
    558 async function test_http2_doubleheader(serverPort, origin = "localhost") {
    559  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/doubleheader`);
    560  return new Promise(resolve => {
    561    var listener = new Http2CheckListener();
    562    listener.finish = resolve;
    563    chan.asyncOpen(listener);
    564  });
    565 }
    566 
    567 // Make sure we handle GETs that cover more than 2 frames properly
    568 async function test_http2_big(serverPort, origin = "localhost") {
    569  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/big`);
    570  return new Promise(resolve => {
    571    var listener = new Http2BigListener();
    572    listener.finish = resolve;
    573    chan.asyncOpen(listener);
    574  });
    575 }
    576 
    577 async function test_http2_huge_suspended(serverPort, origin = "localhost") {
    578  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/huge`);
    579  return new Promise(resolve => {
    580    var listener = new Http2HugeSuspendedListener();
    581    listener.finish = resolve;
    582    chan.asyncOpen(listener);
    583    chan.suspend();
    584    do_timeout(500, chan.resume);
    585  });
    586 }
    587 
    588 // Support for doing a POST
    589 function do_post(content, chan, listener, method) {
    590  var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
    591    Ci.nsIStringInputStream
    592  );
    593  stream.setByteStringData(content);
    594 
    595  var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
    596  uchan.setUploadStream(stream, "text/plain", stream.available());
    597 
    598  chan.requestMethod = method;
    599 
    600  chan.asyncOpen(listener);
    601 }
    602 
    603 // Make sure we can do a simple POST
    604 async function test_http2_post(serverPort, origin = "localhost") {
    605  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`);
    606  var p = new Promise(resolve => {
    607    var listener = new Http2PostListener(md5s[0]);
    608    listener.finish = resolve;
    609    do_post(posts[0], chan, listener, "POST");
    610  });
    611  return p;
    612 }
    613 
    614 async function test_http2_empty_post(serverPort, origin = "localhost") {
    615  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`);
    616  var p = new Promise(resolve => {
    617    var listener = new Http2PostListener("0");
    618    listener.finish = resolve;
    619    do_post("", chan, listener, "POST");
    620  });
    621  return p;
    622 }
    623 
    624 // Make sure we can do a simple PATCH
    625 async function test_http2_patch(serverPort, origin = "localhost") {
    626  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/patch`);
    627  return new Promise(resolve => {
    628    var listener = new Http2PostListener(md5s[0]);
    629    listener.finish = resolve;
    630    do_post(posts[0], chan, listener, "PATCH");
    631  });
    632 }
    633 
    634 // Make sure we can do a POST that covers more than 2 frames
    635 async function test_http2_post_big(serverPort, origin = "localhost") {
    636  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`);
    637  return new Promise(resolve => {
    638    var listener = new Http2PostListener(md5s[1]);
    639    listener.finish = resolve;
    640    do_post(posts[1], chan, listener, "POST");
    641  });
    642 }
    643 
    644 // When a http proxy is used alt-svc is disable. Therefore if withProxy is true,
    645 // try numberOfTries times to connect and make sure that alt-svc is not use and we never
    646 // connect to the HTTP/2 server.
    647 var altsvcClientListener = function (
    648  finish,
    649  httpserv,
    650  httpserv2,
    651  withProxy,
    652  numberOfTries
    653 ) {
    654  this.finish = finish;
    655  this.httpserv = httpserv;
    656  this.httpserv2 = httpserv2;
    657  this.withProxy = withProxy;
    658  this.numberOfTries = numberOfTries;
    659 };
    660 
    661 altsvcClientListener.prototype = {
    662  onStartRequest: function test_onStartR(request) {
    663    Assert.equal(request.status, Cr.NS_OK);
    664  },
    665 
    666  onDataAvailable: function test_ODA(request, stream, offset, cnt) {
    667    read_stream(stream, cnt);
    668  },
    669 
    670  onStopRequest: function test_onStopR(request) {
    671    var isHttp2Connection = checkIsHttp2(
    672      request.QueryInterface(Ci.nsIHttpChannel)
    673    );
    674    if (!isHttp2Connection) {
    675      dump("/altsvc1 not over h2 yet - retry\n");
    676      if (this.withProxy && this.numberOfTries == 0) {
    677        request.QueryInterface(Ci.nsIProxiedChannel);
    678        var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    679        this.finish({ httpProxyConnectResponseCode });
    680        return;
    681      }
    682      let chan = makeHTTPChannel(
    683        `http://foo.example.com:${this.httpserv}/altsvc1`,
    684        this.withProxy
    685      ).QueryInterface(Ci.nsIHttpChannel);
    686      // we use this header to tell the server to issue a altsvc frame for the
    687      // speficied origin we will use in the next part of the test
    688      chan.setRequestHeader(
    689        "x-redirect-origin",
    690        `http://foo.example.com:${this.httpserv2}`,
    691        false
    692      );
    693      chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
    694      chan.asyncOpen(
    695        new altsvcClientListener(
    696          this.finish,
    697          this.httpserv,
    698          this.httpserv2,
    699          this.withProxy,
    700          this.numberOfTries - 1
    701        )
    702      );
    703    } else {
    704      Assert.ok(isHttp2Connection);
    705      let chan = makeHTTPChannel(
    706        `http://foo.example.com:${this.httpserv2}/altsvc2`
    707      ).QueryInterface(Ci.nsIHttpChannel);
    708      chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
    709      chan.asyncOpen(
    710        new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2)
    711      );
    712    }
    713  },
    714 };
    715 
    716 var altsvcClientListener2 = function (finish, httpserv, httpserv2) {
    717  this.finish = finish;
    718  this.httpserv = httpserv;
    719  this.httpserv2 = httpserv2;
    720 };
    721 
    722 altsvcClientListener2.prototype = {
    723  onStartRequest: function test_onStartR(request) {
    724    Assert.equal(request.status, Cr.NS_OK);
    725  },
    726 
    727  onDataAvailable: function test_ODA(request, stream, offset, cnt) {
    728    read_stream(stream, cnt);
    729  },
    730 
    731  onStopRequest: function test_onStopR(request) {
    732    var isHttp2Connection = checkIsHttp2(
    733      request.QueryInterface(Ci.nsIHttpChannel)
    734    );
    735    if (!isHttp2Connection) {
    736      dump("/altsvc2 not over h2 yet - retry\n");
    737      var chan = makeHTTPChannel(
    738        `http://foo.example.com:${this.httpserv2}/altsvc2`
    739      ).QueryInterface(Ci.nsIHttpChannel);
    740      chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE;
    741      chan.asyncOpen(
    742        new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2)
    743      );
    744    } else {
    745      Assert.ok(isHttp2Connection);
    746      request.QueryInterface(Ci.nsIProxiedChannel);
    747      var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    748      this.finish({ httpProxyConnectResponseCode });
    749    }
    750  },
    751 };
    752 
    753 async function test_http2_altsvc(httpserv, httpserv2, withProxy) {
    754  var chan = makeHTTPChannel(
    755    `http://foo.example.com:${httpserv}/altsvc1`,
    756    withProxy
    757  ).QueryInterface(Ci.nsIHttpChannel);
    758  return new Promise(resolve => {
    759    var numberOfTries = 0;
    760    if (withProxy) {
    761      numberOfTries = 20;
    762    }
    763    chan.asyncOpen(
    764      new altsvcClientListener(
    765        resolve,
    766        httpserv,
    767        httpserv2,
    768        withProxy,
    769        numberOfTries
    770      )
    771    );
    772  });
    773 }
    774 
    775 var WrongSuiteListener = function () {};
    776 
    777 WrongSuiteListener.prototype = new Http2CheckListener();
    778 WrongSuiteListener.prototype.shouldBeHttp2 = false;
    779 WrongSuiteListener.prototype.onStopRequest = function (request, status) {
    780  Services.prefs.setBoolPref(
    781    "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
    782    true
    783  );
    784  Services.prefs.clearUserPref("security.tls.version.max");
    785  Http2CheckListener.prototype.onStopRequest.call(this, request, status);
    786 };
    787 
    788 // test that we use h1 without the mandatory cipher suite available when
    789 // offering at most tls1.2
    790 async function test_http2_wrongsuite_tls12(serverPort, origin = "localhost") {
    791  Services.prefs.setBoolPref(
    792    "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
    793    false
    794  );
    795  Services.prefs.setIntPref("security.tls.version.max", 3);
    796  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`);
    797  chan.loadFlags =
    798    Ci.nsIRequest.LOAD_FRESH_CONNECTION |
    799    Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
    800  return new Promise(resolve => {
    801    var listener = new WrongSuiteListener();
    802    listener.finish = resolve;
    803    chan.asyncOpen(listener);
    804  });
    805 }
    806 
    807 // test that we use h2 when offering tls1.3 or higher regardless of if the
    808 // mandatory cipher suite is available
    809 async function test_http2_wrongsuite_tls13(serverPort, origin = "localhost") {
    810  Services.prefs.setBoolPref(
    811    "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256",
    812    false
    813  );
    814  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`);
    815  chan.loadFlags =
    816    Ci.nsIRequest.LOAD_FRESH_CONNECTION |
    817    Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
    818  return new Promise(resolve => {
    819    var listener = new WrongSuiteListener();
    820    listener.finish = resolve;
    821    listener.shouldBeHttp2 = true;
    822    chan.asyncOpen(listener);
    823  });
    824 }
    825 
    826 async function test_http2_h11required_stream(serverPort, origin = "localhost") {
    827  var chan = makeHTTPChannel(
    828    `https://${origin}:${serverPort}/h11required_stream`
    829  );
    830  return new Promise(resolve => {
    831    var listener = new Http2CheckListener();
    832    listener.finish = resolve;
    833    listener.shouldBeHttp2 = false;
    834    chan.asyncOpen(listener);
    835  });
    836 }
    837 
    838 function H11RequiredSessionListener() {}
    839 
    840 H11RequiredSessionListener.prototype = new Http2CheckListener();
    841 
    842 H11RequiredSessionListener.prototype.onStopRequest = function (request) {
    843  var streamReused = request.getResponseHeader("X-H11Required-Stream-Ok");
    844  Assert.equal(streamReused, "yes");
    845 
    846  Assert.ok(this.onStartRequestFired);
    847  Assert.ok(this.onDataAvailableFired);
    848  Assert.equal(this.isHttp2Connection, this.shouldBeHttp2);
    849 
    850  request.QueryInterface(Ci.nsIProxiedChannel);
    851  var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    852  this.finish({ httpProxyConnectResponseCode });
    853 };
    854 
    855 async function test_http2_h11required_session(
    856  serverPort,
    857  origin = "localhost"
    858 ) {
    859  var chan = makeHTTPChannel(
    860    `https://${origin}:${serverPort}/h11required_session`
    861  );
    862  return new Promise(resolve => {
    863    var listener = new H11RequiredSessionListener();
    864    listener.finish = resolve;
    865    listener.shouldBeHttp2 = false;
    866    chan.asyncOpen(listener);
    867  });
    868 }
    869 
    870 async function test_http2_retry_rst(serverPort, origin = "localhost") {
    871  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/rstonce`);
    872  return new Promise(resolve => {
    873    var listener = new Http2CheckListener();
    874    listener.finish = resolve;
    875    chan.asyncOpen(listener);
    876  });
    877 }
    878 
    879 async function test_http2_continuations_over_max_response_limit(
    880  loadGroup,
    881  serverPort,
    882  origin = "localhost"
    883 ) {
    884  var chan = makeHTTPChannel(
    885    `https://${origin}:${serverPort}/hugecontinuedheaders?size=385`
    886  );
    887  chan.loadGroup = loadGroup;
    888  return new Promise(resolve => {
    889    var listener = new Http2CheckListener();
    890    listener.finish = resolve;
    891    listener.shouldSucceed = false;
    892    listener.noResponseStatus = true;
    893    chan.asyncOpen(listener);
    894  });
    895 }
    896 
    897 function Http2IllegalHpackValidationListener() {}
    898 
    899 Http2IllegalHpackValidationListener.prototype = new Http2CheckListener();
    900 Http2IllegalHpackValidationListener.prototype.shouldGoAway = false;
    901 
    902 Http2IllegalHpackValidationListener.prototype.onStopRequest = function (
    903  request
    904 ) {
    905  var wentAway = request.getResponseHeader("X-Did-Goaway") === "yes";
    906  Assert.equal(wentAway, this.shouldGoAway);
    907 
    908  Assert.ok(this.onStartRequestFired);
    909  Assert.ok(this.onDataAvailableFired);
    910  Assert.equal(this.isHttp2Connection, this.shouldBeHttp2);
    911 
    912  request.QueryInterface(Ci.nsIProxiedChannel);
    913  var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode;
    914  this.finish({ httpProxyConnectResponseCode });
    915 };
    916 
    917 function Http2IllegalHpackListener() {}
    918 Http2IllegalHpackListener.prototype = new Http2CheckListener();
    919 Http2IllegalHpackListener.prototype.shouldGoAway = false;
    920 
    921 Http2IllegalHpackListener.prototype.onStopRequest = function () {
    922  var chan = makeHTTPChannel(
    923    `https://${this.origin}:${this.serverPort}/illegalhpack_validate`
    924  );
    925  var listener = new Http2IllegalHpackValidationListener();
    926  listener.finish = this.finish;
    927  listener.shouldGoAway = this.shouldGoAway;
    928  chan.asyncOpen(listener);
    929 };
    930 
    931 async function test_http2_illegalhpacksoft(serverPort, origin = "localhost") {
    932  var chan = makeHTTPChannel(
    933    `https://${origin}:${serverPort}/illegalhpacksoft`
    934  );
    935  return new Promise(resolve => {
    936    var listener = new Http2IllegalHpackListener();
    937    listener.finish = resolve;
    938    listener.serverPort = serverPort;
    939    listener.origin = origin;
    940    listener.shouldGoAway = false;
    941    listener.shouldSucceed = false;
    942    chan.asyncOpen(listener);
    943  });
    944 }
    945 
    946 async function test_http2_illegalhpackhard(serverPort, origin = "localhost") {
    947  var chan = makeHTTPChannel(
    948    `https://${origin}:${serverPort}/illegalhpackhard`
    949  );
    950  return new Promise(resolve => {
    951    var listener = new Http2IllegalHpackListener();
    952    listener.finish = resolve;
    953    listener.serverPort = serverPort;
    954    listener.origin = origin;
    955    listener.shouldGoAway = true;
    956    listener.shouldSucceed = false;
    957    chan.asyncOpen(listener);
    958  });
    959 }
    960 
    961 async function test_http2_folded_header(
    962  loadGroup,
    963  serverPort,
    964  origin = "localhost"
    965 ) {
    966  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/foldedheader`);
    967  chan.loadGroup = loadGroup;
    968  return new Promise(resolve => {
    969    var listener = new Http2CheckListener();
    970    listener.finish = resolve;
    971    listener.shouldSucceed = false;
    972    chan.asyncOpen(listener);
    973  });
    974 }
    975 
    976 async function test_http2_empty_data(serverPort, origin = "localhost") {
    977  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/emptydata`);
    978  return new Promise(resolve => {
    979    var listener = new Http2CheckListener();
    980    listener.finish = resolve;
    981    chan.asyncOpen(listener);
    982  });
    983 }
    984 
    985 async function test_http2_status_phrase(serverPort, origin = "localhost") {
    986  var chan = makeHTTPChannel(`https://${origin}:${serverPort}/statusphrase`);
    987  return new Promise(resolve => {
    988    var listener = new Http2CheckListener();
    989    listener.finish = resolve;
    990    listener.shouldSucceed = false;
    991    chan.asyncOpen(listener);
    992  });
    993 }
    994 
    995 var PulledDiskCacheListener = function () {};
    996 PulledDiskCacheListener.prototype = new Http2CheckListener();
    997 PulledDiskCacheListener.prototype.EXPECTED_DATA = "this was pulled via h2";
    998 PulledDiskCacheListener.prototype.readData = "";
    999 PulledDiskCacheListener.prototype.onDataAvailable =
   1000  function testOnDataAvailable(request, stream, off, cnt) {
   1001    this.onDataAvailableFired = true;
   1002    this.isHttp2Connection = checkIsHttp2(request);
   1003    this.accum += cnt;
   1004    this.readData += read_stream(stream, cnt);
   1005  };
   1006 PulledDiskCacheListener.prototype.onStopRequest = function testOnStopRequest(
   1007  request,
   1008  status
   1009 ) {
   1010  Assert.equal(this.EXPECTED_DATA, this.readData);
   1011  Http2CheckListener.prorotype.onStopRequest.call(this, request, status);
   1012 };
   1013 
   1014 const DISK_CACHE_DATA = "this is from disk cache";
   1015 
   1016 var FromDiskCacheListener = function (finish, loadGroup, serverPort) {
   1017  this.finish = finish;
   1018  this.loadGroup = loadGroup;
   1019  this.serverPort = serverPort;
   1020 };
   1021 FromDiskCacheListener.prototype = {
   1022  onStartRequestFired: false,
   1023  onDataAvailableFired: false,
   1024  readData: "",
   1025 
   1026  onStartRequest: function testOnStartRequest(request) {
   1027    this.onStartRequestFired = true;
   1028    if (!Components.isSuccessCode(request.status)) {
   1029      do_throw("Channel should have a success code! (" + request.status + ")");
   1030    }
   1031 
   1032    Assert.ok(request instanceof Ci.nsIHttpChannel);
   1033    Assert.ok(request.requestSucceeded);
   1034    Assert.equal(request.responseStatus, 200);
   1035  },
   1036 
   1037  onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) {
   1038    this.onDataAvailableFired = true;
   1039    this.readData += read_stream(stream, cnt);
   1040  },
   1041 
   1042  onStopRequest: function testOnStopRequest(request, status) {
   1043    Assert.ok(this.onStartRequestFired);
   1044    Assert.ok(Components.isSuccessCode(status));
   1045    Assert.ok(this.onDataAvailableFired);
   1046    Assert.equal(this.readData, DISK_CACHE_DATA);
   1047 
   1048    evict_cache_entries("disk");
   1049    syncWithCacheIOThread(() => {
   1050      // Now that we know the entry is out of the disk cache, check to make sure
   1051      // we don't have this hiding in the push cache somewhere - if we do, it
   1052      // didn't get cancelled, and we have a bug.
   1053      var chan = makeHTTPChannel(
   1054        `https://localhost:${this.serverPort}/diskcache`
   1055      );
   1056      var listener = new PulledDiskCacheListener();
   1057      listener.finish = this.finish;
   1058      chan.loadGroup = this.loadGroup;
   1059      chan.asyncOpen(listener);
   1060    });
   1061  },
   1062 };