зеркало из https://github.com/mozilla/pjs.git
Bug 612135: Content Encoding Error (partial content request) on galaxys.samsungmobile.com r=bzbarsky a=blocking2.0
--HG-- extra : rebase_source : 32ff74259046fd2f3a673df39620acb10accf2cb
This commit is contained in:
Родитель
6b2549b1e2
Коммит
7b2db5a65f
|
@ -2486,6 +2486,13 @@ nsHttpChannel::CheckCache()
|
|||
|
||||
PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
|
||||
|
||||
mCustomConditionalRequest =
|
||||
mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_Match) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_Range);
|
||||
|
||||
if (method != nsHttp::Head && !isCachedRedirect) {
|
||||
// If the cached content-length is set and it does not match the data
|
||||
// size of the cached content, then the cached response is partial...
|
||||
|
@ -2500,8 +2507,17 @@ nsHttpChannel::CheckCache()
|
|||
if (nsInt64(size) != contentLength) {
|
||||
LOG(("Cached data size does not match the Content-Length header "
|
||||
"[content-length=%lld size=%u]\n", PRInt64(contentLength), size));
|
||||
if ((nsInt64(size) < contentLength) && mCachedResponseHead->IsResumable()) {
|
||||
// looks like a partial entry.
|
||||
|
||||
PRBool hasContentEncoding =
|
||||
mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding)
|
||||
!= nsnull;
|
||||
if ((nsInt64(size) < contentLength) &&
|
||||
size > 0 &&
|
||||
!hasContentEncoding &&
|
||||
mCachedResponseHead->IsResumable() &&
|
||||
!mCustomConditionalRequest &&
|
||||
!mCachedResponseHead->NoStore()) {
|
||||
// looks like a partial entry we can reuse
|
||||
rv = SetupByteRangeRequest(size);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mCachedContentIsPartial = PR_TRUE;
|
||||
|
@ -2514,13 +2530,6 @@ nsHttpChannel::CheckCache()
|
|||
PRBool doValidation = PR_FALSE;
|
||||
PRBool canAddImsHeader = PR_TRUE;
|
||||
|
||||
mCustomConditionalRequest =
|
||||
mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_Match) ||
|
||||
mRequestHead.PeekHeader(nsHttp::If_Range);
|
||||
|
||||
// If the LOAD_FROM_CACHE flag is set, any cached data can simply be used.
|
||||
if (mLoadFlags & LOAD_FROM_CACHE) {
|
||||
LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
|
||||
|
|
|
@ -2,9 +2,18 @@ do_load_httpd_js();
|
|||
|
||||
var httpserver = null;
|
||||
|
||||
const responseBody = [0x1f, 0x8b, 0x08, 0x00, 0x16, 0x5a, 0x8a, 0x48, 0x02,
|
||||
0x03, 0x2b, 0x49, 0x2d, 0x2e, 0xe1, 0x02, 0x00, 0xc6,
|
||||
0x35, 0xb9, 0x3b, 0x05, 0x00, 0x00, 0x00];
|
||||
// testString = "This is a slightly longer test\n";
|
||||
const responseBody = [0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85,
|
||||
0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c, 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85,
|
||||
0x92, 0xd4, 0xe2, 0x12, 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00];
|
||||
|
||||
function getCacheService()
|
||||
{
|
||||
var nsCacheService = Components.classes["@mozilla.org/network/cache-service;1"];
|
||||
var service = nsCacheService.getService(Components.interfaces.nsICacheService);
|
||||
return service;
|
||||
}
|
||||
|
||||
function make_channel(url, callback, ctx) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].
|
||||
|
@ -18,6 +27,8 @@ function cachedHandler(metadata, response) {
|
|||
response.setHeader("Content-Type", "application/x-gzip", false);
|
||||
response.setHeader("Content-Encoding", "gzip", false);
|
||||
response.setHeader("ETag", "Just testing");
|
||||
response.setHeader("Cache-Control", "max-age=3600000"); // avoid validation
|
||||
response.setHeader("Content-Length", "" + responseBody.length);
|
||||
|
||||
var body = responseBody;
|
||||
|
||||
|
@ -37,6 +48,7 @@ function cachedHandler(metadata, response) {
|
|||
response.setHeader("Content-Range", from + "-" + to + "/" + responseBody.length, false);
|
||||
} else {
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
body = body.slice(0, 17); // slice off a piece to send first
|
||||
doRangeResponse = true;
|
||||
}
|
||||
|
||||
|
@ -44,49 +56,24 @@ function cachedHandler(metadata, response) {
|
|||
.createInstance(Ci.nsIBinaryOutputStream);
|
||||
bos.setOutputStream(response.bodyOutputStream);
|
||||
|
||||
response.processAsync();
|
||||
bos.writeByteArray(body, body.length);
|
||||
response.finish();
|
||||
}
|
||||
|
||||
function Canceler() {
|
||||
}
|
||||
|
||||
Canceler.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) {
|
||||
request.QueryInterface(Ci.nsIChannel)
|
||||
.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
},
|
||||
|
||||
onStopRequest: function(request, context, status) {
|
||||
do_check_eq(status, Components.results.NS_BINDING_ABORTED);
|
||||
continue_test();
|
||||
}
|
||||
};
|
||||
|
||||
function continue_test() {
|
||||
function continue_test(request, data) {
|
||||
do_check_true(17 == data.length);
|
||||
var chan = make_channel("http://localhost:4444/cached/test.gz");
|
||||
chan.asyncOpen(new ChannelListener(finish_test, null), null);
|
||||
chan.asyncOpen(new ChannelListener(finish_test, null, CL_EXPECT_GZIP), null);
|
||||
}
|
||||
|
||||
function finish_test(request, data, ctx) {
|
||||
do_test_pending();
|
||||
httpserver.stop(do_test_finished);
|
||||
do_check_eq(request.status, 0);
|
||||
do_check_eq(data.length, responseBody.length);
|
||||
for (var i = 0; i < data.length; ++i) {
|
||||
do_check_eq(data.charCodeAt(i), responseBody[i]);
|
||||
}
|
||||
do_test_finished();
|
||||
httpserver.stop(do_test_finished);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
|
@ -94,7 +81,10 @@ function run_test() {
|
|||
httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
|
||||
httpserver.start(4444);
|
||||
|
||||
// wipe out cached content
|
||||
getCacheService().evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
|
||||
|
||||
var chan = make_channel("http://localhost:4444/cached/test.gz");
|
||||
chan.asyncOpen(new Canceler(), null);
|
||||
chan.asyncOpen(new ChannelListener(continue_test, null, CL_EXPECT_GZIP), null);
|
||||
do_test_pending();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,275 @@
|
|||
//
|
||||
// This test makes sure range-requests are sent and treated the way we want
|
||||
// See bug #612135 for a thorough discussion on the subject
|
||||
//
|
||||
// Necko does a range-request for a partial cache-entry iff
|
||||
//
|
||||
// 1) size of the cached entry < value of the cached Content-Length header
|
||||
// (not tested here - see bug #612135 comments 108-110)
|
||||
// 2) the size of the cached entry is > 0 (see bug #628607)
|
||||
// 3) the cached entry does not have a "no-store" Cache-Control header
|
||||
// 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
|
||||
// 6) nsHttpResponseHead::IsResumable() is true for the cached entry
|
||||
//
|
||||
// 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.
|
||||
|
||||
do_load_httpd_js();
|
||||
|
||||
var httpserver = null;
|
||||
|
||||
const clearTextBody = "This is a slightly longer test\n";
|
||||
const encodedBody = [0x1f, 0x8b, 0x08, 0x08, 0xef, 0x70, 0xe6, 0x4c, 0x00, 0x03, 0x74, 0x65, 0x78, 0x74, 0x66, 0x69,
|
||||
0x6c, 0x65, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x0b, 0xc9, 0xc8, 0x2c, 0x56, 0x00, 0xa2, 0x44, 0x85,
|
||||
0xe2, 0x9c, 0xcc, 0xf4, 0x8c, 0x92, 0x9c, 0x4a, 0x85, 0x9c, 0xfc, 0xbc, 0xf4, 0xd4, 0x22, 0x85,
|
||||
0x92, 0xd4, 0xe2, 0x12, 0x2e, 0x2e, 0x00, 0x00, 0xe5, 0xe6, 0xf0, 0x20, 0x00, 0x00, 0x00];
|
||||
const decodedBody = [0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x6c, 0x69, 0x67, 0x68, 0x74,
|
||||
0x6c, 0x79, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x74, 0x65, 0x73, 0x74, 0x0a, 0x0a];
|
||||
|
||||
const partial_data_length = 4;
|
||||
|
||||
function getCacheService()
|
||||
{
|
||||
var nsCacheService = Components.classes["@mozilla.org/network/cache-service;1"];
|
||||
var service = nsCacheService.getService(Components.interfaces.nsICacheService);
|
||||
return service;
|
||||
}
|
||||
|
||||
function make_channel(url, callback, ctx) {
|
||||
var ios = Cc["@mozilla.org/network/io-service;1"].
|
||||
getService(Ci.nsIIOService);
|
||||
var chan = ios.newChannel(url, "", null);
|
||||
return chan.QueryInterface(Ci.nsIHttpChannel);
|
||||
}
|
||||
|
||||
// StreamListener which cancels its request on first data available
|
||||
function Canceler(continueFn) {
|
||||
this.continueFn = continueFn;
|
||||
}
|
||||
Canceler.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) {
|
||||
request.QueryInterface(Ci.nsIChannel)
|
||||
.cancel(Components.results.NS_BINDING_ABORTED);
|
||||
},
|
||||
onStopRequest: function(request, context, status) {
|
||||
do_check_eq(status, Components.results.NS_BINDING_ABORTED);
|
||||
this.continueFn(request, null);
|
||||
}
|
||||
};
|
||||
// Simple StreamListener which performs no validations
|
||||
function MyListener(continueFn) {
|
||||
this.continueFn = continueFn;
|
||||
this._buffer = null;
|
||||
}
|
||||
MyListener.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) { this._buffer = ""; },
|
||||
|
||||
onDataAvailable: function(request, context, stream, offset, count) {
|
||||
this._buffer = this._buffer.concat(read_stream(stream, count));
|
||||
},
|
||||
onStopRequest: function(request, context, status) {
|
||||
this.continueFn(request, this._buffer);
|
||||
}
|
||||
};
|
||||
|
||||
function received_cleartext(request, data) {
|
||||
do_check_eq(clearTextBody, data);
|
||||
testFinished();
|
||||
}
|
||||
|
||||
function setStdHeaders(response, length) {
|
||||
response.setHeader("Content-Type", "text/plain", false);
|
||||
response.setHeader("ETag", "Just testing");
|
||||
response.setHeader("Cache-Control", "max-age: 360000");
|
||||
response.setHeader("Accept-Ranges", "bytes");
|
||||
response.setHeader("Content-Length", "" + length);
|
||||
}
|
||||
|
||||
function handler_2(metadata, response) {
|
||||
setStdHeaders(response, clearTextBody.length);
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
|
||||
}
|
||||
function received_partial_2(request, data) {
|
||||
do_check_eq(data, undefined);
|
||||
var chan = make_channel("http://localhost:4444/test_2");
|
||||
chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
|
||||
}
|
||||
|
||||
var case_3_request_no = 0;
|
||||
function handler_3(metadata, response) {
|
||||
var body = clearTextBody;
|
||||
setStdHeaders(response, body.length);
|
||||
response.setHeader("Cache-Control", "no-store", false);
|
||||
switch (case_3_request_no) {
|
||||
case 0:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
body = body.slice(0, partial_data_length);
|
||||
response.processAsync();
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
response.finish();
|
||||
break;
|
||||
case 1:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
break;
|
||||
default:
|
||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
||||
}
|
||||
case_3_request_no++;
|
||||
}
|
||||
function received_partial_3(request, data) {
|
||||
do_check_eq(partial_data_length, data.length);
|
||||
var chan = make_channel("http://localhost:4444/test_3");
|
||||
chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
|
||||
}
|
||||
|
||||
var case_4_request_no = 0;
|
||||
function handler_4(metadata, response) {
|
||||
switch (case_4_request_no) {
|
||||
case 0:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
var body = encodedBody;
|
||||
setStdHeaders(response, body.length);
|
||||
response.setHeader("Content-Encoding", "gzip", false);
|
||||
body = body.slice(0, partial_data_length);
|
||||
var bos = Cc["@mozilla.org/binaryoutputstream;1"]
|
||||
.createInstance(Ci.nsIBinaryOutputStream);
|
||||
bos.setOutputStream(response.bodyOutputStream);
|
||||
response.processAsync();
|
||||
bos.writeByteArray(body, body.length);
|
||||
response.finish();
|
||||
break;
|
||||
case 1:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
setStdHeaders(response, clearTextBody.length);
|
||||
response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
|
||||
break;
|
||||
default:
|
||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
||||
}
|
||||
case_4_request_no++;
|
||||
}
|
||||
function received_partial_4(request, data) {
|
||||
// checking length does not work with encoded data
|
||||
// do_check_eq(partial_data_length, data.length);
|
||||
var chan = make_channel("http://localhost:4444/test_4");
|
||||
chan.asyncOpen(new MyListener(received_cleartext), null);
|
||||
}
|
||||
|
||||
var case_5_request_no = 0;
|
||||
function handler_5(metadata, response) {
|
||||
var body = clearTextBody;
|
||||
setStdHeaders(response, body.length);
|
||||
switch (case_5_request_no) {
|
||||
case 0:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
body = body.slice(0, partial_data_length);
|
||||
response.processAsync();
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
response.finish();
|
||||
break;
|
||||
case 1:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
break;
|
||||
default:
|
||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
||||
}
|
||||
case_5_request_no++;
|
||||
}
|
||||
function received_partial_5(request, data) {
|
||||
do_check_eq(partial_data_length, data.length);
|
||||
var chan = make_channel("http://localhost:4444/test_5");
|
||||
chan.setRequestHeader("If-Match", "Some eTag", false);
|
||||
chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
|
||||
}
|
||||
|
||||
var case_6_request_no = 0;
|
||||
function handler_6(metadata, response) {
|
||||
switch (case_6_request_no) {
|
||||
case 0:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
var body = clearTextBody;
|
||||
setStdHeaders(response, body.length);
|
||||
response.setHeader("Accept-Ranges", "", false);
|
||||
body = body.slice(0, partial_data_length);
|
||||
response.processAsync();
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
response.finish();
|
||||
break;
|
||||
case 1:
|
||||
do_check_false(metadata.hasHeader("Range"));
|
||||
setStdHeaders(response, clearTextBody.length);
|
||||
response.bodyOutputStream.write(clearTextBody, clearTextBody.length);
|
||||
break;
|
||||
default:
|
||||
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
|
||||
}
|
||||
case_6_request_no++;
|
||||
}
|
||||
function received_partial_6(request, data) {
|
||||
// would like to verify that the response does not have Accept-Ranges
|
||||
do_check_eq(partial_data_length, data.length);
|
||||
var chan = make_channel("http://localhost:4444/test_6");
|
||||
chan.asyncOpen(new ChannelListener(received_cleartext, null), null);
|
||||
}
|
||||
|
||||
// Simple mechanism to keep track of tests and stop the server
|
||||
var numTestsFinished = 0;
|
||||
function testFinished() {
|
||||
if (++numTestsFinished == 5)
|
||||
httpserver.stop(do_test_finished);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
httpserver = new nsHttpServer();
|
||||
httpserver.registerPathHandler("/test_2", handler_2);
|
||||
httpserver.registerPathHandler("/test_3", handler_3);
|
||||
httpserver.registerPathHandler("/test_4", handler_4);
|
||||
httpserver.registerPathHandler("/test_5", handler_5);
|
||||
httpserver.registerPathHandler("/test_6", handler_6);
|
||||
httpserver.start(4444);
|
||||
|
||||
// wipe out cached content
|
||||
getCacheService().evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
|
||||
|
||||
// Case 2: zero-length partial entry must not trigger range-request
|
||||
var chan = make_channel("http://localhost:4444/test_2");
|
||||
chan.asyncOpen(new Canceler(received_partial_2), null);
|
||||
|
||||
// Case 3: no-store response must not trigger range-request
|
||||
var chan = make_channel("http://localhost:4444/test_3");
|
||||
chan.asyncOpen(new MyListener(received_partial_3), null);
|
||||
|
||||
// Case 4: response with content-encoding must not trigger range-request
|
||||
var chan = make_channel("http://localhost:4444/test_4");
|
||||
chan.asyncOpen(new MyListener(received_partial_4), null);
|
||||
|
||||
// Case 5: conditional request-header set by client
|
||||
var chan = make_channel("http://localhost:4444/test_5");
|
||||
chan.asyncOpen(new MyListener(received_partial_5), null);
|
||||
|
||||
// Case 6: response is not resumable (drop the Accept-Ranges header)
|
||||
var chan = make_channel("http://localhost:4444/test_6");
|
||||
chan.asyncOpen(new MyListener(received_partial_6), null);
|
||||
|
||||
do_test_pending();
|
||||
}
|
|
@ -8,8 +8,6 @@ function make_channel(url, callback, ctx) {
|
|||
return ios.newChannel(url, "", null);
|
||||
}
|
||||
|
||||
var do304 = false;
|
||||
|
||||
const responseBody = "response body";
|
||||
|
||||
function cachedHandler(metadata, response) {
|
||||
|
@ -27,9 +25,6 @@ function cachedHandler(metadata, response) {
|
|||
// always respond to successful range requests with 206
|
||||
response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
|
||||
response.setHeader("Content-Range", from + "-" + to + "/" + responseBody.length, false);
|
||||
} else if (do304) {
|
||||
response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
|
||||
return;
|
||||
}
|
||||
|
||||
response.setHeader("Content-Type", "text/plain", false);
|
||||
|
@ -71,13 +66,11 @@ function finish_test() {
|
|||
}
|
||||
|
||||
function start_cache_read() {
|
||||
do304 = true;
|
||||
var chan = make_channel("http://localhost:4444/cached/test.gz");
|
||||
chan.asyncOpen(new ChannelListener(finish_test, null), null);
|
||||
}
|
||||
|
||||
function start_canceler() {
|
||||
do304 = false;
|
||||
var chan = make_channel("http://localhost:4444/cached/test.gz");
|
||||
chan.asyncOpen(new Canceler(start_cache_read), null);
|
||||
}
|
||||
|
@ -87,7 +80,6 @@ function run_test() {
|
|||
httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
|
||||
httpserver.start(4444);
|
||||
|
||||
do304 = false;
|
||||
var chan = make_channel("http://localhost:4444/cached/test.gz");
|
||||
chan.asyncOpen(new ChannelListener(start_canceler, null), null);
|
||||
do_test_pending();
|
||||
|
|
Загрузка…
Ссылка в новой задаче