bug 911325 - detect mismatch between 206 content-range and 200 content-length r=jduell

This commit is contained in:
Patrick McManus 2013-09-17 18:44:03 -04:00
parent 71a12b80e5
commit 947b301aa8
2 changed files with 142 additions and 2 deletions

View File

@ -2194,6 +2194,25 @@ nsHttpChannel::ProcessPartialContent()
} }
int64_t cachedContentLength = mCachedResponseHead->ContentLength();
int64_t entitySize = mResponseHead->TotalEntitySize();
LOG(("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
"original content-length %lld, entity-size %lld, content-range %s\n",
this, mTransaction.get(), cachedContentLength, entitySize,
mResponseHead->PeekHeader(nsHttp::Content_Range)));
if ((entitySize >= 0) && (cachedContentLength >= 0) &&
(entitySize != cachedContentLength)) {
LOG(("nsHttpChannel::ProcessPartialContent [this=%p] "
"206 has different total entity size than the content length "
"of the original partially cached entity.\n", this));
mCacheEntry->AsyncDoom(nullptr);
Cancel(NS_ERROR_CORRUPTED_CONTENT);
return CallOnStartRequest();
}
// suspend the current transaction // suspend the current transaction
nsresult rv = mTransactionPump->Suspend(); nsresult rv = mTransactionPump->Suspend();
if (NS_FAILED(rv)) return rv; if (NS_FAILED(rv)) return rv;
@ -5327,7 +5346,10 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
uint64_t progressMax(uint64_t(mResponseHead->ContentLength())); uint64_t progressMax(uint64_t(mResponseHead->ContentLength()));
uint64_t progress = mLogicalOffset + uint64_t(count); uint64_t progress = mLogicalOffset + uint64_t(count);
MOZ_ASSERT(progress <= progressMax, "unexpected progress values");
if (progress > progressMax)
NS_WARNING("unexpected progress values - "
"is server exceeding content length?");
if (NS_IsMainThread()) { if (NS_IsMainThread()) {
OnTransportStatus(nullptr, transportStatus, progress, progressMax); OnTransportStatus(nullptr, transportStatus, progress, progressMax);

View File

@ -11,6 +11,9 @@
// 4) the cached entry does not have a Content-Encoding (see bug #613159) // 4) the cached entry does not have a Content-Encoding (see bug #613159)
// 5) the request does not have a conditional-request header set by client // 5) the request does not have a conditional-request header set by client
// 6) nsHttpResponseHead::IsResumable() is true for the cached entry // 6) nsHttpResponseHead::IsResumable() is true for the cached entry
// 7) a basic positive test that makes sure byte ranges work
// 8) ensure NS_ERROR_CORRUPTED_CONTENT is thrown when total entity size
// of 206 does not match content-length of 200
// //
// The test has one handler for each case and run_tests() fires one request // The test has one handler for each case and run_tests() fires one request
// for each. None of the handlers should see a Range-header. // for each. None of the handlers should see a Range-header.
@ -88,6 +91,28 @@ MyListener.prototype = {
} }
}; };
function FailedChannelListener(continueFn) {
this.continueFn = continueFn;
}
FailedChannelListener.prototype = {
QueryInterface: function(iid) {
if (iid.equals(Ci.nsIStreamListener) ||
iid.equals(Ci.nsIRequestObserver) ||
iid.equals(Ci.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
onStartRequest: function(request, context) { },
onDataAvailable: function(request, context, stream, offset, count) { },
onStopRequest: function(request, context, status) {
do_check_eq(status, Components.results.NS_ERROR_CORRUPTED_CONTENT);
this.continueFn(request, null);
}
};
function received_cleartext(request, data) { function received_cleartext(request, data) {
do_check_eq(clearTextBody, data); do_check_eq(clearTextBody, data);
testFinished(); testFinished();
@ -231,10 +256,93 @@ function received_partial_6(request, data) {
chan.asyncOpen(new ChannelListener(received_cleartext, null), null); chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
} }
const simpleBody = "0123456789";
function received_simple(request, data) {
do_check_eq(simpleBody, data);
testFinished();
}
var case_7_request_no = 0;
function handler_7(metadata, response) {
switch (case_7_request_no) {
case 0:
do_check_false(metadata.hasHeader("Range"));
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("ETag", "test7Etag");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Cache-Control", "max-age=360000");
response.setHeader("Content-Length", "10");
response.processAsync();
response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
response.finish();
break;
case 1:
do_check_true(metadata.hasHeader("Range"));
do_check_true(metadata.hasHeader("If-Range"));
response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("ETag", "test7Etag");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Length", "6");
response.setHeader("Content-Range", "4-9/10");
response.bodyOutputStream.write(simpleBody.slice(4), 6);
break;
default:
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
}
case_7_request_no++;
}
function received_partial_7(request, data) {
// make sure we get the first 4 bytes
do_check_eq(4, data.length);
// do it again to get the rest
var chan = make_channel("http://localhost:" + port + "/test_7");
chan.asyncOpen(new ChannelListener(received_simple, null), null);
}
var case_8_request_no = 0;
function handler_8(metadata, response) {
switch (case_8_request_no) {
case 0:
do_check_false(metadata.hasHeader("Range"));
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("ETag", "test8Etag");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Cache-Control", "max-age=360000");
response.setHeader("Content-Length", "10");
response.processAsync();
response.bodyOutputStream.write(simpleBody.slice(0, 4), 4);
response.finish();
break;
case 1:
do_check_true(metadata.hasHeader("Range"));
do_check_true(metadata.hasHeader("If-Range"));
response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
response.setHeader("Content-Type", "text/plain", false);
response.setHeader("ETag", "test8Etag");
response.setHeader("Accept-Ranges", "bytes");
response.setHeader("Content-Length", "5");
response.setHeader("Content-Range", "4-8/9"); // intentionally broken
response.bodyOutputStream.write(simpleBody.slice(4), 5);
break;
default:
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
}
case_8_request_no++;
}
function received_partial_8(request, data) {
// make sure we get the first 4 bytes
do_check_eq(4, data.length);
// do it again to get the rest
var chan = make_channel("http://localhost:" + port + "/test_8");
chan.asyncOpen(new FailedChannelListener(testFinished, null, CL_EXPECT_LATE_FAILURE), null);
}
// Simple mechanism to keep track of tests and stop the server // Simple mechanism to keep track of tests and stop the server
var numTestsFinished = 0; var numTestsFinished = 0;
function testFinished() { function testFinished() {
if (++numTestsFinished == 5) if (++numTestsFinished == 7)
httpserver.stop(do_test_finished); httpserver.stop(do_test_finished);
} }
@ -245,6 +353,8 @@ function run_test() {
httpserver.registerPathHandler("/test_4", handler_4); httpserver.registerPathHandler("/test_4", handler_4);
httpserver.registerPathHandler("/test_5", handler_5); httpserver.registerPathHandler("/test_5", handler_5);
httpserver.registerPathHandler("/test_6", handler_6); httpserver.registerPathHandler("/test_6", handler_6);
httpserver.registerPathHandler("/test_7", handler_7);
httpserver.registerPathHandler("/test_8", handler_8);
httpserver.start(-1); httpserver.start(-1);
port = httpserver.identity.primaryPort; port = httpserver.identity.primaryPort;
@ -272,5 +382,13 @@ function run_test() {
var chan = make_channel("http://localhost:" + port + "/test_6"); var chan = make_channel("http://localhost:" + port + "/test_6");
chan.asyncOpen(new MyListener(received_partial_6), null); chan.asyncOpen(new MyListener(received_partial_6), null);
// Case 7: a basic positive test
var chan = make_channel("http://localhost:" + port + "/test_7");
chan.asyncOpen(new MyListener(received_partial_7), null);
// Case 8: check that mismatched 206 and 200 sizes throw error
var chan = make_channel("http://localhost:" + port + "/test_8");
chan.asyncOpen(new MyListener(received_partial_8), null);
do_test_pending(); do_test_pending();
} }