Bug 426273. Make sure to clear out bogus Content-Disposition values before checking whether we can handle the 206 response. r+sr=biesi
--- a/netwerk/protocol/http/src/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/src/nsHttpChannel.cpp
@@ -874,35 +874,17 @@ nsHttpChannel::ProcessNormal()
LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
// if we're here, then any byte-range requests failed to result in a partial
// response. we must clear this flag to prevent BufferPartialContent from
// being called inside our OnDataAvailable (see bug 136678).
mCachedContentIsPartial = PR_FALSE;
- // For .gz files, apache sends both a Content-Type: application/x-gzip
- // as well as Content-Encoding: gzip, which is completely wrong. In
- // this case, we choose to ignore the rogue Content-Encoding header. We
- // must do this early on so as to prevent it from being seen up stream.
- // The same problem exists for Content-Encoding: compress in default
- // Apache installs.
- if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
- mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
- mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
- mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
- // clear the Content-Encoding header
- mResponseHead->ClearHeader(nsHttp::Content_Encoding);
- }
- else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
- mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
- mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
- // clear the Content-Encoding header
- mResponseHead->ClearHeader(nsHttp::Content_Encoding);
- }
+ ClearBogusContentEncodingIfNeeded();
// this must be called before firing OnStartRequest, since http clients,
// such as imagelib, expect our cache entry to already have the correct
// expiration time (bug 87710).
if (mCacheEntry) {
rv = InitCacheEntry();
if (NS_FAILED(rv))
CloseCacheEntry();
@@ -1194,16 +1176,19 @@ nsHttpChannel::ProcessPartialContent()
// we need to stream whatever data is in the cache out first, and then
// pick up whatever data is on the wire, writing it into the cache.
LOG(("nsHttpChannel::ProcessPartialContent [this=%x]\n", this));
NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
+ // Make sure to clear bogus content-encodings before looking at the header
+ ClearBogusContentEncodingIfNeeded();
+
// Check if the content-encoding we now got is different from the one we
// got before
if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
!= 0) {
Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
return CallOnStartRequest();
}
@@ -2228,16 +2213,40 @@ nsHttpChannel::InstallOfflineCacheListen
rv = tee->Init(mListener, out);
if (NS_FAILED(rv)) return rv;
mListener = tee;
return NS_OK;
}
+void
+nsHttpChannel::ClearBogusContentEncodingIfNeeded()
+{
+ // For .gz files, apache sends both a Content-Type: application/x-gzip
+ // as well as Content-Encoding: gzip, which is completely wrong. In
+ // this case, we choose to ignore the rogue Content-Encoding header. We
+ // must do this early on so as to prevent it from being seen up stream.
+ // The same problem exists for Content-Encoding: compress in default
+ // Apache installs.
+ if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
+ mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
+ mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
+ mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
+ // clear the Content-Encoding header
+ mResponseHead->ClearHeader(nsHttp::Content_Encoding);
+ }
+ else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
+ mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
+ mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
+ // clear the Content-Encoding header
+ mResponseHead->ClearHeader(nsHttp::Content_Encoding);
+ }
+}
+
//-----------------------------------------------------------------------------
// nsHttpChannel <redirect>
//-----------------------------------------------------------------------------
PR_STATIC_CALLBACK(PLDHashOperator)
CopyProperties(const nsAString& aKey, nsIVariant *aData, void *aClosure)
{
nsIWritablePropertyBag* bag = static_cast<nsIWritablePropertyBag*>
--- a/netwerk/protocol/http/src/nsHttpChannel.h
+++ b/netwerk/protocol/http/src/nsHttpChannel.h
@@ -188,16 +188,19 @@ private:
nsresult InitCacheEntry();
nsresult InitOfflineCacheEntry();
nsresult AddCacheEntryHeaders(nsICacheEntryDescriptor *entry);
nsresult StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry);
nsresult FinalizeCacheEntry();
nsresult InstallCacheListener(PRUint32 offset = 0);
nsresult InstallOfflineCacheListener();
+ // Handle the bogus Content-Encoding Apache sometimes sends
+ void ClearBogusContentEncodingIfNeeded();
+
// byte range request specific methods
nsresult SetupByteRangeRequest(PRUint32 partialLen);
nsresult ProcessPartialContent();
nsresult OnDoneReadingPartialCacheEntry(PRBool *streamDone);
// auth specific methods
nsresult PrepareForAuthentication(PRBool proxyAuth);
nsresult GenCredsAndSetEntry(nsIHttpAuthenticator *, PRBool proxyAuth, const char *scheme, const char *host, PRInt32 port, const char *dir, const char *realm, const char *challenge, const nsHttpAuthIdentity &ident, nsCOMPtr<nsISupports> &session, char **result);
--- a/netwerk/test/unit/head_channels.js
+++ b/netwerk/test/unit/head_channels.js
@@ -17,16 +17,17 @@ function read_stream(stream, count) {
count -= bytes.length;
if (bytes.length == 0)
do_throw("Nothing read from input stream!");
}
return data.join('');
}
const CL_EXPECT_FAILURE = 0x1;
+const CL_EXPECT_GZIP = 0x2;
/**
* A stream listener that calls a callback function with a specified
* context and the received data when the channel is loaded.
*
* Signature of the closure:
* void closure(in nsIRequest request, in ACString data, in JSObject context);
*
@@ -46,19 +47,19 @@ ChannelListener.prototype = {
_closure: null,
_closurectx: null,
_buffer: "",
_got_onstartrequest: false,
_got_onstoprequest: false,
_contentLen: -1,
QueryInterface: function(iid) {
- if (iid.Equals(Components.interfaces.nsIStreamChannelListener) ||
- iid.Equals(Components.interfaces.nsIRequestObserver) ||
- iid.Equals(Components.interfaces.nsISupports))
+ if (iid.equals(Components.interfaces.nsIStreamListener) ||
+ iid.equals(Components.interfaces.nsIRequestObserver) ||
+ iid.equals(Components.interfaces.nsISupports))
return this;
throw Components.results.NS_ERROR_NO_INTERFACE;
},
onStartRequest: function(request, context) {
if (this._got_onstartrequest)
do_throw("Got second onStartRequest event!");
this._got_onstartrequest = true;
@@ -93,14 +94,15 @@ ChannelListener.prototype = {
do_throw("Should have failed to load URL (status is " + status.toString(16) + ")");
else if (!(this._flags & CL_EXPECT_FAILURE) && !success)
do_throw("Failed to load URL: " + status.toString(16));
if (status != request.status)
do_throw("request.status does not match status arg to onStopRequest!");
if (request.isPending())
do_throw("request reports itself as pending from onStopRequest!");
if (!(this._flags & CL_EXPECT_FAILURE) &&
- this._contentLen != -1 && this._buffer.length != this._contentLen)
- do_throw("did not read nsIChannel.contentLength number of bytes!");
+ !(this._flags & CL_EXPECT_GZIP) &&
+ this._contentLen != -1)
+ do_check_eq(this._buffer.length, this._contentLen)
this._closure(request, this._buffer, this._closurectx);
}
};
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_gzipped_206.js
@@ -0,0 +1,99 @@
+do_import_script("netwerk/test/httpserver/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];
+
+function make_channel(url, callback, ctx) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return ios.newChannel(url, "", null);
+}
+
+var doRangeResponse = false;
+
+function cachedHandler(metadata, response) {
+ response.setHeader("Content-Type", "application/x-gzip", false);
+ response.setHeader("Content-Encoding", "gzip", false);
+ response.setHeader("ETag", "Just testing");
+
+ var body = responseBody;
+
+ if (doRangeResponse) {
+ do_check_true(metadata.hasHeader("Range"));
+ var matches = metadata.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
+ var from = (matches[1] === undefined) ? 0 : matches[1];
+ var to = (matches[2] === undefined) ? responseBody.length - 1 : matches[2];
+ if (from >= responseBody.length) {
+ response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
+ response.setHeader("Content-Range", "*/" + responseBody.length, false);
+ return;
+ }
+ body = body.slice(from, to + 1);
+ // 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 {
+ response.setHeader("Accept-Ranges", "bytes");
+ doRangeResponse = true;
+ }
+
+ var bos = Cc["@mozilla.org/binaryoutputstream;1"]
+ .createInstance(Ci.nsIBinaryOutputStream);
+ bos.setOutputStream(response.bodyOutputStream);
+
+ bos.writeByteArray(body, body.length);
+}
+
+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() {
+ var chan = make_channel("http://localhost:4444/cached/test.gz");
+ chan.asyncOpen(new ChannelListener(finish_test, null), null);
+}
+
+function finish_test(request, data, ctx) {
+ httpserver.stop();
+ 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();
+}
+
+function run_test() {
+ httpserver = new nsHttpServer();
+ httpserver.registerPathHandler("/cached/test.gz", cachedHandler);
+ httpserver.start(4444);
+
+ var chan = make_channel("http://localhost:4444/cached/test.gz");
+ chan.asyncOpen(new Canceler(), null);
+ do_test_pending();
+}