tor-browser

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

test_range_requests.js (15506B)


      1 //
      2 // This test makes sure range-requests are sent and treated the way we want
      3 // See bug #612135 for a thorough discussion on the subject
      4 //
      5 // Necko does a range-request for a partial cache-entry iff
      6 //
      7 //   1) size of the cached entry < value of the cached Content-Length header
      8 //      (not tested here - see bug #612135 comments 108-110)
      9 //   2) the size of the cached entry is > 0  (see bug #628607)
     10 //   3) the cached entry does not have a "no-store" Cache-Control header
     11 //   4) the cached entry does not have a Content-Encoding (see bug #613159)
     12 //   5) the request does not have a conditional-request header set by client
     13 //   6) nsHttpResponseHead::IsResumable() is true for the cached entry
     14 //   7) a basic positive test that makes sure byte ranges work
     15 //   8) ensure NS_ERROR_CORRUPTED_CONTENT is thrown when total entity size
     16 //      of 206 does not match content-length of 200
     17 //
     18 //  The test has one handler for each case and run_tests() fires one request
     19 //  for each. None of the handlers should see a Range-header.
     20 
     21 "use strict";
     22 
     23 const { HttpServer } = ChromeUtils.importESModule(
     24  "resource://testing-common/httpd.sys.mjs"
     25 );
     26 
     27 var httpserver = null;
     28 
     29 const clearTextBody = "This is a slightly longer test\n";
     30 const encodedBody = [
     31  0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78,
     32  0x74, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8,
     33  0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85, 0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c,
     34  0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85, 0x92, 0xd4, 0xe2, 0x12,
     35  0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00,
     36 ];
     37 
     38 const partial_data_length = 4;
     39 var port = null; // set in run_test
     40 
     41 function make_channel(url) {
     42  return NetUtil.newChannel({
     43    uri: url,
     44    loadUsingSystemPrincipal: true,
     45  }).QueryInterface(Ci.nsIHttpChannel);
     46 }
     47 
     48 // StreamListener which cancels its request on first data available
     49 function Canceler(continueFn) {
     50  this.continueFn = continueFn;
     51 }
     52 Canceler.prototype = {
     53  QueryInterface: ChromeUtils.generateQI([
     54    "nsIStreamListener",
     55    "nsIRequestObserver",
     56  ]),
     57  onStartRequest() {},
     58 
     59  onDataAvailable(request, stream, offset, count) {
     60    // Read stream so we don't assert for not reading from the stream
     61    // if cancelling the channel is slow.
     62    read_stream(stream, count);
     63 
     64    request.QueryInterface(Ci.nsIChannel).cancel(Cr.NS_BINDING_ABORTED);
     65  },
     66  onStopRequest(request, status) {
     67    Assert.equal(status, Cr.NS_BINDING_ABORTED);
     68    this.continueFn(request, null);
     69  },
     70 };
     71 // Simple StreamListener which performs no validations
     72 function MyListener(continueFn) {
     73  this.continueFn = continueFn;
     74  this._buffer = null;
     75 }
     76 MyListener.prototype = {
     77  QueryInterface: ChromeUtils.generateQI([
     78    "nsIStreamListener",
     79    "nsIRequestObserver",
     80  ]),
     81  onStartRequest() {
     82    this._buffer = "";
     83  },
     84 
     85  onDataAvailable(request, stream, offset, count) {
     86    this._buffer = this._buffer.concat(read_stream(stream, count));
     87  },
     88  onStopRequest(request) {
     89    this.continueFn(request, this._buffer);
     90  },
     91 };
     92 
     93 var case_8_range_request = false;
     94 function FailedChannelListener(continueFn) {
     95  this.continueFn = continueFn;
     96 }
     97 FailedChannelListener.prototype = {
     98  QueryInterface: ChromeUtils.generateQI([
     99    "nsIStreamListener",
    100    "nsIRequestObserver",
    101  ]),
    102  onStartRequest() {},
    103 
    104  onDataAvailable(request, stream, offset, count) {
    105    read_stream(stream, count);
    106  },
    107 
    108  onStopRequest(request, status) {
    109    if (case_8_range_request) {
    110      Assert.equal(status, Cr.NS_ERROR_CORRUPTED_CONTENT);
    111    }
    112    this.continueFn(request, null);
    113  },
    114 };
    115 
    116 function received_cleartext(request, data) {
    117  Assert.equal(clearTextBody, data);
    118  testFinished();
    119 }
    120 
    121 function setStdHeaders(response, length) {
    122  response.setHeader("Content-Type", "text/plain", false);
    123  response.setHeader("ETag", "Just testing");
    124  response.setHeader("Cache-Control", "max-age: 360000");
    125  response.setHeader("Accept-Ranges", "bytes");
    126  response.setHeader("Content-Length", "" + length);
    127 }
    128 
    129 function handler_2(metadata, response) {
    130  setStdHeaders(response, clearTextBody.length);
    131  Assert.ok(!metadata.hasHeader("Range"));
    132  response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
    133 }
    134 function received_partial_2(request, data) {
    135  Assert.equal(data, undefined);
    136  var chan = make_channel("http://localhost:" + port + "/test_2");
    137  chan.asyncOpen(new ChannelListener(received_cleartext, null));
    138 }
    139 
    140 var case_3_request_no = 0;
    141 function handler_3(metadata, response) {
    142  var body = clearTextBody;
    143  setStdHeaders(response, body.length);
    144  response.setHeader("Cache-Control", "no-store", false);
    145  switch (case_3_request_no) {
    146    case 0:
    147      Assert.ok(!metadata.hasHeader("Range"));
    148      body = body.slice(0, partial_data_length);
    149      response.processAsync();
    150      response.bodyOutputStream.write(body, body.length);
    151      response.finish();
    152      break;
    153    case 1:
    154      Assert.ok(!metadata.hasHeader("Range"));
    155      response.bodyOutputStream.write(body, body.length);
    156      break;
    157    default:
    158      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    159  }
    160  case_3_request_no++;
    161 }
    162 function received_partial_3(request, data) {
    163  Assert.equal(partial_data_length, data.length);
    164  var chan = make_channel("http://localhost:" + port + "/test_3");
    165  chan.asyncOpen(new ChannelListener(received_cleartext, null));
    166 }
    167 
    168 var case_4_request_no = 0;
    169 function handler_4(metadata, response) {
    170  switch (case_4_request_no) {
    171    case 0:
    172      Assert.ok(!metadata.hasHeader("Range"));
    173      var body = encodedBody;
    174      setStdHeaders(response, body.length);
    175      response.setHeader("Content-Encoding", "gzip", false);
    176      body = body.slice(0, partial_data_length);
    177      var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
    178        Ci.nsIBinaryOutputStream
    179      );
    180      bos.setOutputStream(response.bodyOutputStream);
    181      response.processAsync();
    182      bos.writeByteArray(body);
    183      response.finish();
    184      break;
    185    case 1:
    186      Assert.ok(!metadata.hasHeader("Range"));
    187      setStdHeaders(response, clearTextBody.length);
    188      response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
    189      break;
    190    default:
    191      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    192  }
    193  case_4_request_no++;
    194 }
    195 function received_partial_4() {
    196  // checking length does not work with encoded data
    197  //  do_check_eq(partial_data_length, data.length);
    198  var chan = make_channel("http://localhost:" + port + "/test_4");
    199  chan.asyncOpen(new MyListener(received_cleartext));
    200 }
    201 
    202 var case_5_request_no = 0;
    203 function handler_5(metadata, response) {
    204  var body = clearTextBody;
    205  setStdHeaders(response, body.length);
    206  switch (case_5_request_no) {
    207    case 0:
    208      Assert.ok(!metadata.hasHeader("Range"));
    209      body = body.slice(0, partial_data_length);
    210      response.processAsync();
    211      response.bodyOutputStream.write(body, body.length);
    212      response.finish();
    213      break;
    214    case 1:
    215      Assert.ok(!metadata.hasHeader("Range"));
    216      response.bodyOutputStream.write(body, body.length);
    217      break;
    218    default:
    219      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    220  }
    221  case_5_request_no++;
    222 }
    223 function received_partial_5(request, data) {
    224  Assert.equal(partial_data_length, data.length);
    225  var chan = make_channel("http://localhost:" + port + "/test_5");
    226  chan.setRequestHeader("If-Match", "Some eTag", false);
    227  chan.asyncOpen(new ChannelListener(received_cleartext, null));
    228 }
    229 
    230 var case_6_request_no = 0;
    231 function handler_6(metadata, response) {
    232  switch (case_6_request_no) {
    233    case 0:
    234      Assert.ok(!metadata.hasHeader("Range"));
    235      var body = clearTextBody;
    236      setStdHeaders(response, body.length);
    237      response.setHeader("Accept-Ranges", "", false);
    238      body = body.slice(0, partial_data_length);
    239      response.processAsync();
    240      response.bodyOutputStream.write(body, body.length);
    241      response.finish();
    242      break;
    243    case 1:
    244      Assert.ok(!metadata.hasHeader("Range"));
    245      setStdHeaders(response, clearTextBody.length);
    246      response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
    247      break;
    248    default:
    249      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    250  }
    251  case_6_request_no++;
    252 }
    253 function received_partial_6(request, data) {
    254  // would like to verify that the response does not have Accept-Ranges
    255  Assert.equal(partial_data_length, data.length);
    256  var chan = make_channel("http://localhost:" + port + "/test_6");
    257  chan.asyncOpen(new ChannelListener(received_cleartext, null));
    258 }
    259 
    260 const simpleBody = "0123456789";
    261 
    262 function received_simple(request, data) {
    263  Assert.equal(simpleBody, data);
    264  testFinished();
    265 }
    266 
    267 var case_7_request_no = 0;
    268 function handler_7(metadata, response) {
    269  switch (case_7_request_no) {
    270    case 0:
    271      Assert.ok(!metadata.hasHeader("Range"));
    272      response.setHeader("Content-Type", "text/plain", false);
    273      response.setHeader("ETag", "test7Etag");
    274      response.setHeader("Accept-Ranges", "bytes");
    275      response.setHeader("Cache-Control", "max-age=360000");
    276      response.setHeader("Content-Length", "10");
    277      response.processAsync();
    278      response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
    279      response.finish();
    280      break;
    281    case 1:
    282      response.setHeader("Content-Type", "text/plain", false);
    283      response.setHeader("ETag", "test7Etag");
    284      if (metadata.hasHeader("Range")) {
    285        Assert.ok(metadata.hasHeader("If-Range"));
    286        response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
    287        response.setHeader("Content-Range", "4-9/10");
    288        response.setHeader("Content-Length", "6");
    289        response.bodyOutputStream.write(simpleBody.slice(4), 6);
    290      } else {
    291        response.setHeader("Content-Length", "10");
    292        response.bodyOutputStream.write(simpleBody, 10);
    293      }
    294      break;
    295    default:
    296      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    297  }
    298  case_7_request_no++;
    299 }
    300 function received_partial_7(request, data) {
    301  // make sure we get the first 4 bytes
    302  Assert.equal(4, data.length);
    303  // do it again to get the rest
    304  var chan = make_channel("http://localhost:" + port + "/test_7");
    305  chan.asyncOpen(new ChannelListener(received_simple, null));
    306 }
    307 
    308 var case_8_request_no = 0;
    309 function handler_8(metadata, response) {
    310  switch (case_8_request_no) {
    311    case 0:
    312      Assert.ok(!metadata.hasHeader("Range"));
    313      response.setHeader("Content-Type", "text/plain", false);
    314      response.setHeader("ETag", "test8Etag");
    315      response.setHeader("Accept-Ranges", "bytes");
    316      response.setHeader("Cache-Control", "max-age=360000");
    317      response.setHeader("Content-Length", "10");
    318      response.processAsync();
    319      response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
    320      response.finish();
    321      break;
    322    case 1:
    323      if (metadata.hasHeader("Range")) {
    324        Assert.ok(metadata.hasHeader("If-Range"));
    325        case_8_range_request = true;
    326      }
    327      response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
    328      response.setHeader("Content-Type", "text/plain", false);
    329      response.setHeader("ETag", "test8Etag");
    330      response.setHeader("Content-Range", "4-8/9"); // intentionally broken
    331      response.setHeader("Content-Length", "5");
    332      response.bodyOutputStream.write(simpleBody.slice(4), 5);
    333      break;
    334    default:
    335      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    336  }
    337  case_8_request_no++;
    338 }
    339 function received_partial_8(request, data) {
    340  // make sure we get the first 4 bytes
    341  Assert.equal(4, data.length);
    342  // do it again to get the rest
    343  var chan = make_channel("http://localhost:" + port + "/test_8");
    344  chan.asyncOpen(
    345    new FailedChannelListener(testFinished, null, CL_EXPECT_LATE_FAILURE)
    346  );
    347 }
    348 
    349 var case_9_request_no = 0;
    350 function handler_9(metadata, response) {
    351  switch (case_9_request_no) {
    352    case 0:
    353      Assert.ok(!metadata.hasHeader("Range"));
    354      response.setHeader("Content-Type", "text/plain", false);
    355      response.setHeader("ETag", "W/test9WeakEtag");
    356      response.setHeader("Accept-Ranges", "bytes");
    357      response.setHeader("Cache-Control", "max-age=360000");
    358      response.setHeader("Content-Length", "10");
    359      response.processAsync();
    360      response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
    361      response.finish(); // truncated response
    362      break;
    363    case 1:
    364      Assert.ok(!metadata.hasHeader("Range"));
    365      response.setHeader("Content-Type", "text/plain", false);
    366      response.setHeader("ETag", "W/test9WeakEtag");
    367      response.setHeader("Accept-Ranges", "bytes");
    368      response.setHeader("Cache-Control", "max-age=360000");
    369      response.setHeader("Content-Length", "10");
    370      response.processAsync();
    371      response.bodyOutputStream.write(simpleBody, 10);
    372      response.finish(); // full response
    373      break;
    374    default:
    375      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
    376  }
    377  case_9_request_no++;
    378 }
    379 function received_partial_9(request, data) {
    380  Assert.equal(partial_data_length, data.length);
    381  var chan = make_channel("http://localhost:" + port + "/test_9");
    382  chan.asyncOpen(new ChannelListener(received_simple, null));
    383 }
    384 
    385 // Simple mechanism to keep track of tests and stop the server
    386 var numTestsFinished = 0;
    387 function testFinished() {
    388  if (++numTestsFinished == 7) {
    389    httpserver.stop(do_test_finished);
    390  }
    391 }
    392 
    393 function run_test() {
    394  httpserver = new HttpServer();
    395  httpserver.registerPathHandler("/test_2", handler_2);
    396  httpserver.registerPathHandler("/test_3", handler_3);
    397  httpserver.registerPathHandler("/test_4", handler_4);
    398  httpserver.registerPathHandler("/test_5", handler_5);
    399  httpserver.registerPathHandler("/test_6", handler_6);
    400  httpserver.registerPathHandler("/test_7", handler_7);
    401  httpserver.registerPathHandler("/test_8", handler_8);
    402  httpserver.registerPathHandler("/test_9", handler_9);
    403  httpserver.start(-1);
    404 
    405  port = httpserver.identity.primaryPort;
    406 
    407  // wipe out cached content
    408  evict_cache_entries();
    409 
    410  // Case 2: zero-length partial entry must not trigger range-request
    411  let chan = make_channel("http://localhost:" + port + "/test_2");
    412  chan.asyncOpen(new Canceler(received_partial_2));
    413 
    414  // Case 3: no-store response must not trigger range-request
    415  chan = make_channel("http://localhost:" + port + "/test_3");
    416  chan.asyncOpen(new MyListener(received_partial_3));
    417 
    418  // Case 4: response with content-encoding must not trigger range-request
    419  chan = make_channel("http://localhost:" + port + "/test_4");
    420  chan.asyncOpen(new MyListener(received_partial_4));
    421 
    422  // Case 5: conditional request-header set by client
    423  chan = make_channel("http://localhost:" + port + "/test_5");
    424  chan.asyncOpen(new MyListener(received_partial_5));
    425 
    426  // Case 6: response is not resumable (drop the Accept-Ranges header)
    427  chan = make_channel("http://localhost:" + port + "/test_6");
    428  chan.asyncOpen(new MyListener(received_partial_6));
    429 
    430  // Case 7: a basic positive test
    431  chan = make_channel("http://localhost:" + port + "/test_7");
    432  chan.asyncOpen(new MyListener(received_partial_7));
    433 
    434  // Case 8: check that mismatched 206 and 200 sizes throw error
    435  chan = make_channel("http://localhost:" + port + "/test_8");
    436  chan.asyncOpen(new MyListener(received_partial_8));
    437 
    438  // Case 9: check that weak etag is not used for a range request
    439  chan = make_channel("http://localhost:" + port + "/test_9");
    440  chan.asyncOpen(new MyListener(received_partial_9));
    441 
    442  do_test_pending();
    443 }