michael@0: /* michael@0: * Tests bugs 597706, 655389: prevent duplicate headers with differing values michael@0: * for some headers like Content-Length, Location, etc. michael@0: */ michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test infrastructure michael@0: michael@0: Cu.import("resource://testing-common/httpd.js"); michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "URL", function() { michael@0: return "http://localhost:" + httpserver.identity.primaryPort; michael@0: }); michael@0: michael@0: var httpserver = new HttpServer(); michael@0: var index = 0; michael@0: var test_flags = new Array(); michael@0: var testPathBase = "/dupe_hdrs"; michael@0: michael@0: function run_test() michael@0: { michael@0: httpserver.start(-1); michael@0: michael@0: do_test_pending(); michael@0: run_test_number(1); michael@0: } michael@0: michael@0: function run_test_number(num) michael@0: { michael@0: testPath = testPathBase + num; michael@0: httpserver.registerPathHandler(testPath, eval("handler" + num)); michael@0: michael@0: var channel = setupChannel(testPath); michael@0: flags = test_flags[num]; // OK if flags undefined for test michael@0: channel.asyncOpen(new ChannelListener(eval("completeTest" + num), michael@0: channel, flags), null); michael@0: } michael@0: michael@0: function setupChannel(url) michael@0: { michael@0: var ios = Components.classes["@mozilla.org/network/io-service;1"]. michael@0: getService(Ci.nsIIOService); michael@0: var chan = ios.newChannel(URL + url, "", null); michael@0: var httpChan = chan.QueryInterface(Components.interfaces.nsIHttpChannel); michael@0: return httpChan; michael@0: } michael@0: michael@0: function endTests() michael@0: { michael@0: httpserver.stop(do_test_finished); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 1: FAIL because of conflicting Content-Length headers michael@0: test_flags[1] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler1(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: // Comrades! We must seize power from the petty-bourgeois running dogs of michael@0: // httpd.js in order to reply with multiple instances of the same header! michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Content-Length: 20\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: michael@0: function completeTest1(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(2); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 2: OK to have duplicate same Content-Length headers michael@0: michael@0: function handler2(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest2(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, 0); michael@0: run_test_number(3); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 3: FAIL: 2nd Content-length is blank michael@0: test_flags[3] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler3(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Content-Length:\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest3(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(4); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 4: ensure that blank C-len header doesn't allow attacker to reset Clen, michael@0: // then insert CRLF attack michael@0: test_flags[4] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler4(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: michael@0: // Bad Mr Hacker! Bad! michael@0: var evilBody = "We are the Evil bytes, Evil bytes, Evil bytes!"; michael@0: response.write("Content-Length:\r\n"); michael@0: response.write("Content-Length: %s\r\n\r\n%s" % (evilBody.length, evilBody)); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest4(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(5); michael@0: } michael@0: michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 5: ensure that we take 1st instance of duplicate, nonmerged headers that michael@0: // are permitted : (ex: Referrer) michael@0: michael@0: function handler5(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Referer: naive.org\r\n"); michael@0: response.write("Referer: evil.net\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest5(request, data, ctx) michael@0: { michael@0: try { michael@0: referer = request.getResponseHeader("Referer"); michael@0: do_check_eq(referer, "naive.org"); michael@0: } catch (ex) { michael@0: do_throw("Referer header should be present"); michael@0: } michael@0: michael@0: run_test_number(6); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 5: FAIL if multiple, different Location: headers present michael@0: // - needed to prevent CRLF injection attacks michael@0: test_flags[6] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler6(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 301 Moved\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Location: " + URL + "/content\r\n"); michael@0: response.write("Location: http://www.microsoft.com/\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest6(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: // run_test_number(7); // Test 7 leaking under e10s: unrelated bug? michael@0: run_test_number(8); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 7: OK to have multiple Location: headers with same value michael@0: michael@0: function handler7(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 301 Moved\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: // redirect to previous test handler that completes OK: test 5 michael@0: response.write("Location: " + URL + testPathBase + "5\r\n"); michael@0: response.write("Location: " + URL + testPathBase + "5\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest7(request, data, ctx) michael@0: { michael@0: // for some reason need this here michael@0: request.QueryInterface(Components.interfaces.nsIHttpChannel); michael@0: michael@0: try { michael@0: referer = request.getResponseHeader("Referer"); michael@0: do_check_eq(referer, "naive.org"); michael@0: } catch (ex) { michael@0: do_throw("Referer header should be present"); michael@0: } michael@0: michael@0: run_test_number(8); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // FAIL if 2nd Location: headers blank michael@0: test_flags[8] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler8(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 301 Moved\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: // redirect to previous test handler that completes OK: test 4 michael@0: response.write("Location: " + URL + testPathBase + "4\r\n"); michael@0: response.write("Location:\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest8(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(9); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 9: ensure that blank Location header doesn't allow attacker to reset, michael@0: // then insert an evil one michael@0: test_flags[9] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler9(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 301 Moved\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: // redirect to previous test handler that completes OK: test 2 michael@0: response.write("Location: " + URL + testPathBase + "2\r\n"); michael@0: response.write("Location:\r\n"); michael@0: // redirect to previous test handler that completes OK: test 4 michael@0: response.write("Location: " + URL + testPathBase + "4\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest9(request, data, ctx) michael@0: { michael@0: // All redirection should fail: michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(10); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 10: FAIL: if conflicting values for Content-Dispo michael@0: test_flags[10] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler10(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Content-Disposition: attachment; filename=foo\r\n"); michael@0: response.write("Content-Disposition: attachment; filename=bar\r\n"); michael@0: response.write("Content-Disposition: attachment; filename=baz\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: michael@0: function completeTest10(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(11); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Test 11: OK to have duplicate same Content-Disposition headers michael@0: michael@0: function handler11(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Content-Disposition: attachment; filename=foo\r\n"); michael@0: response.write("Content-Disposition: attachment; filename=foo\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest11(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, 0); michael@0: michael@0: try { michael@0: var chan = request.QueryInterface(Ci.nsIChannel); michael@0: do_check_eq(chan.contentDisposition, chan.DISPOSITION_ATTACHMENT); michael@0: do_check_eq(chan.contentDispositionFilename, "foo"); michael@0: do_check_eq(chan.contentDispositionHeader, "attachment; filename=foo"); michael@0: } catch (ex) { michael@0: do_throw("error parsing Content-Disposition: " + ex); michael@0: } michael@0: michael@0: run_test_number(12); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Bug 716801 OK for Location: header to be blank michael@0: michael@0: function handler12(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Location:\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest12(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_OK); michael@0: do_check_eq(30, data.length); michael@0: michael@0: run_test_number(13); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // Negative content length is ok michael@0: test_flags[13] = CL_ALLOW_UNKNOWN_CL; michael@0: michael@0: function handler13(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: -1\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest13(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_OK); michael@0: do_check_eq(30, data.length); michael@0: michael@0: run_test_number(14); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // leading negative content length is not ok if paired with positive one michael@0: michael@0: test_flags[14] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL; michael@0: michael@0: function handler14(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: -1\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest14(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(15); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // trailing negative content length is not ok if paired with positive one michael@0: michael@0: test_flags[15] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL; michael@0: michael@0: function handler15(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Content-Length: -1\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest15(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(16); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // empty content length is ok michael@0: test_flags[16] = CL_ALLOW_UNKNOWN_CL; michael@0: reran16 = false; michael@0: michael@0: function handler16(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: \r\n"); michael@0: response.write("Cache-Control: max-age=600\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest16(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_OK); michael@0: do_check_eq(30, data.length); michael@0: michael@0: if (!reran16) { michael@0: reran16 = true; michael@0: run_test_number(16); michael@0: } michael@0: else { michael@0: run_test_number(17); michael@0: } michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // empty content length paired with non empty is not ok michael@0: test_flags[17] = CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL; michael@0: michael@0: function handler17(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: \r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest17(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: run_test_number(18); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // alpha content-length is just like -1 michael@0: test_flags[18] = CL_ALLOW_UNKNOWN_CL; michael@0: michael@0: function handler18(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: seventeen\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest18(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_OK); michael@0: do_check_eq(30, data.length); michael@0: michael@0: run_test_number(19); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // semi-colons are ok too in the content-length michael@0: test_flags[19] = CL_ALLOW_UNKNOWN_CL; michael@0: michael@0: function handler19(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 200 OK\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30;\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest19(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_OK); michael@0: do_check_eq(30, data.length); michael@0: michael@0: run_test_number(20); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: // FAIL if 1st Location: header is blank, followed by non-blank michael@0: test_flags[20] = CL_EXPECT_FAILURE; michael@0: michael@0: function handler20(metadata, response) michael@0: { michael@0: var body = "012345678901234567890123456789"; michael@0: response.seizePower(); michael@0: response.write("HTTP/1.0 301 Moved\r\n"); michael@0: response.write("Content-Type: text/plain\r\n"); michael@0: response.write("Content-Length: 30\r\n"); michael@0: // redirect to previous test handler that completes OK: test 4 michael@0: response.write("Location:\r\n"); michael@0: response.write("Location: " + URL + testPathBase + "4\r\n"); michael@0: response.write("Connection: close\r\n"); michael@0: response.write("\r\n"); michael@0: response.write(body); michael@0: response.finish(); michael@0: } michael@0: michael@0: function completeTest20(request, data, ctx) michael@0: { michael@0: do_check_eq(request.status, Components.results.NS_ERROR_CORRUPTED_CONTENT); michael@0: michael@0: endTests(); michael@0: } michael@0: