netwerk/test/unit/test_duplicate_headers.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

     1 /*
     2  * Tests bugs 597706, 655389: prevent duplicate headers with differing values
     3  * for some headers like Content-Length, Location, etc.
     4  */
     6 ////////////////////////////////////////////////////////////////////////////////
     7 // Test infrastructure
     9 Cu.import("resource://testing-common/httpd.js");
    11 XPCOMUtils.defineLazyGetter(this, "URL", function() {
    12   return "http://localhost:" + httpserver.identity.primaryPort;
    13 });
    15 var httpserver = new HttpServer();
    16 var index = 0;
    17 var test_flags = new Array();
    18 var testPathBase = "/dupe_hdrs";
    20 function run_test()
    21 {
    22   httpserver.start(-1);
    24   do_test_pending();
    25   run_test_number(1);
    26 }
    28 function run_test_number(num)
    29 {
    30   testPath = testPathBase + num;
    31   httpserver.registerPathHandler(testPath, eval("handler" + num));
    33   var channel = setupChannel(testPath);
    34   flags = test_flags[num];   // OK if flags undefined for test
    35   channel.asyncOpen(new ChannelListener(eval("completeTest" + num),
    36                                         channel, flags), null);
    37 }
    39 function setupChannel(url)
    40 {
    41   var ios = Components.classes["@mozilla.org/network/io-service;1"].
    42                        getService(Ci.nsIIOService);
    43   var chan = ios.newChannel(URL + url, "", null);
    44   var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel);
    45   return httpChan;
    46 }
    48 function endTests()
    49 {
    50   httpserver.stop(do_test_finished);
    51 }
    53 ////////////////////////////////////////////////////////////////////////////////
    54 // Test 1: FAIL because of conflicting Content-Length headers
    55 test_flags[1] = CL_EXPECT_FAILURE;
    57 function handler1(metadata, response)
    58 {
    59   var body = "012345678901234567890123456789";
    60   // Comrades!  We must seize power from the petty-bourgeois running dogs of
    61   // httpd.js in order to reply with multiple instances of the same header!
    62   response.seizePower();
    63   response.write("HTTP/1.0 200 OK\r\n");
    64   response.write("Content-Type: text/plain\r\n");
    65   response.write("Content-Length: 30\r\n");
    66   response.write("Content-Length: 20\r\n");
    67   response.write("\r\n");
    68   response.write(body);
    69   response.finish();
    70 }
    73 function completeTest1(request, data, ctx)
    74 {
    75   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
    77   run_test_number(2);
    78 }
    80 ////////////////////////////////////////////////////////////////////////////////
    81 // Test 2: OK to have duplicate same Content-Length headers
    83 function handler2(metadata, response)
    84 {
    85   var body = "012345678901234567890123456789";
    86   response.seizePower();
    87   response.write("HTTP/1.0 200 OK\r\n");
    88   response.write("Content-Type: text/plain\r\n");
    89   response.write("Content-Length: 30\r\n");
    90   response.write("Content-Length: 30\r\n");
    91   response.write("\r\n");
    92   response.write(body);
    93   response.finish();
    94 }
    96 function completeTest2(request, data, ctx)
    97 {
    98   do_check_eq(request.status, 0);
    99   run_test_number(3);
   100 }
   102 ////////////////////////////////////////////////////////////////////////////////
   103 // Test 3: FAIL: 2nd Content-length is blank
   104 test_flags[3] = CL_EXPECT_FAILURE;
   106 function handler3(metadata, response)
   107 {
   108   var body = "012345678901234567890123456789";
   109   response.seizePower();
   110   response.write("HTTP/1.0 200 OK\r\n");
   111   response.write("Content-Type: text/plain\r\n");
   112   response.write("Content-Length: 30\r\n");
   113   response.write("Content-Length:\r\n");
   114   response.write("\r\n");
   115   response.write(body);
   116   response.finish();
   117 }
   119 function completeTest3(request, data, ctx)
   120 {
   121   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   123   run_test_number(4);
   124 }
   126 ////////////////////////////////////////////////////////////////////////////////
   127 // Test 4: ensure that blank C-len header doesn't allow attacker to reset Clen, 
   128 // then insert CRLF attack
   129 test_flags[4] = CL_EXPECT_FAILURE;
   131 function handler4(metadata, response)
   132 {
   133   var body = "012345678901234567890123456789";
   135   response.seizePower();
   136   response.write("HTTP/1.0 200 OK\r\n");
   137   response.write("Content-Type: text/plain\r\n");
   138   response.write("Content-Length: 30\r\n");
   140   // Bad Mr Hacker!  Bad!
   141   var evilBody = "We are the Evil bytes, Evil bytes, Evil bytes!";
   142   response.write("Content-Length:\r\n");
   143   response.write("Content-Length: %s\r\n\r\n%s" % (evilBody.length, evilBody));
   144   response.write("\r\n");
   145   response.write(body);
   146   response.finish();
   147 }
   149 function completeTest4(request, data, ctx)
   150 {
   151   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   153   run_test_number(5);
   154 }
   157 ////////////////////////////////////////////////////////////////////////////////
   158 // Test 5: ensure that we take 1st instance of duplicate, nonmerged headers that
   159 // are permitted : (ex: Referrer)
   161 function handler5(metadata, response)
   162 {
   163   var body = "012345678901234567890123456789";
   164   response.seizePower();
   165   response.write("HTTP/1.0 200 OK\r\n");
   166   response.write("Content-Type: text/plain\r\n");
   167   response.write("Content-Length: 30\r\n");
   168   response.write("Referer: naive.org\r\n");
   169   response.write("Referer: evil.net\r\n");
   170   response.write("\r\n");
   171   response.write(body);
   172   response.finish();
   173 }
   175 function completeTest5(request, data, ctx)
   176 {
   177   try {
   178     referer = request.getResponseHeader("Referer");
   179     do_check_eq(referer, "naive.org");
   180   } catch (ex) {
   181     do_throw("Referer header should be present");
   182   }
   184   run_test_number(6);
   185 }
   187 ////////////////////////////////////////////////////////////////////////////////
   188 // Test 5: FAIL if multiple, different Location: headers present
   189 // - needed to prevent CRLF injection attacks
   190 test_flags[6] = CL_EXPECT_FAILURE;
   192 function handler6(metadata, response)
   193 {
   194   var body = "012345678901234567890123456789";
   195   response.seizePower();
   196   response.write("HTTP/1.0 301 Moved\r\n");
   197   response.write("Content-Type: text/plain\r\n");
   198   response.write("Content-Length: 30\r\n");
   199   response.write("Location: " + URL + "/content\r\n");
   200   response.write("Location: http://www.microsoft.com/\r\n");
   201   response.write("Connection: close\r\n");
   202   response.write("\r\n");
   203   response.write(body);
   204   response.finish();
   205 }
   207 function completeTest6(request, data, ctx)
   208 {
   209   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   211 //  run_test_number(7);   // Test 7 leaking under e10s: unrelated bug?
   212   run_test_number(8);
   213 }
   215 ////////////////////////////////////////////////////////////////////////////////
   216 // Test 7: OK to have multiple Location: headers with same value
   218 function handler7(metadata, response)
   219 {
   220   var body = "012345678901234567890123456789";
   221   response.seizePower();
   222   response.write("HTTP/1.0 301 Moved\r\n");
   223   response.write("Content-Type: text/plain\r\n");
   224   response.write("Content-Length: 30\r\n");
   225   // redirect to previous test handler that completes OK: test 5
   226   response.write("Location: " + URL + testPathBase + "5\r\n");
   227   response.write("Location: " + URL + testPathBase + "5\r\n");
   228   response.write("Connection: close\r\n");
   229   response.write("\r\n");
   230   response.write(body);
   231   response.finish();
   232 }
   234 function completeTest7(request, data, ctx)
   235 {
   236   // for some reason need this here
   237   request.QueryInterface(Components.interfaces.nsIHttpChannel);
   239   try {
   240     referer = request.getResponseHeader("Referer");
   241     do_check_eq(referer, "naive.org");
   242   } catch (ex) {
   243     do_throw("Referer header should be present");
   244   }
   246   run_test_number(8);
   247 }
   249 ////////////////////////////////////////////////////////////////////////////////
   250 // FAIL if 2nd Location: headers blank
   251 test_flags[8] = CL_EXPECT_FAILURE;
   253 function handler8(metadata, response)
   254 {
   255   var body = "012345678901234567890123456789";
   256   response.seizePower();
   257   response.write("HTTP/1.0 301 Moved\r\n");
   258   response.write("Content-Type: text/plain\r\n");
   259   response.write("Content-Length: 30\r\n");
   260   // redirect to previous test handler that completes OK: test 4
   261   response.write("Location: " + URL + testPathBase + "4\r\n");
   262   response.write("Location:\r\n");
   263   response.write("Connection: close\r\n");
   264   response.write("\r\n");
   265   response.write(body);
   266   response.finish();
   267 }
   269 function completeTest8(request, data, ctx)
   270 {
   271   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   273   run_test_number(9);
   274 }
   276 ////////////////////////////////////////////////////////////////////////////////
   277 // Test 9: ensure that blank Location header doesn't allow attacker to reset, 
   278 // then insert an evil one
   279 test_flags[9] = CL_EXPECT_FAILURE;
   281 function handler9(metadata, response)
   282 {
   283   var body = "012345678901234567890123456789";
   284   response.seizePower();
   285   response.write("HTTP/1.0 301 Moved\r\n");
   286   response.write("Content-Type: text/plain\r\n");
   287   response.write("Content-Length: 30\r\n");
   288   // redirect to previous test handler that completes OK: test 2 
   289   response.write("Location: " + URL + testPathBase + "2\r\n");
   290   response.write("Location:\r\n");
   291   // redirect to previous test handler that completes OK: test 4
   292   response.write("Location: " + URL + testPathBase + "4\r\n");
   293   response.write("Connection: close\r\n");
   294   response.write("\r\n");
   295   response.write(body);
   296   response.finish();
   297 }
   299 function completeTest9(request, data, ctx)
   300 {
   301   // All redirection should fail:
   302   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   304   run_test_number(10);
   305 }
   307 ////////////////////////////////////////////////////////////////////////////////
   308 // Test 10: FAIL:  if conflicting values for Content-Dispo 
   309 test_flags[10] = CL_EXPECT_FAILURE;
   311 function handler10(metadata, response)
   312 {
   313   var body = "012345678901234567890123456789";
   314   response.seizePower();
   315   response.write("HTTP/1.0 200 OK\r\n");
   316   response.write("Content-Type: text/plain\r\n");
   317   response.write("Content-Length: 30\r\n");
   318   response.write("Content-Disposition: attachment; filename=foo\r\n");
   319   response.write("Content-Disposition: attachment; filename=bar\r\n");
   320   response.write("Content-Disposition: attachment; filename=baz\r\n");
   321   response.write("\r\n");
   322   response.write(body);
   323   response.finish();
   324 }
   327 function completeTest10(request, data, ctx)
   328 {
   329   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   331   run_test_number(11);
   332 }
   334 ////////////////////////////////////////////////////////////////////////////////
   335 // Test 11: OK to have duplicate same Content-Disposition headers
   337 function handler11(metadata, response)
   338 {
   339   var body = "012345678901234567890123456789";
   340   response.seizePower();
   341   response.write("HTTP/1.0 200 OK\r\n");
   342   response.write("Content-Type: text/plain\r\n");
   343   response.write("Content-Length: 30\r\n");
   344   response.write("Content-Disposition: attachment; filename=foo\r\n");
   345   response.write("Content-Disposition: attachment; filename=foo\r\n");
   346   response.write("\r\n");
   347   response.write(body);
   348   response.finish();
   349 }
   351 function completeTest11(request, data, ctx)
   352 {
   353   do_check_eq(request.status, 0);
   355   try {
   356     var chan = request.QueryInterface(Ci.nsIChannel);
   357     do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT);
   358     do_check_eq(chan.contentDispositionFilename, "foo");
   359     do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo");
   360   } catch (ex) {
   361     do_throw("error parsing Content-Disposition: " + ex);
   362   }
   364   run_test_number(12);
   365 }
   367 ////////////////////////////////////////////////////////////////////////////////
   368 // Bug 716801 OK for Location: header to be blank
   370 function handler12(metadata, response)
   371 {
   372   var body = "012345678901234567890123456789";
   373   response.seizePower();
   374   response.write("HTTP/1.0 200 OK\r\n");
   375   response.write("Content-Type: text/plain\r\n");
   376   response.write("Content-Length: 30\r\n");
   377   response.write("Location:\r\n");
   378   response.write("Connection: close\r\n");
   379   response.write("\r\n");
   380   response.write(body);
   381   response.finish();
   382 }
   384 function completeTest12(request, data, ctx)
   385 {
   386   do_check_eq(request.status, Components.results.NS_OK);
   387   do_check_eq(30, data.length);
   389   run_test_number(13);
   390 }
   392 ////////////////////////////////////////////////////////////////////////////////
   393 // Negative content length is ok
   394 test_flags[13] = CL_ALLOW_UNKNOWN_CL;
   396 function handler13(metadata, response)
   397 {
   398   var body = "012345678901234567890123456789";
   399   response.seizePower();
   400   response.write("HTTP/1.0 200 OK\r\n");
   401   response.write("Content-Type: text/plain\r\n");
   402   response.write("Content-Length: -1\r\n");
   403   response.write("Connection: close\r\n");
   404   response.write("\r\n");
   405   response.write(body);
   406   response.finish();
   407 }
   409 function completeTest13(request, data, ctx)
   410 {
   411   do_check_eq(request.status, Components.results.NS_OK);
   412   do_check_eq(30, data.length);
   414   run_test_number(14);
   415 }
   417 ////////////////////////////////////////////////////////////////////////////////
   418 // leading negative content length is not ok if paired with positive one
   420 test_flags[14] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
   422 function handler14(metadata, response)
   423 {
   424   var body = "012345678901234567890123456789";
   425   response.seizePower();
   426   response.write("HTTP/1.0 200 OK\r\n");
   427   response.write("Content-Type: text/plain\r\n");
   428   response.write("Content-Length: -1\r\n");
   429   response.write("Content-Length: 30\r\n");
   430   response.write("Connection: close\r\n");
   431   response.write("\r\n");
   432   response.write(body);
   433   response.finish();
   434 }
   436 function completeTest14(request, data, ctx)
   437 {
   438   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   440   run_test_number(15);
   441 }
   443 ////////////////////////////////////////////////////////////////////////////////
   444 // trailing negative content length is not ok if paired with positive one
   446 test_flags[15] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
   448 function handler15(metadata, response)
   449 {
   450   var body = "012345678901234567890123456789";
   451   response.seizePower();
   452   response.write("HTTP/1.0 200 OK\r\n");
   453   response.write("Content-Type: text/plain\r\n");
   454   response.write("Content-Length: 30\r\n");
   455   response.write("Content-Length: -1\r\n");
   456   response.write("Connection: close\r\n");
   457   response.write("\r\n");
   458   response.write(body);
   459   response.finish();
   460 }
   462 function completeTest15(request, data, ctx)
   463 {
   464   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   466   run_test_number(16);
   467 }
   469 ////////////////////////////////////////////////////////////////////////////////
   470 // empty content length is ok
   471 test_flags[16] = CL_ALLOW_UNKNOWN_CL;
   472 reran16 = false;
   474 function handler16(metadata, response)
   475 {
   476   var body = "012345678901234567890123456789";
   477   response.seizePower();
   478   response.write("HTTP/1.0 200 OK\r\n");
   479   response.write("Content-Type: text/plain\r\n");
   480   response.write("Content-Length: \r\n");
   481   response.write("Cache-Control: max-age=600\r\n");
   482   response.write("Connection: close\r\n");
   483   response.write("\r\n");
   484   response.write(body);
   485   response.finish();
   486 }
   488 function completeTest16(request, data, ctx)
   489 {
   490   do_check_eq(request.status, Components.results.NS_OK);
   491   do_check_eq(30, data.length);
   493   if (!reran16) {
   494     reran16 = true;
   495     run_test_number(16);
   496   }
   497  else {
   498    run_test_number(17);
   499  }
   500 }
   502 ////////////////////////////////////////////////////////////////////////////////
   503 // empty content length paired with non empty is not ok
   504 test_flags[17] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL;
   506 function handler17(metadata, response)
   507 {
   508   var body = "012345678901234567890123456789";
   509   response.seizePower();
   510   response.write("HTTP/1.0 200 OK\r\n");
   511   response.write("Content-Type: text/plain\r\n");
   512   response.write("Content-Length: \r\n");
   513   response.write("Content-Length: 30\r\n");
   514   response.write("Connection: close\r\n");
   515   response.write("\r\n");
   516   response.write(body);
   517   response.finish();
   518 }
   520 function completeTest17(request, data, ctx)
   521 {
   522   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   524   run_test_number(18);
   525 }
   527 ////////////////////////////////////////////////////////////////////////////////
   528 // alpha content-length is just like -1
   529 test_flags[18] = CL_ALLOW_UNKNOWN_CL;
   531 function handler18(metadata, response)
   532 {
   533   var body = "012345678901234567890123456789";
   534   response.seizePower();
   535   response.write("HTTP/1.0 200 OK\r\n");
   536   response.write("Content-Type: text/plain\r\n");
   537   response.write("Content-Length: seventeen\r\n");
   538   response.write("Connection: close\r\n");
   539   response.write("\r\n");
   540   response.write(body);
   541   response.finish();
   542 }
   544 function completeTest18(request, data, ctx)
   545 {
   546   do_check_eq(request.status, Components.results.NS_OK);
   547   do_check_eq(30, data.length);
   549   run_test_number(19);
   550 }
   552 ////////////////////////////////////////////////////////////////////////////////
   553 // semi-colons are ok too in the content-length
   554 test_flags[19] = CL_ALLOW_UNKNOWN_CL;
   556 function handler19(metadata, response)
   557 {
   558   var body = "012345678901234567890123456789";
   559   response.seizePower();
   560   response.write("HTTP/1.0 200 OK\r\n");
   561   response.write("Content-Type: text/plain\r\n");
   562   response.write("Content-Length: 30;\r\n");
   563   response.write("Connection: close\r\n");
   564   response.write("\r\n");
   565   response.write(body);
   566   response.finish();
   567 }
   569 function completeTest19(request, data, ctx)
   570 {
   571   do_check_eq(request.status, Components.results.NS_OK);
   572   do_check_eq(30, data.length);
   574   run_test_number(20);
   575 }
   577 ////////////////////////////////////////////////////////////////////////////////
   578 // FAIL if 1st Location: header is blank, followed by non-blank
   579 test_flags[20] = CL_EXPECT_FAILURE;
   581 function handler20(metadata, response)
   582 {
   583   var body = "012345678901234567890123456789";
   584   response.seizePower();
   585   response.write("HTTP/1.0 301 Moved\r\n");
   586   response.write("Content-Type: text/plain\r\n");
   587   response.write("Content-Length: 30\r\n");
   588   // redirect to previous test handler that completes OK: test 4
   589   response.write("Location:\r\n");
   590   response.write("Location: " + URL + testPathBase + "4\r\n");
   591   response.write("Connection: close\r\n");
   592   response.write("\r\n");
   593   response.write(body);
   594   response.finish();
   595 }
   597 function completeTest20(request, data, ctx)
   598 {
   599   do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
   601   endTests();
   602 }

mercurial