Bug 426273. Make sure to clear out bogus Content-Disposition values before checking whether we can handle the 206 response. r+sr=biesi
authorBoris Zbarsky <bzbarsky@mit.edu>
Thu, 31 Jul 2008 17:55:14 -0700
changeset 16322 1238046c4ccedc722b035ae4ac9aa9fc2b4b00aa
parent 16321 e5b10a4a4e80fe85ec4ed138d4b94e1e231534ce
child 16323 b3689adc7e26e4966c0d69b2635fe67ad2975645
push id934
push userbzbarsky@mozilla.com
push dateFri, 01 Aug 2008 00:55:21 +0000
treeherdermozilla-central@1238046c4cce [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs426273
milestone1.9.1a2pre
Bug 426273. Make sure to clear out bogus Content-Disposition values before checking whether we can handle the 206 response. r+sr=biesi
netwerk/protocol/http/src/nsHttpChannel.cpp
netwerk/protocol/http/src/nsHttpChannel.h
netwerk/test/unit/head_channels.js
netwerk/test/unit/test_gzipped_206.js
--- 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();
+}