netwerk/test/unit/test_range_requests.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/test/unit/test_range_requests.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,394 @@
     1.4 +//
     1.5 +// This test makes sure range-requests are sent and treated the way we want
     1.6 +// See bug #612135 for a thorough discussion on the subject
     1.7 +//
     1.8 +// Necko does a range-request for a partial cache-entry iff
     1.9 +//
    1.10 +//   1) size of the cached entry < value of the cached Content-Length header
    1.11 +//      (not tested here - see bug #612135 comments 108-110)
    1.12 +//   2) the size of the cached entry is > 0  (see bug #628607)
    1.13 +//   3) the cached entry does not have a "no-store" Cache-Control header
    1.14 +//   4) the cached entry does not have a Content-Encoding (see bug #613159)
    1.15 +//   5) the request does not have a conditional-request header set by client
    1.16 +//   6) nsHttpResponseHead::IsResumable() is true for the cached entry
    1.17 +//   7) a basic positive test that makes sure byte ranges work
    1.18 +//   8) ensure NS_ERROR_CORRUPTED_CONTENT is thrown when total entity size
    1.19 +//      of 206 does not match content-length of 200
    1.20 +//
    1.21 +//  The test has one handler for each case and run_tests() fires one request
    1.22 +//  for each. None of the handlers should see a Range-header.
    1.23 +
    1.24 +Cu.import("resource://testing-common/httpd.js");
    1.25 +
    1.26 +var httpserver = null;
    1.27 +
    1.28 +const clearTextBody = "This is a slightly longer test\n";
    1.29 +const encodedBody = [0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x66, 0x69,
    1.30 +                     0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85,
    1.31 +                     0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c, 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85,
    1.32 +                     0x92, 0xd4, 0xe2, 0x12, 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00];
    1.33 +const decodedBody = [0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74,
    1.34 +                     0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x0a];
    1.35 +
    1.36 +const partial_data_length = 4;
    1.37 +var port = null; // set in run_test
    1.38 +
    1.39 +function make_channel(url, callback, ctx) {
    1.40 +  var ios = Cc["@mozilla.org/network/io-service;1"].
    1.41 +            getService(Ci.nsIIOService);
    1.42 +  var chan = ios.newChannel(url, "", null);
    1.43 +  return chan.QueryInterface(Ci.nsIHttpChannel);
    1.44 +}
    1.45 +
    1.46 +// StreamListener which cancels its request on first data available
    1.47 +function Canceler(continueFn) {
    1.48 +  this.continueFn = continueFn;
    1.49 +}
    1.50 +Canceler.prototype = {
    1.51 +  QueryInterface: function(iid) {
    1.52 +    if (iid.equals(Ci.nsIStreamListener) ||
    1.53 +        iid.equals(Ci.nsIRequestObserver) ||
    1.54 +        iid.equals(Ci.nsISupports))
    1.55 +      return this;
    1.56 +    throw Components.results.NS_ERROR_NO_INTERFACE;
    1.57 +  },
    1.58 +  onStartRequest: function(request, context) { },
    1.59 +
    1.60 +  onDataAvailable: function(request, context, stream, offset, count) {
    1.61 +    request.QueryInterface(Ci.nsIChannel)
    1.62 +           .cancel(Components.results.NS_BINDING_ABORTED);
    1.63 +  },
    1.64 +  onStopRequest: function(request, context, status) {
    1.65 +    do_check_eq(status, Components.results.NS_BINDING_ABORTED);
    1.66 +    this.continueFn(request, null);
    1.67 +  }
    1.68 +};
    1.69 +// Simple StreamListener which performs no validations
    1.70 +function MyListener(continueFn) {
    1.71 +  this.continueFn = continueFn;
    1.72 +  this._buffer = null;
    1.73 +}
    1.74 +MyListener.prototype = {
    1.75 +  QueryInterface: function(iid) {
    1.76 +    if (iid.equals(Ci.nsIStreamListener) ||
    1.77 +        iid.equals(Ci.nsIRequestObserver) ||
    1.78 +        iid.equals(Ci.nsISupports))
    1.79 +      return this;
    1.80 +    throw Components.results.NS_ERROR_NO_INTERFACE;
    1.81 +  },
    1.82 +  onStartRequest: function(request, context) { this._buffer = ""; },
    1.83 +
    1.84 +  onDataAvailable: function(request, context, stream, offset, count) {
    1.85 +    this._buffer = this._buffer.concat(read_stream(stream, count));
    1.86 +  },
    1.87 +  onStopRequest: function(request, context, status) {
    1.88 +    this.continueFn(request, this._buffer);
    1.89 +  }
    1.90 +};
    1.91 +
    1.92 +var case_8_range_request = false;
    1.93 +function FailedChannelListener(continueFn) {
    1.94 +  this.continueFn = continueFn;
    1.95 +}
    1.96 +FailedChannelListener.prototype = {
    1.97 +  QueryInterface: function(iid) {
    1.98 +    if (iid.equals(Ci.nsIStreamListener) ||
    1.99 +        iid.equals(Ci.nsIRequestObserver) ||
   1.100 +        iid.equals(Ci.nsISupports))
   1.101 +      return this;
   1.102 +    throw Components.results.NS_ERROR_NO_INTERFACE;
   1.103 +  },
   1.104 +  onStartRequest: function(request, context) { },
   1.105 +
   1.106 +  onDataAvailable: function(request, context, stream, offset, count) { },
   1.107 +
   1.108 +  onStopRequest: function(request, context, status) {
   1.109 +    if (case_8_range_request)
   1.110 +      do_check_eq(status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   1.111 +    this.continueFn(request, null);
   1.112 +  }
   1.113 +};
   1.114 +
   1.115 +function received_cleartext(request, data) {
   1.116 +  do_check_eq(clearTextBody, data);
   1.117 +  testFinished();
   1.118 +}
   1.119 +
   1.120 +function setStdHeaders(response, length) {
   1.121 +  response.setHeader("Content-Type", "text/plain", false);
   1.122 +  response.setHeader("ETag", "Just testing");
   1.123 +  response.setHeader("Cache-Control", "max-age: 360000");
   1.124 +  response.setHeader("Accept-Ranges", "bytes");
   1.125 +  response.setHeader("Content-Length", "" + length);
   1.126 +}
   1.127 +
   1.128 +function handler_2(metadata, response) {
   1.129 +  setStdHeaders(response, clearTextBody.length);
   1.130 +  do_check_false(metadata.hasHeader("Range"));
   1.131 +  response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
   1.132 +}
   1.133 +function received_partial_2(request, data) {
   1.134 +  do_check_eq(data, undefined);
   1.135 +  var chan = make_channel("http://localhost:" + port + "/test_2");
   1.136 +  chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   1.137 +}
   1.138 +
   1.139 +var case_3_request_no = 0;
   1.140 +function handler_3(metadata, response) {
   1.141 +  var body = clearTextBody;
   1.142 +  setStdHeaders(response, body.length);
   1.143 +  response.setHeader("Cache-Control", "no-store", false);
   1.144 +  switch (case_3_request_no) {
   1.145 +    case 0:
   1.146 +      do_check_false(metadata.hasHeader("Range"));
   1.147 +      body = body.slice(0, partial_data_length);
   1.148 +      response.processAsync();
   1.149 +      response.bodyOutputStream.write(body, body.length);
   1.150 +      response.finish();
   1.151 +      break;
   1.152 +    case 1:
   1.153 +      do_check_false(metadata.hasHeader("Range"));
   1.154 +      response.bodyOutputStream.write(body, body.length);
   1.155 +      break;
   1.156 +    default:
   1.157 +      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   1.158 +  }
   1.159 +  case_3_request_no++;
   1.160 +}
   1.161 +function received_partial_3(request, data) {
   1.162 +  do_check_eq(partial_data_length, data.length);
   1.163 +  var chan = make_channel("http://localhost:" + port + "/test_3");
   1.164 +  chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   1.165 +}
   1.166 +
   1.167 +var case_4_request_no = 0;
   1.168 +function handler_4(metadata, response) {
   1.169 +  switch (case_4_request_no) {
   1.170 +    case 0:
   1.171 +      do_check_false(metadata.hasHeader("Range"));
   1.172 +      var body = encodedBody;
   1.173 +      setStdHeaders(response, body.length);
   1.174 +      response.setHeader("Content-Encoding", "gzip", false);
   1.175 +      body = body.slice(0, partial_data_length);
   1.176 +	  var bos = Cc["@mozilla.org/binaryoutputstream;1"]
   1.177 +	              .createInstance(Ci.nsIBinaryOutputStream);
   1.178 +	  bos.setOutputStream(response.bodyOutputStream);
   1.179 +      response.processAsync();
   1.180 +      bos.writeByteArray(body, body.length);
   1.181 +      response.finish();
   1.182 +      break;
   1.183 +    case 1:
   1.184 +      do_check_false(metadata.hasHeader("Range"));
   1.185 +      setStdHeaders(response, clearTextBody.length);
   1.186 +      response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
   1.187 +      break;
   1.188 +    default:
   1.189 +      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   1.190 +  }
   1.191 +  case_4_request_no++;
   1.192 +}
   1.193 +function received_partial_4(request, data) {
   1.194 +// checking length does not work with encoded data
   1.195 +//  do_check_eq(partial_data_length, data.length);
   1.196 +  var chan = make_channel("http://localhost:" + port + "/test_4");
   1.197 +  chan.asyncOpen(new MyListener(received_cleartext), null);
   1.198 +}
   1.199 +
   1.200 +var case_5_request_no = 0;
   1.201 +function handler_5(metadata, response) {
   1.202 +  var body = clearTextBody;
   1.203 +  setStdHeaders(response, body.length);
   1.204 +  switch (case_5_request_no) {
   1.205 +    case 0:
   1.206 +      do_check_false(metadata.hasHeader("Range"));
   1.207 +      body = body.slice(0, partial_data_length);
   1.208 +      response.processAsync();
   1.209 +      response.bodyOutputStream.write(body, body.length);
   1.210 +      response.finish();
   1.211 +      break;
   1.212 +    case 1:
   1.213 +      do_check_false(metadata.hasHeader("Range"));
   1.214 +      response.bodyOutputStream.write(body, body.length);
   1.215 +      break;
   1.216 +    default:
   1.217 +      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   1.218 +  }
   1.219 +  case_5_request_no++;
   1.220 +}
   1.221 +function received_partial_5(request, data) {
   1.222 +  do_check_eq(partial_data_length, data.length);
   1.223 +  var chan = make_channel("http://localhost:" + port + "/test_5");
   1.224 +  chan.setRequestHeader("If-Match", "Some eTag", false);
   1.225 +  chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   1.226 +}
   1.227 +
   1.228 +var case_6_request_no = 0;
   1.229 +function handler_6(metadata, response) {
   1.230 +  switch (case_6_request_no) {
   1.231 +    case 0:
   1.232 +      do_check_false(metadata.hasHeader("Range"));
   1.233 +      var body = clearTextBody;
   1.234 +      setStdHeaders(response, body.length);
   1.235 +      response.setHeader("Accept-Ranges", "", false);
   1.236 +      body = body.slice(0, partial_data_length);
   1.237 +      response.processAsync();
   1.238 +      response.bodyOutputStream.write(body, body.length);
   1.239 +      response.finish();
   1.240 +      break;
   1.241 +    case 1:
   1.242 +      do_check_false(metadata.hasHeader("Range"));
   1.243 +      setStdHeaders(response, clearTextBody.length);
   1.244 +      response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
   1.245 +      break;
   1.246 +    default:
   1.247 +      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   1.248 +  }
   1.249 +  case_6_request_no++;
   1.250 +}
   1.251 +function received_partial_6(request, data) {
   1.252 +// would like to verify that the response does not have Accept-Ranges
   1.253 +  do_check_eq(partial_data_length, data.length);
   1.254 +  var chan = make_channel("http://localhost:" + port + "/test_6");
   1.255 +  chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   1.256 +}
   1.257 +
   1.258 +const simpleBody = "0123456789";
   1.259 +
   1.260 +function received_simple(request, data) {
   1.261 +  do_check_eq(simpleBody, data);
   1.262 +  testFinished();
   1.263 +}
   1.264 +
   1.265 +var case_7_request_no = 0;
   1.266 +function handler_7(metadata, response) {
   1.267 +  switch (case_7_request_no) {
   1.268 +    case 0:
   1.269 +      do_check_false(metadata.hasHeader("Range"));
   1.270 +      response.setHeader("Content-Type", "text/plain", false);
   1.271 +      response.setHeader("ETag", "test7Etag");
   1.272 +      response.setHeader("Accept-Ranges", "bytes");
   1.273 +      response.setHeader("Cache-Control", "max-age=360000");
   1.274 +      response.setHeader("Content-Length", "10");
   1.275 +      response.processAsync();
   1.276 +      response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
   1.277 +      response.finish();
   1.278 +      break;
   1.279 +    case 1:
   1.280 +      response.setHeader("Content-Type", "text/plain", false);
   1.281 +      response.setHeader("ETag", "test7Etag");
   1.282 +      if (metadata.hasHeader("Range")) {
   1.283 +	  do_check_true(metadata.hasHeader("If-Range"));
   1.284 +	  response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
   1.285 +	  response.setHeader("Content-Range", "4-9/10");
   1.286 +	  response.setHeader("Content-Length", "6");
   1.287 +	  response.bodyOutputStream.write(simpleBody.slice(4), 6);
   1.288 +      } else {
   1.289 +	  response.setHeader("Content-Length", "10");
   1.290 +	  response.bodyOutputStream.write(simpleBody, 10);
   1.291 +      }
   1.292 +      break;
   1.293 +    default:
   1.294 +      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   1.295 +  }
   1.296 +  case_7_request_no++;
   1.297 +}
   1.298 +function received_partial_7(request, data) {
   1.299 +  // make sure we get the first 4 bytes
   1.300 +  do_check_eq(4, data.length);
   1.301 +  // do it again to get the rest
   1.302 +  var chan = make_channel("http://localhost:" + port + "/test_7");
   1.303 +  chan.asyncOpen(new ChannelListener(received_simple, null), null);
   1.304 +}
   1.305 +
   1.306 +var case_8_request_no = 0;
   1.307 +function handler_8(metadata, response) {
   1.308 +  switch (case_8_request_no) {
   1.309 +    case 0:
   1.310 +      do_check_false(metadata.hasHeader("Range"));
   1.311 +      response.setHeader("Content-Type", "text/plain", false);
   1.312 +      response.setHeader("ETag", "test8Etag");
   1.313 +      response.setHeader("Accept-Ranges", "bytes");
   1.314 +      response.setHeader("Cache-Control", "max-age=360000");
   1.315 +      response.setHeader("Content-Length", "10");
   1.316 +      response.processAsync();
   1.317 +      response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
   1.318 +      response.finish();
   1.319 +      break;
   1.320 +    case 1:
   1.321 +      if (metadata.hasHeader("Range")) {
   1.322 +	  do_check_true(metadata.hasHeader("If-Range"));
   1.323 +	  case_8_range_request = true;
   1.324 +      }
   1.325 +      response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
   1.326 +      response.setHeader("Content-Type", "text/plain", false);
   1.327 +      response.setHeader("ETag", "test8Etag");
   1.328 +      response.setHeader("Content-Range", "4-8/9"); // intentionally broken
   1.329 +      response.setHeader("Content-Length", "5");
   1.330 +      response.bodyOutputStream.write(simpleBody.slice(4), 5);
   1.331 +      break;
   1.332 +    default:
   1.333 +      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   1.334 +  }
   1.335 +  case_8_request_no++;
   1.336 +}
   1.337 +function received_partial_8(request, data) {
   1.338 +  // make sure we get the first 4 bytes
   1.339 +  do_check_eq(4, data.length);
   1.340 +  // do it again to get the rest
   1.341 +  var chan = make_channel("http://localhost:" + port + "/test_8");
   1.342 +  chan.asyncOpen(new FailedChannelListener(testFinished, null, CL_EXPECT_LATE_FAILURE), null);
   1.343 +}
   1.344 +
   1.345 +// Simple mechanism to keep track of tests and stop the server
   1.346 +var numTestsFinished = 0;
   1.347 +function testFinished() {
   1.348 +  if (++numTestsFinished == 7)
   1.349 +    httpserver.stop(do_test_finished);
   1.350 +}
   1.351 +
   1.352 +function run_test() {
   1.353 +  httpserver = new HttpServer();
   1.354 +  httpserver.registerPathHandler("/test_2", handler_2);
   1.355 +  httpserver.registerPathHandler("/test_3", handler_3);
   1.356 +  httpserver.registerPathHandler("/test_4", handler_4);
   1.357 +  httpserver.registerPathHandler("/test_5", handler_5);
   1.358 +  httpserver.registerPathHandler("/test_6", handler_6);
   1.359 +  httpserver.registerPathHandler("/test_7", handler_7);
   1.360 +  httpserver.registerPathHandler("/test_8", handler_8);
   1.361 +  httpserver.start(-1);
   1.362 +
   1.363 +  port = httpserver.identity.primaryPort;
   1.364 +
   1.365 +  // wipe out cached content
   1.366 +  evict_cache_entries();
   1.367 +
   1.368 +  // Case 2: zero-length partial entry must not trigger range-request
   1.369 +  var chan = make_channel("http://localhost:" + port + "/test_2");
   1.370 +  chan.asyncOpen(new Canceler(received_partial_2), null);
   1.371 +
   1.372 +  // Case 3: no-store response must not trigger range-request
   1.373 +  var chan = make_channel("http://localhost:" + port + "/test_3");
   1.374 +  chan.asyncOpen(new MyListener(received_partial_3), null);
   1.375 +
   1.376 +  // Case 4: response with content-encoding must not trigger range-request
   1.377 +  var chan = make_channel("http://localhost:" + port + "/test_4");
   1.378 +  chan.asyncOpen(new MyListener(received_partial_4), null);
   1.379 +
   1.380 +  // Case 5: conditional request-header set by client
   1.381 +  var chan = make_channel("http://localhost:" + port + "/test_5");
   1.382 +  chan.asyncOpen(new MyListener(received_partial_5), null);
   1.383 +
   1.384 +  // Case 6: response is not resumable (drop the Accept-Ranges header)
   1.385 +  var chan = make_channel("http://localhost:" + port + "/test_6");
   1.386 +  chan.asyncOpen(new MyListener(received_partial_6), null);
   1.387 +
   1.388 +  // Case 7: a basic positive test
   1.389 +  var chan = make_channel("http://localhost:" + port + "/test_7");
   1.390 +  chan.asyncOpen(new MyListener(received_partial_7), null);
   1.391 +
   1.392 +  // Case 8: check that mismatched 206 and 200 sizes throw error
   1.393 +  var chan = make_channel("http://localhost:" + port + "/test_8");
   1.394 +  chan.asyncOpen(new MyListener(received_partial_8), null);
   1.395 +
   1.396 +  do_test_pending();
   1.397 +}

mercurial