netwerk/test/unit/test_range_requests.js

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     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.
    21 Cu.import("resource://testing-common/httpd.js");
    23 var httpserver = null;
    25 const clearTextBody = "This is a slightly longer test\n";
    26 const encodedBody = [0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x66, 0x69,
    27                      0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85,
    28                      0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c, 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85,
    29                      0x92, 0xd4, 0xe2, 0x12, 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00];
    30 const decodedBody = [0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74,
    31                      0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x0a];
    33 const partial_data_length = 4;
    34 var port = null; // set in run_test
    36 function make_channel(url, callback, ctx) {
    37   var ios = Cc["@mozilla.org/network/io-service;1"].
    38             getService(Ci.nsIIOService);
    39   var chan = ios.newChannel(url, "", null);
    40   return chan.QueryInterface(Ci.nsIHttpChannel);
    41 }
    43 // StreamListener which cancels its request on first data available
    44 function Canceler(continueFn) {
    45   this.continueFn = continueFn;
    46 }
    47 Canceler.prototype = {
    48   QueryInterface: function(iid) {
    49     if (iid.equals(Ci.nsIStreamListener) ||
    50         iid.equals(Ci.nsIRequestObserver) ||
    51         iid.equals(Ci.nsISupports))
    52       return this;
    53     throw Components.results.NS_ERROR_NO_INTERFACE;
    54   },
    55   onStartRequest: function(request, context) { },
    57   onDataAvailable: function(request, context, stream, offset, count) {
    58     request.QueryInterface(Ci.nsIChannel)
    59            .cancel(Components.results.NS_BINDING_ABORTED);
    60   },
    61   onStopRequest: function(request, context, status) {
    62     do_check_eq(status, Components.results.NS_BINDING_ABORTED);
    63     this.continueFn(request, null);
    64   }
    65 };
    66 // Simple StreamListener which performs no validations
    67 function MyListener(continueFn) {
    68   this.continueFn = continueFn;
    69   this._buffer = null;
    70 }
    71 MyListener.prototype = {
    72   QueryInterface: function(iid) {
    73     if (iid.equals(Ci.nsIStreamListener) ||
    74         iid.equals(Ci.nsIRequestObserver) ||
    75         iid.equals(Ci.nsISupports))
    76       return this;
    77     throw Components.results.NS_ERROR_NO_INTERFACE;
    78   },
    79   onStartRequest: function(request, context) { this._buffer = ""; },
    81   onDataAvailable: function(request, context, stream, offset, count) {
    82     this._buffer = this._buffer.concat(read_stream(stream, count));
    83   },
    84   onStopRequest: function(request, context, status) {
    85     this.continueFn(request, this._buffer);
    86   }
    87 };
    89 var case_8_range_request = false;
    90 function FailedChannelListener(continueFn) {
    91   this.continueFn = continueFn;
    92 }
    93 FailedChannelListener.prototype = {
    94   QueryInterface: function(iid) {
    95     if (iid.equals(Ci.nsIStreamListener) ||
    96         iid.equals(Ci.nsIRequestObserver) ||
    97         iid.equals(Ci.nsISupports))
    98       return this;
    99     throw Components.results.NS_ERROR_NO_INTERFACE;
   100   },
   101   onStartRequest: function(request, context) { },
   103   onDataAvailable: function(request, context, stream, offset, count) { },
   105   onStopRequest: function(request, context, status) {
   106     if (case_8_range_request)
   107       do_check_eq(status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   108     this.continueFn(request, null);
   109   }
   110 };
   112 function received_cleartext(request, data) {
   113   do_check_eq(clearTextBody, data);
   114   testFinished();
   115 }
   117 function setStdHeaders(response, length) {
   118   response.setHeader("Content-Type", "text/plain", false);
   119   response.setHeader("ETag", "Just testing");
   120   response.setHeader("Cache-Control", "max-age: 360000");
   121   response.setHeader("Accept-Ranges", "bytes");
   122   response.setHeader("Content-Length", "" + length);
   123 }
   125 function handler_2(metadata, response) {
   126   setStdHeaders(response, clearTextBody.length);
   127   do_check_false(metadata.hasHeader("Range"));
   128   response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
   129 }
   130 function received_partial_2(request, data) {
   131   do_check_eq(data, undefined);
   132   var chan = make_channel("http://localhost:" + port + "/test_2");
   133   chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   134 }
   136 var case_3_request_no = 0;
   137 function handler_3(metadata, response) {
   138   var body = clearTextBody;
   139   setStdHeaders(response, body.length);
   140   response.setHeader("Cache-Control", "no-store", false);
   141   switch (case_3_request_no) {
   142     case 0:
   143       do_check_false(metadata.hasHeader("Range"));
   144       body = body.slice(0, partial_data_length);
   145       response.processAsync();
   146       response.bodyOutputStream.write(body, body.length);
   147       response.finish();
   148       break;
   149     case 1:
   150       do_check_false(metadata.hasHeader("Range"));
   151       response.bodyOutputStream.write(body, body.length);
   152       break;
   153     default:
   154       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   155   }
   156   case_3_request_no++;
   157 }
   158 function received_partial_3(request, data) {
   159   do_check_eq(partial_data_length, data.length);
   160   var chan = make_channel("http://localhost:" + port + "/test_3");
   161   chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   162 }
   164 var case_4_request_no = 0;
   165 function handler_4(metadata, response) {
   166   switch (case_4_request_no) {
   167     case 0:
   168       do_check_false(metadata.hasHeader("Range"));
   169       var body = encodedBody;
   170       setStdHeaders(response, body.length);
   171       response.setHeader("Content-Encoding", "gzip", false);
   172       body = body.slice(0, partial_data_length);
   173 	  var bos = Cc["@mozilla.org/binaryoutputstream;1"]
   174 	              .createInstance(Ci.nsIBinaryOutputStream);
   175 	  bos.setOutputStream(response.bodyOutputStream);
   176       response.processAsync();
   177       bos.writeByteArray(body, body.length);
   178       response.finish();
   179       break;
   180     case 1:
   181       do_check_false(metadata.hasHeader("Range"));
   182       setStdHeaders(response, clearTextBody.length);
   183       response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
   184       break;
   185     default:
   186       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   187   }
   188   case_4_request_no++;
   189 }
   190 function received_partial_4(request, data) {
   191 // checking length does not work with encoded data
   192 //  do_check_eq(partial_data_length, data.length);
   193   var chan = make_channel("http://localhost:" + port + "/test_4");
   194   chan.asyncOpen(new MyListener(received_cleartext), null);
   195 }
   197 var case_5_request_no = 0;
   198 function handler_5(metadata, response) {
   199   var body = clearTextBody;
   200   setStdHeaders(response, body.length);
   201   switch (case_5_request_no) {
   202     case 0:
   203       do_check_false(metadata.hasHeader("Range"));
   204       body = body.slice(0, partial_data_length);
   205       response.processAsync();
   206       response.bodyOutputStream.write(body, body.length);
   207       response.finish();
   208       break;
   209     case 1:
   210       do_check_false(metadata.hasHeader("Range"));
   211       response.bodyOutputStream.write(body, body.length);
   212       break;
   213     default:
   214       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   215   }
   216   case_5_request_no++;
   217 }
   218 function received_partial_5(request, data) {
   219   do_check_eq(partial_data_length, data.length);
   220   var chan = make_channel("http://localhost:" + port + "/test_5");
   221   chan.setRequestHeader("If-Match", "Some eTag", false);
   222   chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   223 }
   225 var case_6_request_no = 0;
   226 function handler_6(metadata, response) {
   227   switch (case_6_request_no) {
   228     case 0:
   229       do_check_false(metadata.hasHeader("Range"));
   230       var body = clearTextBody;
   231       setStdHeaders(response, body.length);
   232       response.setHeader("Accept-Ranges", "", false);
   233       body = body.slice(0, partial_data_length);
   234       response.processAsync();
   235       response.bodyOutputStream.write(body, body.length);
   236       response.finish();
   237       break;
   238     case 1:
   239       do_check_false(metadata.hasHeader("Range"));
   240       setStdHeaders(response, clearTextBody.length);
   241       response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
   242       break;
   243     default:
   244       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   245   }
   246   case_6_request_no++;
   247 }
   248 function received_partial_6(request, data) {
   249 // would like to verify that the response does not have Accept-Ranges
   250   do_check_eq(partial_data_length, data.length);
   251   var chan = make_channel("http://localhost:" + port + "/test_6");
   252   chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
   253 }
   255 const simpleBody = "0123456789";
   257 function received_simple(request, data) {
   258   do_check_eq(simpleBody, data);
   259   testFinished();
   260 }
   262 var case_7_request_no = 0;
   263 function handler_7(metadata, response) {
   264   switch (case_7_request_no) {
   265     case 0:
   266       do_check_false(metadata.hasHeader("Range"));
   267       response.setHeader("Content-Type", "text/plain", false);
   268       response.setHeader("ETag", "test7Etag");
   269       response.setHeader("Accept-Ranges", "bytes");
   270       response.setHeader("Cache-Control", "max-age=360000");
   271       response.setHeader("Content-Length", "10");
   272       response.processAsync();
   273       response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
   274       response.finish();
   275       break;
   276     case 1:
   277       response.setHeader("Content-Type", "text/plain", false);
   278       response.setHeader("ETag", "test7Etag");
   279       if (metadata.hasHeader("Range")) {
   280 	  do_check_true(metadata.hasHeader("If-Range"));
   281 	  response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
   282 	  response.setHeader("Content-Range", "4-9/10");
   283 	  response.setHeader("Content-Length", "6");
   284 	  response.bodyOutputStream.write(simpleBody.slice(4), 6);
   285       } else {
   286 	  response.setHeader("Content-Length", "10");
   287 	  response.bodyOutputStream.write(simpleBody, 10);
   288       }
   289       break;
   290     default:
   291       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   292   }
   293   case_7_request_no++;
   294 }
   295 function received_partial_7(request, data) {
   296   // make sure we get the first 4 bytes
   297   do_check_eq(4, data.length);
   298   // do it again to get the rest
   299   var chan = make_channel("http://localhost:" + port + "/test_7");
   300   chan.asyncOpen(new ChannelListener(received_simple, null), null);
   301 }
   303 var case_8_request_no = 0;
   304 function handler_8(metadata, response) {
   305   switch (case_8_request_no) {
   306     case 0:
   307       do_check_false(metadata.hasHeader("Range"));
   308       response.setHeader("Content-Type", "text/plain", false);
   309       response.setHeader("ETag", "test8Etag");
   310       response.setHeader("Accept-Ranges", "bytes");
   311       response.setHeader("Cache-Control", "max-age=360000");
   312       response.setHeader("Content-Length", "10");
   313       response.processAsync();
   314       response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
   315       response.finish();
   316       break;
   317     case 1:
   318       if (metadata.hasHeader("Range")) {
   319 	  do_check_true(metadata.hasHeader("If-Range"));
   320 	  case_8_range_request = true;
   321       }
   322       response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
   323       response.setHeader("Content-Type", "text/plain", false);
   324       response.setHeader("ETag", "test8Etag");
   325       response.setHeader("Content-Range", "4-8/9"); // intentionally broken
   326       response.setHeader("Content-Length", "5");
   327       response.bodyOutputStream.write(simpleBody.slice(4), 5);
   328       break;
   329     default:
   330       response.setStatusLine(metadata.httpVersion, 404, "Not Found");
   331   }
   332   case_8_request_no++;
   333 }
   334 function received_partial_8(request, data) {
   335   // make sure we get the first 4 bytes
   336   do_check_eq(4, data.length);
   337   // do it again to get the rest
   338   var chan = make_channel("http://localhost:" + port + "/test_8");
   339   chan.asyncOpen(new FailedChannelListener(testFinished, null, CL_EXPECT_LATE_FAILURE), null);
   340 }
   342 // Simple mechanism to keep track of tests and stop the server
   343 var numTestsFinished = 0;
   344 function testFinished() {
   345   if (++numTestsFinished == 7)
   346     httpserver.stop(do_test_finished);
   347 }
   349 function run_test() {
   350   httpserver = new HttpServer();
   351   httpserver.registerPathHandler("/test_2", handler_2);
   352   httpserver.registerPathHandler("/test_3", handler_3);
   353   httpserver.registerPathHandler("/test_4", handler_4);
   354   httpserver.registerPathHandler("/test_5", handler_5);
   355   httpserver.registerPathHandler("/test_6", handler_6);
   356   httpserver.registerPathHandler("/test_7", handler_7);
   357   httpserver.registerPathHandler("/test_8", handler_8);
   358   httpserver.start(-1);
   360   port = httpserver.identity.primaryPort;
   362   // wipe out cached content
   363   evict_cache_entries();
   365   // Case 2: zero-length partial entry must not trigger range-request
   366   var chan = make_channel("http://localhost:" + port + "/test_2");
   367   chan.asyncOpen(new Canceler(received_partial_2), null);
   369   // Case 3: no-store response must not trigger range-request
   370   var chan = make_channel("http://localhost:" + port + "/test_3");
   371   chan.asyncOpen(new MyListener(received_partial_3), null);
   373   // Case 4: response with content-encoding must not trigger range-request
   374   var chan = make_channel("http://localhost:" + port + "/test_4");
   375   chan.asyncOpen(new MyListener(received_partial_4), null);
   377   // Case 5: conditional request-header set by client
   378   var chan = make_channel("http://localhost:" + port + "/test_5");
   379   chan.asyncOpen(new MyListener(received_partial_5), null);
   381   // Case 6: response is not resumable (drop the Accept-Ranges header)
   382   var chan = make_channel("http://localhost:" + port + "/test_6");
   383   chan.asyncOpen(new MyListener(received_partial_6), null);
   385   // Case 7: a basic positive test
   386   var chan = make_channel("http://localhost:" + port + "/test_7");
   387   chan.asyncOpen(new MyListener(received_partial_7), null);
   389   // Case 8: check that mismatched 206 and 200 sizes throw error
   390   var chan = make_channel("http://localhost:" + port + "/test_8");
   391   chan.asyncOpen(new MyListener(received_partial_8), null);
   393   do_test_pending();
   394 }

mercurial